题目描述
一句话题意: 求一棵N-2条边权确定的树中那条不确定的边的边权分别为[L,R]中的每一个数时树中路径权值gcd为1的点对有多少。
(路径的gcd为所有边权的gcd)
输入格式
第一行N,L,R。
第二行是不确定的边的两个端点。
接下来N-2行每行三个数表示一条边的两个端点和边权。
输出格式
L-R+1行,第i行为当不确定边权等于L+i-1时的答案。
样例
sample.in:
5 1 5
1 2
2 3 6
2 4 4
1 5 3
sample.out:
6
3
2
3
5
数据范围与提示
对于 30% 的数据, 满足 N ≤ 1000, R − L ≤ 1000;
对于另外的 30% 的数据, 满足 N ≤ 10^5, L = R;
对于 100% 的数据, 满足 N ≤ 10^5, Di ≤ 10^5, 1 ≤ L ≤ R ≤ 10^5.
注意:版权归杨乐所属!!
清北寒假省选班的最后一次考试的压轴题,当时我远程提交(厚颜无耻)了个暴力竟然得了80分!!!震惊!
我的暴力就是维护一个点到子树中的每种gcd路径的数量,然后合并,计算答案。
所以先贴一个我的暴力(沉迷重载运算符无法自拔)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<cmath> #include<vector> #define ll long long #define maxn 150005 #define pb push_back using namespace std; ll ans=0,base=0; int to[maxn*2],ne[maxn*2],val[maxn*2]; int hd[maxn],u,v,n,m,L,R; int vis[maxn],dfn,pos[maxn]; int gcd(int a,int b){ return b?gcd(b,a%b):a; } struct p{ int num,sum; }; struct node{ vector<p> g; node operator *(const int &u)const{ node r; p x; r.g.clear(); dfn++; for(int i=g.size()-1;i>=0;i--){ x=g[i]; int d=gcd(u,x.num); if(vis[d]!=dfn){ vis[d]=dfn,pos[d]=r.g.size(); r.g.pb((p){d,x.sum}); } else{ r.g[pos[d]].sum+=x.sum; } } return r; } node operator +(const node &u){ node r; p x; r.g.clear(); dfn++; for(int i=g.size()-1;i>=0;i--){ x=g[i]; if(vis[x.num]!=dfn){ vis[x.num]=dfn,pos[x.num]=r.g.size(); r.g.pb(x); } else r.g[pos[x.num]].sum+=x.sum; } for(int i=u.g.size()-1;i>=0;i--){ x=u.g[i]; if(vis[x.num]!=dfn){ vis[x.num]=dfn,pos[x.num]=r.g.size(); r.g.pb(x); } else r.g[pos[x.num]].sum+=x.sum; } return r; } }tree[maxn]; inline ll calc(node x,node y){ ll an=0; p o,k; for(int i=x.g.size()-1;i>=0;i--){ o=x.g[i]; for(int j=y.g.size()-1;j>=0;j--){ k=y.g[j]; if(gcd(k.num,o.num)==1) an+=k.sum*(ll)o.sum; } } return an; } inline ll calc_line(node x,node y,int D){ ll an=0; p o,k; for(int i=x.g.size()-1;i>=0;i--){ o=x.g[i]; for(int j=y.g.size()-1;j>=0;j--){ k=y.g[j]; if(gcd(gcd(k.num,o.num),D)==1) an+=k.sum*(ll)o.sum; } } return an; } void dfs(int x,int fa){ tree[x].g.pb((p){0,1}); node w; for(int i=hd[x];i;i=ne[i]) if(to[i]!=fa){ dfs(to[i],x); w=tree[to[i]]*val[i]; ans+=calc(tree[x],w); tree[x]=tree[x]+w; } } int main(){ freopen("touch.in","r",stdin); freopen("touch.out","w",stdout); scanf("%d%d%d",&n,&L,&R); scanf("%d%d",&u,&v); int uu,vv,ww; for(int i=n-2;i;i--){ scanf("%d%d%d",&uu,&vv,&ww); to[i]=vv,ne[i]=hd[uu],hd[uu]=i,val[i]=ww; i+=n; to[i]=uu,ne[i]=hd[vv],hd[vv]=i,val[i]=ww; i-=n; } dfs(u,u),dfs(v,v); for(int i=L;i<=R;i++) printf("%lld ",ans+calc_line(tree[u],tree[v],i)); return 0; }
然后来正经的讲一下正解咳咳。
(要不是当时T2是我第一次写高精调了大半年最后没时间了我觉得我是能想到的hhh)
设f[x]为路径gcd为x的点对对数,g[x]为路径gcd为x的倍数的点对对数。
显然g[x]=Σf[x*i]
那么反演一下,f[x]=Σg[x*i]*μ[i]
然后我们求的是f[1],所以答案就是Σg[i]*μ[i] (先假装没有那条边权不定的边)。
求g[x]很简单,并查集一下就好了。
那么怎么考虑那条边权不确定的边呢???
设那条不确定的边的边权是w。
它只会对d|w的g[d]产生影响,而且影响也是很好计算的,就在计算g[d]之后的并查集上再连一下不定边的两个端点即可。
至于计算所有g[d]和考虑所有d对d|w的w的影响的话,可以用调和级数开开心心O(N log N)过的。。。(我是不会告诉你标程是N sqrt(N)然后我艹爆了标程hhh,
其实是老师懒懒得写调和级数)
这样的话其实时限1s就够了(当然如果时限1s我当时就没法水那么多暴力分了hhh)
#include<iostream> #include<cmath> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<vector> #define ll long long #define maxn 100005 #define pb push_back using namespace std; vector<int> g[maxn]; int zs[maxn],t=0,miu[maxn]; bool v[maxn]; inline void init(){ miu[1]=1; for(int i=2;i<=100000;i++){ if(!v[i]) zs[++t]=i,miu[i]=-1; for(int j=1,u;j<=t&&(u=zs[j]*i)<=100000;j++){ v[u]=1; if(!(i%zs[j])) break; miu[u]=-miu[i]; } } } int num,op[maxn],mx; int vis[maxn],dfn=0; struct lines{ int u,v; }l[maxn]; int n,m,S,T,L,R; int p[maxn],siz[maxn]; ll base=0,ans[maxn]; int ff(int x){ return p[x]==x?x:(p[x]=ff(p[x])); } inline void clear(){ for(int i=1;i<=num;i++){ p[op[i]]=op[i]; siz[op[i]]=1; } p[S]=S,p[T]=T; siz[S]=siz[T]=1; num=0; } inline ll solve(int x){ dfn++; lines e; int fa,fb; ll an=0; for(int i=x;i<=mx;i+=x) for(int j=g[i].size()-1;j>=0;j--){ e=l[g[i][j]]; fa=ff(e.u),fb=ff(e.v); if(vis[e.u]!=dfn){ vis[e.u]=dfn,op[++num]=e.u; } if(vis[e.v]!=dfn){ vis[e.v]=dfn,op[++num]=e.v; } p[fa]=fb,an+=siz[fa]*(ll)siz[fb]; siz[fb]+=siz[fa]; } return an; } int main(){ init(); scanf("%d%d%d",&n,&L,&R); scanf("%d%d",&S,&T); int ww; for(int i=n-2;i;i--){ scanf("%d%d%d",&l[i].u,&l[i].v,&ww); g[ww].pb(i),mx=max(mx,ww); } for(int i=1;i<=n;i++) p[i]=i,siz[i]=1; for(int i=mx;i;i--){ clear(); base+=miu[i]*solve(i); int fa=ff(S),fb=ff(T); ll now=siz[fa]*(ll)siz[fb]*(ll)miu[i]; for(int j=i;j<=mx;j+=i) ans[j]+=now; } for(int i=L;i<=R;i++) printf("%lld ",base+ans[i]); return 0; }
但是暴力还是在loj上A了,对比一下(根据数据的随机性的话,我这种暴力的效果也是很好的):