点分治

算法学习

  1. 先在原树上通过siz的递归找根(因为还要算子树外的大小,所以还需要求一下整颗子树大小
  2. 通过遍历子树求出来每个节点的深度,一边统计答案一边计算
  3. 用vis打在根上的标记来分割子树,递归求解

看这里还有这里

但他的代码中有一些错误(第一篇)

一个是在计算前没有先计算子树大小,另一个是没有在修改 \(ex\) 函数时判断是否越界(ex是我的函数)

建议看我的代码的注释(P3806 )

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,K=1e7+5,inf=1e9+5;
struct edge{
    int v,w;
};
vector<edge>b[N];
vector<int>query,del;
int n,root,mn,tot,m;
int dis[N],dp[N],vis[N],siz[N],ans[N];
bool ex[K];
void getsiz(int x,int f){
    siz[x]=1;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getsiz(i.v,x);
        siz[x]+=siz[i.v];
    }
}
void getroot(int x,int f,int sum){
    siz[x]=1;dp[x]=0;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getroot(i.v,x,sum);
        siz[x]+=siz[i.v];
        dp[x]=max(dp[x],siz[i.v]);
    }
    dp[x]=max(dp[x],sum-siz[x]);
    if(dp[x]<mn)  root=x,mn=dp[x];
}
void getdis(int x,int w,int f){
    dis[++tot]=w;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getdis(i.v,w+i.w,x);
    }
}
void figue(int x){
    for(auto i:b[x]){
        if(vis[i.v])  continue;
        tot=0;
        getdis(i.v,i.w,x);//每一个子树单独枚举,并且深度已经是w了,并且从i.v开始调用,记得改fa
        for(int j=1;j<=tot;j++){
            int cnt=0;
            for(int k=0;k<query.size();k++){
                if(query[k]>=dis[j])  ans[k]|=ex[query[k]-dis[j]];//先判断防止越界
            }  
        }
        for(int j=1;j<=tot;j++){
            if(dis[j]<K)  ex[dis[j]]=1,del.push_back(dis[j]);//防止越界
        }
    }
    for(int k:del)  ex[k]=0;
    del.clear();
}
void solve(int x){
    ex[0]=1;//路径正好经过root
    vis[x]=1;
    figue(x);
    for(auto i:b[x]){
        if(vis[i.v])  continue;//加了一个vis的判断
        mn=inf;
        getsiz(i.v,x);//因为之前并不知道子树的大小,所以要提前计算
        getroot(i.v,x,siz[i.v]);//传当前子树大小
        solve(root);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        b[u].push_back({v,w});
        b[v].push_back({u,w});
    }
    for(int i=1;i<=m;i++){
        int k;
        scanf("%d",&k);
        query.push_back(k);
    }
    mn=inf;
    getroot(1,0,n);
    solve(root);
    for(int i=0;i<m;i++){
        if(ans[i])  printf("AYE\n");
        else  printf("NAY\n");
    }
}

T1:

trick:
判断一个序列中两个数的加和有多少小于k,可以枚举每一个数,然后判断有多少数小于它与k的差,树状数组维护即可

或者是二分?但这道题是动态的,我怕二分会炸

注这道题洛谷原题w是可以等于0的,所以要用支持修改0的树状数组

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+5,inf=1e9+5,K=2e4+5;
struct edge{
    int v,w;
};
vector<edge>b[N];
vector<int>del;
int n,root,mn,tot,k,ans;
int dis[N],dp[N],vis[N],siz[N],tr[K];
int lowbit(int x){
    return x&(-x);
}
void add(int x,int z){
    if(x==0){//可能为0
        tr[0]+=z;
        return;
    }
    for(;x<K;x+=lowbit(x)){
        tr[x]+=z;
    }
}
int ask(int x){
    int res=0;
    for(;x;x-=lowbit(x)){
        res+=tr[x];
    }
    return res+tr[0];
}
void getsiz(int x,int f){
    siz[x]=1;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getsiz(i.v,x);
        siz[x]+=siz[i.v];
    }
}
void getroot(int x,int f,int sum){
    siz[x]=1;dp[x]=0;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getroot(i.v,x,sum);
        siz[x]+=siz[i.v];
        dp[x]=max(dp[x],siz[i.v]);
    }
    dp[x]=max(dp[x],sum-siz[x]);
    if(dp[x]<mn)  root=x,mn=dp[x];
}
void getdis(int x,int w,int f){
    dis[++tot]=w;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getdis(i.v,w+i.w,x);
    }
}
void figue(int x){
    // printf("root=%d\n",x);
    for(auto i:b[x]){
        if(vis[i.v])  continue;
        tot=0;
        getdis(i.v,i.w,x);//每一个子树单独枚举,并且深度已经是w了,并且从i.v开始调用,记得改fa
        for(int j=1;j<=tot;j++){
            if(k>=dis[j])  ans+=ask(k-dis[j]);
        }
        for(int j=1;j<=tot;j++){
            // printf("%d %d\n",dis[j],k);
            if(dis[j]<=k){
                // printf("%d ",dis[j]);
                ans++;//计算以重心为终点的贡献
                add(dis[j],1);
                del.push_back(dis[j]);
            }  
        }
        // printf("\n");
    }
    for(int j:del)  add(j,-1);
    del.clear();
}
void solve(int x){
    vis[x]=1;
    figue(x);
    for(auto i:b[x]){
        if(vis[i.v])  continue;//加了一个vis的判断
        mn=inf;
        getsiz(i.v,x);//因为之前并不知道子树的大小,所以要提前计算
        getroot(i.v,x,siz[i.v]);//传当前子树大小
        solve(root);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        b[u].push_back({v,w});
        b[v].push_back({u,w});
    }
    scanf("%d",&k);
    mn=inf;
    getroot(1,0,n);
    solve(root);
    printf("%d",ans);
}

T2:

水题,直接秒了

考虑怎样求一个集合内两个数加和为3的倍数的个数即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5,inf=1e9+5;
struct edge{
    int v,w;
};
vector<edge>b[N];
vector<int>del;
int n,root,mn,tot,k,ans1,ans2,m0,m1,m2,cnt;
int dis[N],dp[N],vis[N],siz[N];
void getsiz(int x,int f){
    siz[x]=1;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getsiz(i.v,x);
        siz[x]+=siz[i.v];
    }
}
void getroot(int x,int f,int sum){
    siz[x]=1;dp[x]=0;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getroot(i.v,x,sum);
        siz[x]+=siz[i.v];
        dp[x]=max(dp[x],siz[i.v]);
    }
    dp[x]=max(dp[x],sum-siz[x]);
    if(dp[x]<mn)  root=x,mn=dp[x];
}
void getdis(int x,int w,int f){
    dis[++tot]=w;
    for(auto i:b[x]){
        if(vis[i.v]||i.v==f)  continue;
        getdis(i.v,w+i.w,x);
    }
}
void change(int x,int z){
    if(x%3==0)  m0+=z;
    if(x%3==1)  m1+=z;
    if(x%3==2)  m2+=z;
}
void update(int x){
    if(x%3==0)  ans2+=m0+1;//单独成路径
    if(x%3==1)  ans2+=m2;
    if(x%3==2)  ans2+=m1;
}
void figue(int x){
    cnt=0;
    for(auto i:b[x]){
        if(vis[i.v])  continue;
        tot=0;
        getdis(i.v,i.w,x);//每一个子树单独枚举,并且深度已经是w了,并且从i.v开始调用,记得改fa
        for(int i=1;i<=tot;i++){
            ans1+=cnt+1;//单独和与之前的都可以组合
            update(dis[i]);
        }
        for(int i=1;i<=tot;i++){
            cnt++;
            change(dis[i],1);
        }
    }
    m0=m1=m2=0;
}
void solve(int x){
    vis[x]=1;
    figue(x);
    for(auto i:b[x]){
        if(vis[i.v])  continue;//加了一个vis的判断
        mn=inf;
        getsiz(i.v,x);//因为之前并不知道子树的大小,所以要提前计算
        getroot(i.v,x,siz[i.v]);//传当前子树大小
        solve(root);
    }
}
int gcd(int x,int y){
    if(!y)  return x;
    return gcd(y,x%y);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        b[u].push_back({v,w});
        b[v].push_back({u,w});
    }
    mn=inf;
    getroot(1,0,n);
    solve(root);
    ans1=ans1*2+n;
    ans2=ans2*2+n;
    int g=gcd(ans1,ans2);
    printf("%d/%d",ans2/g,ans1/g);
}

posted @ 2025-07-27 08:57  daydreamer_zcxnb  阅读(16)  评论(0)    收藏  举报