[Luogu] P3806 【模板】点分治1

题目背景

感谢hzwer的点分治互测。

题目描述

给定一棵有n个点的树

询问树上距离为k的点对是否存在。

输入输出格式

输入格式:

n,m
接下来n-1条边a,b,c描述a到b有一条长度为c的路径

接下来m行每行询问一个K

输出格式:

对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)

输入输出样例

输入样例#1:

2 1

1 2 2

2

输出样例#1:

AYE

说明

对于30%的数据n<=100

对于60%的数据n<=1000,m<=50

对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000

题目解析

很有必要好好讲讲点分治,很有意思的算法。

对于一些树上的询问(如本题的路径长度),直接进行计算可能带来巨大的复杂度。

如果了解分治的话应该知道我们可以利用分治来降低一些东西的复杂度。

说白了,树上分治分为点分治和边分治。点分治就是一种树上的分治算法。

就这道题而言,选取一个点为重心,所有路径只有两种情况:直接经过这个点或在这个点的子树中。这时我们处理直接经过该点的路径,同时进行分治,递归处理子树。

但是有一个问题:这样的算法可能被某些极端图(链,菊花图等)卡到时间爆炸,我们必须采取对策——找子树的重心作为新的选取点,这样就可以了

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXK = 10000000 + 5;
const int MAXM = 100 + 5;
const int MAXN = 10000 + 5;

int n,m;
int sum,root;
struct Edge {
    int nxt;
    int to,w;
} l[MAXN << 1];
int cnt,head[MAXN];
int maxpart[MAXN],siz[MAXN];
int dis[MAXN],rem[MAXN],q[MAXN];
int test[MAXK],judge[MAXK];
int query[MAXM];
bool vis[MAXN];

void add(int x,int y,int z) {
    cnt++;
    l[cnt].nxt = head[x];
    l[cnt].to = y;
    l[cnt].w = z;
    head[x] = cnt;
    return;
}

void findroot(int x,int from) {
    siz[x] = 1;
    for(int i = head[x]; i; i = l[i].nxt) {
        if(l[i].to == from || vis[l[i].to]) continue;
        findroot(l[i].to,x);
        siz[x] += siz[l[i].to];
        maxpart[x] = max(maxpart[x],siz[l[i].to]);
    }
    maxpart[x] = max(maxpart[x],sum - siz[x]);
    if(maxpart[x] < maxpart[root]) root = x;
    return;
}

void getdis(int x,int from) {
    rem[++rem[0]] = dis[x];
    for(int i = head[x]; i; i = l[i].nxt) {
        if(l[i].to == from || vis[l[i].to]) continue;
        dis[l[i].to] = dis[x] + l[i].w;
        getdis(l[i].to,x);
    }
    return;
}

void calc(int x) {
    int tmp = 0;
    for(int i = head[x]; i; i = l[i].nxt) {
        if(vis[l[i].to]) continue;
        rem[0] = 0;
        dis[l[i].to] = l[i].w;
        getdis(l[i].to,x);
        for(int j = rem[0]; j; j--) {
            for(int k = 1; k <= m; k++) {
                if(query[k] >= rem[j] && judge[query[k] - rem[j]]){
                    test[k] = true;
                }
            }
        }
        for(int j = rem[0]; j; j--) {
            q[++tmp] = rem[j];
            judge[rem[j]] = true;
        }
    }
    for(int i = 1; i <= tmp; i++) {
        judge[q[i]] = false;
    }
    return;
}

void solve(int x) {
    vis[x] = judge[0] = 1;
    calc(x);
    for(int i = head[x]; i; i = l[i].nxt) {
        if(vis[l[i].to])continue;
        sum = siz[l[i].to];
        root = 0;
        findroot(l[i].to,0);
        solve(root);
    }
}

int main() {
    scanf("%d%d",&n,&m);
    int x,y,z;
    for(int i = 1; i < n; i++) {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    for(int i = 1; i <= m; i++) {
        scanf("%d",&query[i]);
    }
    maxpart[root] = sum = n;
    findroot(1,0);
    solve(root);
    for(int i = 1; i <= m; i++) {
        if(test[i]) printf("AYE\n");
        else printf("NAY\n");
    }
    return 0;
}

原文地址:https://www.cnblogs.com/floatiy/p/9478252.html

时间: 2024-10-08 18:36:27

[Luogu] P3806 【模板】点分治1的相关文章

[luogu P3384] [模板]树链剖分

[luogu P3384] [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<iomanip> #include<algorithm> #include<ctime> #include<queue> #inc

模板&#183;点分治(luogu P3806)

[模板]洛谷·点分治 1.求树的重心 树的重心:若A点的子树中最大的子树的size[] 最小时,A为该树的中心 步骤: 所需变量:siz[x] 表示 x 的子树大小(含自己),msz[x] 表示 其子树中最大的子树的大小,sum表示当前子树所有节点个数,root表示当前子树根节点 处理出siz[x],msz[x] 按最大子树最小的标准处理出root inline void GetRoot(int x,int fa){ siz[x]=1;msz[x]=0;siz[x]//表示 x 的子树大小(含自

Luogu 4721 【模板】分治 FFT

还不会这题的多项式求逆的算法. 发现每一项都是一个卷积的形式,那么我们可以使用$NTT$来加速,直接做是$O(n^2logn)$的,我们考虑如何加速转移. 可以采用$cdq$分治的思想,对于区间$[l, r]$中的数,先计算出$[l, mid]$中的数对$[mid + 1, r]$中的数的贡献,然后直接累加到右边去. 容易发现,这样子每一次需要用向量$[l,l + 1, l +  2, \dots, mid]$卷上$g$中$[1, 2, \dots, r - l]$. 时间复杂度$O(nlog^

[题解] Luogu P4721 【模板】分治 FFT

分治FFT的板子为什么要求逆呢 传送门 这个想法有点\(cdq\)啊,就是考虑分治,在算一段区间的时候,我们把他分成两个一样的区间,然后先做左区间的,算完过后把左区间和\(g\)卷积一下,这样就可以算出左区间里的\(f\)对右边的贡献,然后再算右边的就好了. 手玩一组样例吧:g=[0,3,1,2](默认\(g[0] = 0\)) 一开始,只有f[0]=1 f: [1 0|0 0] 然后我们从中间分开来,先算左边的 f: [1|0|0 0] 然后在分下去我们会找到\(f[0]\),就拿这一段和\(

Luogu【模板】树状数组

https://www.luogu.org/problemnew/show/P3374 单点修改, 区间查询 1 //2018年2月18日17:58:16 2 #include <iostream> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 500001; 7 int n, m; 8 int a[N], c[N]; 9 10 inline int lowbit(int x){ 11 return x &

[模板] 点分治

之前搞了一个树的中心,结果当时把点分治给扔下了,现在搞一搞.其实点分治的分治思想很明显,就是把树切成一个个小树,然后在重心的位置再分治就行了. 代码实现有一定困难,但就我觉得前一个函数和树刨的dfs1很像吗,详情见代码. 题干: 题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径 接下来m行每行询问一个K 输出格式: 对于每个K每行输出一个答案,存在输

#50: Luogu 2485 模板

$des$ 1.给定y.z.p,计算y^z mod p 的值: 2.给定y.z.p,计算满足xy ≡z(mod p)的最小非负整数x: 3.给定y.z.p,计算满足y^x ≡z(mod p)的最小非负整数x. $sol$ 模板+模板+模板 #include <bits/stdc++.h> using namespace std; #define LL long long LL n, k; LL Ksm(LL a, LL b, LL p) { LL ret = 1; while(b) { if(

[Luogu P4178]Tree (点分治+splay)

题面 传送门:https://www.luogu.org/problemnew/show/P4178 Solution 首先,长成这样的题目一定是淀粉质跑不掉了. 考虑到我们不知道K的大小,我们可以开一个splay来统计比某个数小的数的数量. 具体做法等我开淀粉质讲解的坑再满满填(咕) Code #include<iostream> #include<vector> #include<cstdio> using namespace std; long long read