点分治

点分治(1·针对裸题)

luogu P3806 【模板】点分治1

-题目描述

给定一棵有n个点的树
询问树上距离为k的点对是否存在。
-输入格式:
n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径
接下来m行每行询问一个K
-输出格式:
对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)

传送门

分析:

我们先定义一些变量:

int n,m,flag;
struct edge{int from,to,cost;}bian[N];
int head[N],tot,sum,root;//sum是子树的总点数,root为根。
int max_son[N],son[N];//max_son是子树的最大集,son为以i为根的子树节点。
bool vis[N],k_ans[100000010];//访问标记,k_ans为k是否存在。
int rode[N];//i到根的距离。
struct node {int ancestors,cost;}dian[N];//ancestors,表示点i除根以外的最浅祖先,cost表示到根的代价。

然后我们要往图里加入点:( 这非常简单)

void add(int l,int r,int k){bian[++tot]=(edge){head[l],r,k};head[l]=tot;}

然后我们要找重心,如果不利用重心对代码进行优化,复杂度可能变大,本蒟蒻就不解释了。找重心只要遍历整棵树,找到所有节点中儿子大小最大值最小的点就是重心,代码中用root表示。

void find_root(int now,int fa)//递归找重心。
{
    son[now]=1,max_son[now]=0;
    for(int i=head[now];i;i=bian[i].from)
    {
        int next=bian[i].to;
        if(vis[next]||next==fa)
            continue;
        find_root(next,now);
        son[now]+=son[next];
        max_son[now]=max(max_son[now],son[next]);
    }
    max_son[now]=max(max_son[now],sum-son[now]);
    if(max_son[now]<max_son[root])
        root=now;
}

然后,就到了整个代码的核心,点分治。我们已知暴力枚举长度,复杂度爆炸,然而,如果我们每次找到这棵树的重心, O(n) O ( n ) 遍历棵树,找到每个节点到根的cost,最后暴利把两条位于不同子树的链组合起来,更新 kans k a n s 数组就好了。接着就删去现在的根节点,找到剩下子树的重心,重复上面的操作就好了,复杂度大概是 nlogn2 n l o g n 2

下面是完整代码:

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<time.h>
#include<vector>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const LL N=2e4+10;
const LL mod=1e9+7;
const LL inf=0x3f3f3f3f;
namespace FastIO
{
    template<typename tp> inline void read(tp &x)
    {
        x=0; register char c=getchar(); register bool f=0;
        for(;c<'0'||c>'9';f|=(c=='-'),c = getchar());
        for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c = getchar());
        if(f) x=-x;
    }
    template<typename tp> inline void write(tp x)
    {
        if (x==0) return (void) (putchar('0'));
        if (x<0) putchar('-'),x=-x;
        LL pr[20]; register LL cnt=0;
        for (;x;x/=10) pr[++cnt]=x%10;
        while (cnt) putchar(pr[cnt--]+'0');
    }
    template<typename tp> inline void writeln(tp x)
    {
        write(x);
        putchar('\n');
    }
}
using namespace FastIO;
int n,m,flag;
struct edge{int from,to,cost;}bian[N];
int head[N],tot,sum,root;//sum是子树的总点数,root为根。
int max_son[N],son[N];//max_son是子树的最大集,son为以i为根的子树节点。
bool vis[N],k_ans[100000010];//访问标记,k_ans为k是否存在。
int rode[N];//i到根的距离。
struct node {int ancestors,cost;}dian[N];
void add(int l,int r,int k){bian[++tot]=(edge){head[l],r,k};head[l]=tot;}
void find_root(int now,int fa)//递归找重心。
{
    son[now]=1,max_son[now]=0;
    for(int i=head[now];i;i=bian[i].from)
    {
        int next=bian[i].to;
        if(vis[next]||next==fa)
            continue;
        find_root(next,now);
        son[now]+=son[next];
        max_son[now]=max(max_son[now],son[next]);
    }
    max_son[now]=max(max_son[now],sum-son[now]);
    if(max_son[now]<max_son[root])
        root=now;
}
int tot_anss;
void solve_dfs(int now,int zuxian,int fa)
{
    for(int i=head[now];i;i=bian[i].from)
    {
        int next=bian[i].to;
        if(vis[next]||fa==next)
            continue;
        rode[next]=rode[now]+bian[i].cost;
        dian[++tot_anss]=(node){zuxian,rode[next]};
        solve_dfs(next,zuxian,now);
    }
}
void find_k(int now)
{
    rode[now]=0,tot_anss=0;
    vis[now]=1;
    for(int i=head[now];i;i=bian[i].from)
    {
        int next=bian[i].to;
        if(vis[next]==1)
        continue;
        rode[next]=bian[i].cost;
        dian[++tot_anss]=(node){next,rode[next]};
        solve_dfs(bian[i].to,bian[i].to,now);
    }
    for (int i=1;i<=tot_anss;i++) k_ans[dian[i].cost]=1;
    for(int i=1;i<=tot_anss;i++)
        for(int j=i+1;j<=tot_anss;j++)
        if(dian[i].ancestors!=dian[j].ancestors)
            k_ans[dian[i].cost+dian[j].cost]=1;
    for(int i=head[now];i;i=bian[i].from)
    {
        int next=bian[i].to;
        if(!vis[next])
        {
            max_son[0]=inf;
            root=0;
            sum=son[next];
            find_root(next,0);
            find_k(root);
        }
    }
}
int main()
{
    read(n),read(m);
    for(int i=1;i<n;i++)
    {
        int l,r,k;
        read(l),read(r),read(k);
        add(l,r,k);add(r,l,k);
    }
    sum=max_son[0]=n,root=0;//root必须更新。
    find_root(1,0);
    find_k(root);
    for(int i=1;i<=m;i++)
    {
        read(flag);
        if(k_ans[flag]==1)
            printf("AYE\n");
        else
            printf("NAY\n");
    }
    return 0;
}

day2 luogu P2634 [国家集训队]聪聪可可

分析:

这道题就是在上题的模板上,把统计 k k <script type="math/tex" id="MathJax-Element-74">k</script>改成%3就好了,所以k_ans数组就没用了,但刚开始,我边的数组开小了,所以wa了一发。

代码如下:

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<time.h>
#include<vector>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const LL N=2e4+10;
const LL mod=1e9+7;
const LL inf=0x3f3f3f3f;
namespace FastIO
{
    template<typename tp> inline void read(tp &x)
    {
        x=0; register char c=getchar(); register bool f=0;
        for(;c<'0'||c>'9';f|=(c=='-'),c = getchar());
        for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c = getchar());
        if(f) x=-x;
    }
    template<typename tp> inline void write(tp x)
    {
        if (x==0) return (void) (putchar('0'));
        if (x<0) putchar('-'),x=-x;
        LL pr[20]; register LL cnt=0;
        for (;x;x/=10) pr[++cnt]=x%10;
        while (cnt) putchar(pr[cnt--]+'0');
    }
    template<typename tp> inline void writeln(tp x)
    {
        write(x);
        putchar('\n');
    }
}
using namespace FastIO;
int n,m,flag;
struct edge{int from,to,cost;}bian[N*2];
int head[N],tot,sum,root;//sum是子树的总点数,root为根。
int max_son[N],son[N];//max_son是子树的最大集,son为以i为根的子树节点。
bool vis[N];//访问标记,k_ans为k是否存在。
int rode[N],ans;//i到根的距离。
struct node {int ancestors,cost;}dian[N];
inline void add(int l,int r,int k){bian[++tot]=(edge){head[l],r,k};head[l]=tot;}
inline void find_root(int now,int fa)//递归找重心。
{
    son[now]=1,max_son[now]=0;
    for(register int i=head[now];i;i=bian[i].from)
    {
        register int next=bian[i].to;
        if(vis[next]||next==fa)
            continue;
        find_root(next,now);
        son[now]+=son[next];
        max_son[now]=max(max_son[now],son[next]);
    }
    max_son[now]=max(max_son[now],sum-son[now]);
    if(max_son[now]<max_son[root])
        root=now;
}
int tot_anss;
inline void solve_dfs(int now,int zuxian,int fa)
{
    for(register int i=head[now];i;i=bian[i].from)
    {
        register int next=bian[i].to;
        if(vis[next]||fa==next)
            continue;
        rode[next]=rode[now]+bian[i].cost;
        dian[++tot_anss]=(node){zuxian,rode[next]};
        solve_dfs(next,zuxian,now);
    }
}
inline void find_k(int now)
{
    rode[now]=0,tot_anss=0;
    vis[now]=1;
    for(register int i=head[now];i;i=bian[i].from)
    {
        register int next=bian[i].to;
        if(vis[next]==1)
            continue;
        rode[next]=bian[i].cost;
        dian[++tot_anss]=(node){next,rode[next]};
        solve_dfs(bian[i].to,bian[i].to,now);
    }
    for (register int i=1;i<=tot_anss;i++) 
        if(dian[i].cost%3==0) 
            ans++;
    for(register int i=1;i<=tot_anss;i++)
        for(register int j=i+1;j<=tot_anss;j++)
            if(dian[i].ancestors!=dian[j].ancestors)
                if((dian[i].cost+dian[j].cost)%3==0)
                    ans++;
    for(register int i=head[now];i;i=bian[i].from)
    {
        register int next=bian[i].to;
        if(!vis[next])
        {
            max_son[0]=inf;
            root=0;
            sum=son[next];
            find_root(next,0);
            find_k(root);
        }
    }
}
main()
{
    read(n);
    for(register int i=1;i<n;i++)
    {
        register int l,r,k;
        read(l),read(r),read(k);
        add(l,r,k);add(r,l,k);
    }
    sum=max_son[0]=n,root=0;//root必须更新。
    find_root(1,0);
    find_k(root);
    int zz=n*n;
    ans*=2;
    ans+=n;
    int gcd=__gcd(zz,ans);
    printf("%d/%d\n",ans/gcd,zz/gcd);
    return 0;
}

好久没写博客了,如有错误,请留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值