【BZOJ3425】Poi2013 Polarization 猜結論+DP
阿新 • • 發佈:2017-11-05
lan sizeof sof 超過 ext 成了 double highlight 定向
1 2
1 3
1 4
【BZOJ3425】Poi2013 Polarization
Description
給定一棵樹,可以對每條邊定向成一個有向圖,這張有向圖的可達點對數為樹上有路徑從u到達v的點對(u,v)個數。求最小可達點對數和最大可達點對數
n<=250000
Sample Input
41 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; }
【BZOJ3425】Poi2013 Polarization 猜結論+DP