【BZOJ3425】Poi2013 Polarization
Description
给定一棵树,可以对每条边定向成一个有向图,这张有向图的可达点对数为树上有路径从u到达v的点对(u,v)个数。求最小可达点对数和最大可达点对数
n<=250000
Sample Input
4
1 2
1 3
1 4
1 2
1 3
1 4
Sample Output
3 5
题解:想了一晚上,怎么想怎么是个搭建双塔,结果看题解发现还真tm是搭建双塔。
本题的结论有点神,不过很好猜,证明见Claris博客。
第一问的答案一定是n-1,因为树是个二分图,所以我们将树黑白染色后所有边都从黑点指向白点即可。
第二问的答案是什么呢?我们猜测一定是先选中一个点,然后一半的边都指向这个点,另一半的边从这个点指出去。而选择哪个点呢?显然是重心啦。然后我们将重心之外所有联通块的大小都拿出来,这就变成了搭建双塔问题。
话说搭建双塔不是$O(n^2)$的吗?n=250000你逗我?然而正解很奇特。
当联通块大小<sqrt(n)时,我们将所有这样的联通块合到一起跑分组背包;当联通块大小>sqrt(n)时,这样的联通块不超过sqrt(n)个,所以暴力DP即可。DP时用bitset维护,于是时间复杂度就变成了神奇的$O({n sqrt{n}over 32})$。
#include <cstdio> #include <cstring> #include <iostream> #include <bitset> #include <cmath> using namespace std; const int maxn=250010; typedef long long ll; int B,n,m,cnt,rt,mn; ll sum,ans; int to[maxn<<1],next[maxn<<1],head[maxn],siz[maxn],v[maxn],dep[maxn],s[maxn]; bitset<maxn> f; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-') f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+(gc^'0'),gc=getchar(); return ret*f; } inline void add(int a,int b) { to[cnt]=b,next[cnt]=head[a],head[a]=cnt++; } void dfs(int x) { siz[x]=1,sum+=dep[x]-1; int i,tmp=0; for(i=head[x];i!=-1;i=next[i]) if(!dep[to[i]]) dep[to[i]]=dep[x]+1,dfs(to[i]),siz[x]+=siz[to[i]],tmp=max(tmp,siz[to[i]]); tmp=max(tmp,n-siz[x]); if(tmp<mn) mn=tmp,rt=x; } int main() { n=rd(),B=int(sqrt(double(n))),mn=1<<30; memset(head,-1,sizeof(head)); int i,j,a,b; for(i=1;i<n;i++) a=rd(),b=rd(),add(a,b),add(b,a); dep[1]=1,dfs(1),memset(dep,0,sizeof(dep)); sum=0,dep[rt]=1,dfs(rt); for(i=head[rt];i!=-1;i=next[i]) v[++m]=siz[to[i]]; f[0]=1; for(i=1;i<=m;i++) { if(v[i]<=B) s[v[i]]++; else f=f|(f<<v[i]); } for(i=1;i<=B;i++) { for(j=1;j<=s[i];s[i]-=j,j<<=1) f=f|(f<<(i*j)); if(s[i]) f=f|(f<<(i*s[i])); } for(i=0;i<=n;i++) if(f[i]) ans=max(ans,sum+(ll)i*(n-1-i)); printf("%d %lld",n-1,ans); return 0; }