返回顶部

点分治

当我们遇到一些规模较大的树上距离的问题,暴力显然不适用了,所以我们引入了一个新的算法。

点分治,顾名思义,就是在处理问题时利用点进行分治,从而达到降低时间复杂度的目的。

但是,我们又需要利用那些点来进行分治呢?

我们为了问题简单化,先假设这是棵有根树。我们为了解决这类问题,选取两个点。这两个点关于第三个点有两种关系:路径经过该点或不经过该点。

对于经过该点的是我们在分治当前点时需要考虑的,而不经过的情况,就可以交给其他的点。

可以看出,我们的大概思路时:利用一个点,对其子树进行分治。

出题人:我构造数据,使时间复杂度退化,阁下又该如何应对?

这里我们引入一个新名词:重心 三条中线的交点(bushi)

重心就是在当前子树里,最大子树最小的一个点。

code
void getroot(int x,int father){
    w[x]=0;							//最大子树的节点数
    size[x]=1;						//子树大小
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            getroot(y.y,x);
            size[x]+=size[y.y];
            w[x]=max(w[x],size[y.y]);
        }
    }
    w[x]=max(w[x],sum-size[x]);		//如果你是根,那么父亲也是儿子
    if(!root||w[x]<=w[root]){
        root=x;
    }
}

我们找到了重心,接着利用重心不断分治,解决问题即可。


P3806【模板】点分治1

暴力就是 \(O(n^2)\) 的枚举点对,然后暴跳求距离,显然,会 \(T\) 的飞起,考虑点分治优化。

点分治板子不再说,直接讲求解函数。

我们枚举队列里的点,逐个比较权值和,但是依然跑得飞慢,考虑迷之优化

我们将所有询问离线下来,如果所有询问都出现过,直接疯狂 \(return\),可以通过本题。

有个注意点,你在分治一个点时,其中两个点可能在同一棵子树里,这样就会多算贡献,我们可以在寻找子树重心时容斥掉这些点对,当然也可以预先存下它属于那棵子树。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
const int M=1e4+10;
struct abc{
    int y,va;
};
int n,m,tot;
int sum,cnt,root;
int w[M],size[M];
int d[M],belong[M];
int dis[M];
int ans[M];
bool z[M],zz[N];
vector<abc>h[M];
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void getroot(int x,int father){
    w[x]=0; size[x]=1;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            getroot(y.y,x);
            size[x]+=size[y.y];
            w[x]=max(w[x],size[y.y]);
        }
    }
    w[x]=max(w[x],sum-size[x]);
    if(!root||w[x]<=w[root]){
        root=x;
    }
    if(tot==m){
        return;
    }
}
void dfs(int x,int father,int ch){
    belong[x]=ch;
    d[++cnt]=x;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            dis[y.y]=dis[x]+y.va;
            dfs(y.y,x,ch);
        }
    }
    if(tot==m){
        return;
    }
}
inline void calc(int x){
    d[++cnt]=x;
    dis[x]=belong[x]=0;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            dis[y.y]=y.va;
            dfs(y.y,x,i+1);
        }
    }
    for(int i=1;i<=cnt;i++){
        for(int j=i+1;j<=cnt;j++){
            int l=d[i],r=d[j];
            if(belong[l]!=belong[r]){
                if(dis[l]+dis[r]<=N){
                    if(zz[dis[l]+dis[r]]==1){
                        zz[dis[l]+dis[r]]=0;
                        tot++;
                    }
                }
            }
        }
    }
    cnt=0;
    if(tot==m){
        return;
    }
}
void solve(int x){
    z[x]=1;
    calc(x);
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            sum=size[y.y];
            root=0;
            getroot(y.y,0);
            solve(root);
        }
    }
    if(tot==m){
        return;
    }
}
int main(){
    n=read(); m=read();
    sum=n;
    for(int i=1;i<n;i++){
        int x=read(),y=read(),w=read();
        h[x].push_back((abc){y,w});
        h[y].push_back((abc){x,w});
    }
    for(int i=1;i<=m;i++){
        ans[i]=read();
        zz[ans[i]]=1;
    }
    getroot(1,0);
    solve(root);
    for(int i=1;i<=m;i++){
        if(zz[ans[i]]==0){
            cout<<"AYE"<<'\n';
        }
        else {
            cout<<"NAY"<<'\n';
        }
    }
	return 0;
}

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

\(dfs\) 的时候存一下重心其他子树权值为 \(\{0,1,2\}\) 的路径数,合并一下即可。

code
#include<bits/stdc++.h>
using namespace std;
const int M=2e4+10;
struct abc{
    int y,va;
};
int n,zi,mu;
int root,sum;
int w[M],size[M];
int f[3][2],dis[M],belong[M];
bool z[M];
vector<abc>h[M];
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void getroot(int x,int father){
    w[x]=0; size[x]=1;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            getroot(y.y,x);
            size[x]+=size[y.y];
            w[x]=max(w[x],size[y.y]);
        }
    }
    w[x]=max(w[x],sum-size[x]);
    if(w[x]<=w[root]||!root){
        root=x;
    }
}
void dfs(int x,int father){
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            dis[y.y]=(dis[x]+y.va)%3;
            f[dis[y.y]][1]++;
            dfs(y.y,x);
        }
    }
}
void calc(int x){
    f[0][0]=1;
    int tot=0;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            dis[y.y]=y.va;
            f[dis[y.y]][1]++;
            dfs(y.y,x);
            tot+=f[0][0]*f[0][1]+f[2][0]*f[1][1]+f[1][0]*f[2][1];
            f[0][0]+=f[0][1]; f[1][0]+=f[1][1]; f[2][0]+=f[2][1];
            f[0][1]=f[1][1]=f[2][1]=0;
        }
    }
    zi+=tot*2;
    memset(f,0,sizeof(f));
}
void solve(int x){
    zi++;
    z[x]=1;
    calc(x);
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            sum=size[y.y];
            root=0;
            getroot(y.y,0);
            solve(root);
        }
    }
}
int getgcd(int a,int b){
    return b==0?a:getgcd(b,a%b);
}
int main(){
    n=read();
    sum=n; mu=n*n;
    for(int i=1;i<n;i++){
        int x=read(),y=read(),w=read();
        h[x].push_back((abc){y,w%3});
        h[y].push_back((abc){x,w%3});
    }
    getroot(1,0);
    solve(root);
    int gcd=getgcd(zi,mu);
    cout<<zi/gcd<<"/"<<mu/gcd<<endl;
    return 0;
}


P4149 [IOI2011] Race

和题解不一样,但是卡过去了

\(P3806\) 类似,只不过再加一些剪枝和一个双指针

对于单点权值大于 \(k\) 的,一定不会作为答案中的一个点,直接 \(return\)

把队列按权值排个序,然后枚举两端的权值,大于的话 \(r\)--,小于的话 \(l\)++,等于直接更新答案。

还是建议去看下题解

code
#include<bits/stdc++.h>
using namespace std;
const int inf=2147483647;
const int M=2e5+10;
struct abc{
    int y,va;
};
struct abd{
    int id,w;
}d[M];
struct abe{
    long long x,y,to,va;
}e[M<<1];
int n,k,tot;
int sum,cnt,root,ans=inf;
int w[M],size[M],deep[M];
int belong[M];
int dis[M];
int head[M];
bool z[M];
vector<abc>h[M];
void add(int x,int y,int va){
    e[++tot].x=x;
    e[tot].y=y;
    e[tot].va=va;
    e[tot].to=head[x];
    head[x]=tot;
}
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
bool cmp(abd a,abd b){
    return a.w<b.w;
}
void getroot(int x,int father){
    w[x]=0; size[x]=1;
    for(long long i=head[x];i;i=e[i].to){
        int y=e[i].y;
        if(y!=father&&z[y]==0){
            getroot(y,x);
            size[x]+=size[y];
            w[x]=max(w[x],size[y]);
        }
    }
    w[x]=max(w[x],sum-size[x]);
    if(w[x]<w[root]||!root){
        root=x;
    }
}
void dfs(int x,int father,int ch){
    deep[x]=deep[father]+1;
    if(deep[x]>ans||dis[x]>k){
        return;
    }
    belong[x]=ch;
    d[++cnt]=(abd){x,dis[x]};
    for(long long i=head[x];i;i=e[i].to){
        int y=e[i].y;
        if(y!=father&&z[y]==0){
            dis[y]=dis[x]+e[i].va;
            dfs(y,x,ch);
        }
    }
}
void calc(int x){
    d[++cnt]=(abd){x,0};
    dis[x]=deep[x]=belong[x]=0;
    for(long long i=head[x];i;i=e[i].to){
        int y=e[i].y;
        if(z[y]==0){
            dis[y]=e[i].va;
            dfs(y,x,i+1);
        }
    }
    sort(d+1,d+cnt+1,cmp);
    int ll=1,rr=cnt;
    while(ll<=rr){
        abd l=d[ll],r=d[rr];
        if(dis[l.id]+dis[r.id]>k){
            rr--;
        }
        if(dis[l.id]+dis[r.id]==k){
            int lll=ll;
            for(int i=ll+1;i<=cnt;i++){
                if(dis[d[i].id]==dis[l.id]){
                    lll++;
                }
                else {
                    break;
                }
            }
            int rrr=rr;
            for(int i=rr-1;i>=0;i--){
                if(dis[d[i].id]==dis[r.id]){
                    rrr--;
                }
                else {
                    break;
                }
            }
            for(int i=ll;i<=lll;i++){
                for(int j=rrr;j<=rr;j++){
                    if(belong[d[i].id]!=belong[d[j].id]){
                        ans=min(ans,deep[d[i].id]+deep[d[j].id]);
                    }
                }
            }
            ll=lll+1; rr=rrr-1;
        }
        if(dis[l.id]+dis[r.id]<k){
            ll++;
        }
    }
    cnt=0;
}
void solve(int x){
    z[x]=1;
    calc(x);
    for(long long i=head[x];i;i=e[i].to){
        int y=e[i].y;
        if(z[y]==0){
            root=0; sum=size[y];
            getroot(y,0);
            solve(root);
        }
    }
}
int main(){
    n=read(); k=read();
    sum=n;
    for(int i=1;i<n;i++){
        int x=read()+1,y=read()+1,w=read();
        add(x,y,w); add(y,x,w);
    }
    getroot(1,0);
    solve(root);
    if(ans==inf){
        cout<<-1<<'\n';
    }
    else {
        cout<<ans<<'\n';
    }
    return 0;
}

P4178 Tree

双指针+容斥

我们对于当前队列进行排序,找到对于每一个 \(l\) 使其与队列所有现存点的权值和都小于 \(k\)\(r\),直接将答案加和,容掉在同一棵子树里的即可。

code
#include<bits/stdc++.h>
using namespace std;
const int M=4e4+10;
struct abc{
    int y,va;
};
int n,m;
int sum,cnt,root;
int ans;
int w[M],size[M];
int d[M],dis[M];
bool z[M];
vector<abc>h[M];
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void getroot(int x,int father){
    w[x]=0; size[x]=1;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            getroot(y.y,x);
            size[x]+=size[y.y];
            w[x]=max(w[x],size[y.y]);
        }
    }
    w[x]=max(w[x],sum-size[x]);
    if(!root||w[x]<w[root]){
        root=x;
    }
}
void dfs(int x,int father){
    if(dis[x]>m){
        return;
    }
    d[++cnt]=x;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(y.y!=father&&z[y.y]==0){
            dis[y.y]=dis[x]+y.va;
            dfs(y.y,x);
        }
    }
}
bool cmp(int a,int b){
    return dis[a]<dis[b];
}
inline void calc(int x,int c,int w){
    dis[x]=w;
    d[++cnt]=x;
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            dis[y.y]=w+y.va;
            dfs(y.y,x);
        }
    }
    sort(d+1,d+cnt+1,cmp);
    int l=1,r=cnt;
    while(l<=r){
        if(dis[d[l]]+dis[d[r]]<=m){
            ans+=(r-l)*c;
            l++;
        }
        else {
            r--;
        }
    }
    cnt=0;
}
void solve(int x){
    z[x]=1;
    calc(x,1,0);
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            calc(y.y,-1,y.va);
        }
    }
    for(int i=0;i<h[x].size();i++){
        abc y=h[x][i];
        if(z[y.y]==0){
            root=0; sum=size[y.y];
            getroot(y.y,0);
            solve(root);
        }
    }
}
int main(){
    n=read();
    sum=n;
    for(int i=1;i<n;i++){
        int x=read(),y=read(),w=read();
        h[x].push_back((abc){y,w});
        h[y].push_back((abc){x,w});
    }
    m=read();
    getroot(1,0);
    solve(root);
    cout<<ans<<'\n';
	return 0;
}

采药人的路径

麻烦的采药人

可以联想一下聪聪可可

首先可以将阳性的药材权值设为 1,阴性的药材权值设为 -1,这样当权值和为 0 时就满足阴阳平衡的限制。

\(dfs\) 的时候,存一下前几颗子树的路径状态,直接合并。

code
#include<bits/stdc++.h>
using namespace std;
const long long M=1e5+10;
long long n,head[M],to[M*2],nextt[M*2],w[M*2],tot=1;
long long size[M],F[M],sum,root,f[M*2][2],g[M*2][2],ans;
long long maxn,dis[M],np[M*2],z[M];
inline long long read(){
    long long x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(long long x,long long y,long long z){
    w[tot]=z;
    to[tot]=y;
    nextt[tot]=head[x];
    head[x]=tot++;
}
void getroot(long long u,long long fa){
    F[u]=0;
    size[u]=1;
    for(long long i=head[u];i;i=nextt[i]){
        long long v=to[i];
        if(v==fa || z[v])
            continue;
        getroot(v,u);
        size[u] +=size[v];
        F[u]=max(F[u],size[v]);
    }
    F[u]=max(F[u],sum - size[u]);
    if(F[u] < F[root]||root==0)
        root=u;
}
void dfs(long long u,long long fa){
    maxn=max(maxn,abs(dis[u]));
    if(np[dis[u]+M]){
        g[dis[u]+M][1]++;
    }
    else {
        g[dis[u]+M][0]++;
    }
    np[dis[u]+M]++;
    for(long long i=head[u];i;i=nextt[i]){
        long long v=to[i];
        if(z[v]==0&&v!=fa){
            dis[v]=dis[u]+w[i];
            dfs(v,u);
        }
    }
    np[dis[u]+M]--;
}
void solve(long long u){
    z[u]=1;
    long long top=0;
    for(long long i=head[u];i;i=nextt[i]){
        long long v=to[i];
        if(z[v])
            continue;
        dis[v]=w[i];
        maxn=0;
        dfs(v,u);
        top=max(top,maxn);
        for(long long j=-maxn;j<=maxn;j++){
            ans+=f[M+j][0]*g[M-j][1];
            ans+=f[M+j][1]*g[M-j][0];
            ans+=f[M+j][1]*g[M-j][1];
        }
        ans+=f[M][0]*g[M][0];
        ans+=g[M][1];
        for(long long j=-maxn;j<=maxn;j++){
            f[M+j][0]+=g[M+j][0];
            f[M+j][1]+=g[M+j][1];
            g[M+j][0]=g[M+j][1]=0;
        }
    }
    for(long long i=-top;i<=top;i++){
        f[M+i][0]=f[M+i][1]=0;
    }
    for(long long i=head[u];i;i=nextt[i]){
        long long v=to[i];
        if(z[v]==0){
            sum=size[v];root=0;
            getroot(v,0);
            solve(root);
        }
    }
}
int main(){
    n=read();sum=n;
    for(long long i=1;i<n;i++){
		long long x=read(),y=read(),w=read();
		if(w==0){
            w=-1;
		}
        add(x,y,w);add(y,x,w);
	}
    getroot(1,0);
    solve(root);
    cout<<ans<<'\n';
    return 0;
}

posted @ 2023-10-31 17:29  Airposs  阅读(35)  评论(0)    收藏  举报