【BZOJ5211】[ZJOI2018]線圖(樹哈希,動態規划)
題面
題解
吉老師的題目是真的神仙啊。
去年去現場這題似乎騙了\(20\)分就滾粗了?
首先\(k=2\)直接算\(k=1\)時的邊數就好了。\(k=3\)同理。
這里直接計算每個點的度數就可以做,然后就有\(20\)分了。
我們發現如果企圖繼續考慮線圖應該怎么計算出來,這里是很難做的。
注意到原圖是一棵樹,所以想想線圖和原圖之間的關系。
對於做一次線圖\(L(G)\)而言,點數顯然等於原圖的邊數。
對於做兩次線圖\(L^2(G)\)而言,點數就是在\(L(G)\)中的邊數。\(L(G)\)中的點在原圖中表示邊,如果兩個點在\(L(G)\)中有邊,證明在原圖中他們兩是有交點的邊,即一條長度為\(2\)的路徑。
對於做三次線圖\(L^3(G)\)而言,即\(L^2(G)\)的邊數。而\(L^2(G)\)的邊表示他們在\(L(G)\)上是一條長度為\(2\)的邊,那么放在原圖上,發現可以是一個長度為\(3\)的鏈,亦或者是三條共點的邊。
那么如果繼續口胡呢?\(L^4(G)\)是啥呢?那么就是\(L^2(L^2(G))\)。考慮在\(L^2(G)\)上找長度為\(2\)的路徑。顯然長度為\(4\)的鏈是可以的,然后四條共點的邊也是可以的。其實回到\(L^2(2)\)的邊的含義,那么不難發現\(L^4(G)\)也就是原圖中兩條長度為\(2\)的路徑拼起來,如果要拼起來顯然要有一個交點,所以實際上就是一棵擁有\(4\)條連在一起的東西,放在一棵樹的原圖中,就是一個點數為\(5\)的樹。
那么這么分析一下,似乎發現前面的\(L(G)\)對應着邊數為\(1\)、點數為\(2\)的樹。\(L^2(G)\)對應着邊數為\(2\),點數為\(3\)的樹。\(L^3(G)\)對應着邊數為\(3\)、點數為\(4\)的樹......
真的?就這么簡單?
然而仔細想想這樣子似乎有點鍋。因為我們發現在線圖的構建過程中是會有環的出現,最簡單的例子就是如果\(G\)並不是題目給出的樹的話,那么顯然原圖中的三元環也要計入答案。
所以來改正一下我們的措辭,\(L^k(G)\)的每一個點對應着原圖\(G\)中的一個邊數不超過\(k\)的聯通的導出子圖的個數。注意這里的用詞,是一個點對應着一個導出子圖,而不是每一個導出子圖對應着一個點,同時也意味着一個導出子圖可能被多個點所對應。
而原圖就是一棵樹,所以實際上的聯通導出子圖只可能是一棵樹的形態。
對於每個邊數不超過\(k\)的子樹的貢獻太慢了,顯然是把每種子樹都統計一下個數,然后再對於每種子樹計算貢獻然后統計答案。
貢獻不好算?直接暴力算\(k\)次不就完事了?然而這樣子就會發現復雜度單次計算的復雜度很爆炸。
我們發現\(k\)很小的時候可以直接推式子來快速計算,所以我們只需要模擬出一個較小的\(k\)的圖然后直接計算。
那么我們來推推式子?假設\(n\)是點數,\(m\)是邊數。
對於\(L(G)\),顯然\(ans=m\)。
對於\(L^2(G)\),\(ans=\sum_{i=1}^n {deg(i)\choose 2}\)
對於\(L^3(G)\),考慮長度為三的鏈以及三元環的貢獻\(\sum _{(u,v)\in E}(deg(u)-1)*(deg(v)-1)\),即枚舉中間那條邊,考慮以這條邊的兩個端點再延伸出去。然后再考慮一個點掛三條邊的貢獻,就是\(3\sum_{i=1}^n {deg(i)\choose 3}\),乘三的原因是這樣的三條邊在求線圖后成環,貢獻了三個點。兩個式子求和就是\(L^3(G)\)的貢獻了。
然后\(L^4(G)\)怎么算?直接算肯定不方便,所以變形一下成了\(L^3(L(G))\)。而\(L(G)\)每個點的度數是很好算的,為\(deg((u,v))=deg(u)+deg(v)-2,(u,v)\in E\)。
而計算\(L^3(G)\)時對於每條邊計算貢獻時,我們不可能把每條邊全部整出來算。這樣子考慮,\(L(G)\)中的一個點表示的是\(G\)中的一條邊,而\(L(G)\)中的一條邊,表示的是原圖中的一個長度為\(2\)的鏈,現在要順次考慮每一條邊的貢獻,等價於在原圖中考慮每一個長度為\(2\)的鏈的貢獻。而一個長度為\(2\)的鏈由三個點構成,所以我們枚舉其中中間的那個點,那么這兩條邊都從這個點射出。
所以令\(S(u)\)表示以\(u\)為端點的邊\((u,v)\in E\)的所有邊的\(deg((u,v))-1\)的和,那么貢獻就是\(\frac{1}{2}S(u)^2\),然而這個東西算重了,每條邊在每個端點時都被自己和自己匹配了\(\frac{1}{2}\)次,也就是一共被多算了\(1\)次,所以還需要減去\(\sum_{(u,v)\in E}(deg((u,v))-1)^2\)。
所以\(L^4(G)\)的答案就是\(\frac{1}{2}\sum_{i=1}^n S(i)^2-\sum_{i=1}^m (deg(i)-1)^2+3\sum_{i=1}^m{deg(i)\choose 3}\)。
再往后似乎也能繼續推,但是也沒有什么必要了。
好的,那么現在我們要做的就是兩個事情,統計每種邊數不超過\(k\)的樹的貢獻,以及它在原樹中的出現次數。
我們先考慮怎么求解邊數不超過\(k\)的樹的貢獻,可以類似括號序列的方法爆搜樹,左括號表示當前點加入一個兒子,右括號表示回到父親,但是這樣會計算出同構的樹,所以再樹哈希一下去重。
那么我們計算一棵樹\(T\)的\(L^k(T)\)的點數,就是前文所說的,只需要模擬出\(L^{k-4}(T)\),然后直接計算即可。
接着是考慮如何計算貢獻,顯然就是對於當前聯通塊而言減去其所有聯通導出子圖的貢獻,這個容斥處理即可。
然后就是對於每棵搜出來的樹,如何計算它在原樹上的出現次數。
我們來做一個\(dp\),設\(f[i][r]\)表示以原樹上的\(i\)節點為根節點,當前點可以匹配上目標樹上的\(r\)的子樹的方案數。
轉移的時候枚舉當前點匹配目標樹上的哪個節點,那么它的兒子就要和目標樹上枚舉的這個節點對應,這個直接\(dp\)就好了。注意一個小問題,因為子樹是可以同構的,所以這里要考慮可重排列的貢獻。
當然,如果直接暴力\(dp\)復雜度是比較難接受的,這里做一個小小的優化,把目標樹上的葉子節點全部刪掉,在匹配完之后再考慮當前點掛的葉子節點,這樣子可以直接用一個組合數計算方案數,同時可以把這里要求解的點數優化很多。
本機\(2.2s\),洛谷和\(LOJ\)都能過,\(UOJ\)我卡不動了,\(BZOJ\)今天掛了,所以就這樣了。
upd:BZOJ上莫名CE。。。。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
#define MOD 998244353
#define inv2 499122177
#define ull unsigned int
#define MAX 5050
void halt(){exit(0);}
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int fpow(int a,int b)
{
int s=1;
while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
return s;
}
int n,k,ans;
int S[MAX*2000],dg[MAX*2000];
int C[MAX][15],JC[15];
const ull base1=998,base2=244,base3=353;
struct Graph
{
struct Line{int v,next;}e[MAX*2000];
int h[MAX*2000],cnt,n;
void pre(int N){n=N;cnt=2;for(int i=1;i<=n;++i)h[i]=0;}
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int Calc4()
{
int ans=0;for(int i=1;i<=n;++i)dg[i]=S[i]=0;
for(int u=1;u<=n;++u)
for(int i=h[u];i;i=e[i].next)dg[e[i].v]+=1;
for(int u=1;u<=n;++u)
for(int i=h[u];i;i=e[i].next)
S[u]+=dg[u]+dg[e[i].v]-3;
for(int i=1;i<=n;++i)ans=(ans+1ll*S[i]*S[i])%MOD;
ans=1ll*ans*inv2%MOD;
for(int i=2;i<cnt;i+=2)
{
int d=dg[e[i].v]+dg[e[i^1].v]-2;
ans=(ans+MOD-(d-1)*(d-1))%MOD;
ans=(ans+1ll*d*(d-1)*(d-2)/2)%MOD;
}
return ans;
}
}T,E[2];
int lg[MAX],bul[MAX];
int lb(int x){return x&(-x);}
struct Tree
{
ull f[15],Q[15];int n,E[15],size[15],jc[15];
void Add(int u,int v){E[u]|=1<<v;E[v]|=1<<u;}
void Resize(int u,int ff)
{
size[u]=1;
for(int i=E[u];i;i-=lb(i))
if(lg[lb(i)]!=ff)
Resize(lg[lb(i)],u),size[u]+=size[lg[lb(i)]];
}
void dfs(int u,int ff)
{
size[u]=1;
for(int i=E[u];i;i-=lb(i))
if(lg[lb(i)]!=ff)
dfs(lg[lb(i)],u),size[u]+=size[lg[lb(i)]];
int son=0;for(int i=E[u];i;i-=lb(i))Q[++son]=f[lg[lb(i)]];
sort(&Q[1],&Q[son+1]);f[u]=size[u];jc[u]=1;Q[son+1]=Q[son]+1;
for(int i=1;i<=son;++i)f[u]=f[u]*base1+Q[i];Q[0]=Q[1]+1;
for(int i=1,s=1,cnt=1;i<=son+1;++i)
if(Q[i]==Q[i-1])++cnt,s=1ll*s*cnt%MOD;
else jc[u]=1ll*jc[u]*s%MOD,cnt=s=1;
jc[u]=fpow(jc[u],MOD-2);
f[u]*=size[u]*base2+base3;
}
int sz[15];ull F[15];
void CalcHash(int u,int ff,int S,int &T)
{
sz[u]=1;T|=1<<u;
for(int i=E[u]&S;i;i-=lb(i))
if(lg[lb(i)]!=ff)
CalcHash(lg[lb(i)],u,S,T),sz[u]+=sz[lg[lb(i)]];
int son=0;for(int i=E[u]&S;i;i-=lb(i))Q[++son]=F[lg[lb(i)]];
sort(&Q[1],&Q[son+1]);F[u]=sz[u];
for(int i=1;i<=son;++i)F[u]=F[u]*base1+Q[i];
F[u]*=sz[u]*base2+base3;
}
void Hash(){dfs(0,0);}
int check(int S)
{
int mx=lg[lb(S)];
for(int i=0;i<n;++i)if(S&(1<<i))if(size[mx]<size[i])mx=i;
int vis=0;CalcHash(mx,0,S,vis);return vis==S?mx:-1;
}
void clear(){for(int i=0;i<15;++i)E[i]=size[i]=sz[i]=F[i]=f[i]=jc[i]=Q[i]=0;}
};
void SpecialCheck()
{
int ans=0;
if(k==1)printf("%d\n",n-1),halt();
if(k==2)
{
for(int i=1;i<=n;++i)ans=(ans+1ll*dg[i]*(dg[i]-1)/2)%MOD;
printf("%d\n",ans);halt();
}
if(k==3)
{
for(int i=2;i<T.cnt;i+=2)
{
int d=dg[T.e[i].v]+dg[T.e[i^1].v]-2;
ans=(ans+1ll*d*(d-1)/2)%MOD;
}
printf("%d\n",ans);halt();
}
if(k==4)printf("%d\n",T.Calc4()),halt();
}
void GetLineGraph(Graph &a,Graph &b)
{
int N=0;
for(int u=1;u<=a.n;++u)
for(int i=a.h[u];i;i=a.e[i].next)N+=1;
N/=2;b.pre(N);
for(int u=1;u<=a.n;++u)
for(int i=a.h[u];i;i=a.e[i].next)
for(int j=a.e[i].next;j;j=a.e[j].next)
b.Add(i>>1,j>>1),b.Add(j>>1,i>>1);
}
map<ull,int> M;
int CalcNode(int k)
{
int nw=0,pw=1;k-=4;
while(k--)GetLineGraph(E[nw],E[pw]),nw^=1,pw^=1;
return E[nw].Calc4();
}
int CalcValue(Tree &a,int k)
{
a.Hash();E[0].pre(a.n);
for(int i=0;i<a.n;++i)
for(int j=a.E[i];j;j-=lb(j))
E[0].Add(i+1,lg[lb(j)]+1),E[0].Add(lg[lb(j)]+1,i+1);
int ret=CalcNode(k);
for(int i=1;i<(1<<a.n)-1;++i){int p=a.check(i);if(~p)if(M.find(a.F[p])!=M.end())ret=(ret+MOD-M[a.F[p]])%MOD;}
return M[a.f[0]]=ret;
}
int leaf[15],f[MAX][15];
bool Leaf[15];
Tree now;int pre[MAX];
void dfs(int u,int ff)
{
int son=0;
for(int i=T.h[u];i;i=T.e[i].next)
if(T.e[i].v!=ff)++son,dfs(T.e[i].v,u);
for(int i=0;i<now.n;++i)
{
if(Leaf[i])continue;
if(son<bul[now.E[i]]){f[u][i]=0;continue;}
for(int j=now.E[i];j;j=(j-1)&now.E[i])pre[j]=0;
pre[0]=1;
for(int j=T.h[u];j;j=T.e[j].next)
{
if(T.e[j].v==ff)continue;
for(int k=now.E[i];k;k=(k-1)&now.E[i])
for(int p=k;p;p-=lb(p))
pre[k]=(pre[k]+1ll*pre[k^lb(p)]*f[T.e[j].v][lg[lb(p)]])%MOD;
}
f[u][i]=1ll*pre[now.E[i]]*now.jc[i]%MOD*C[son-bul[now.E[i]]][leaf[i]]%MOD*JC[leaf[i]]%MOD;
}
}
int CalcTimes(Tree &a)
{
now=a;
for(int i=0;i<a.n;++i)leaf[i]=0,Leaf[i]=false;
for(int i=0;i<a.n;++i)
for(int j=a.E[i];j;j-=lb(j))
if(a.size[lg[lb(j)]]==1)
++leaf[i],now.E[i]^=lb(j);
for(int i=0;i<a.n;++i)if(a.size[i]==1)Leaf[i]=true;
now.Resize(0,0);dfs(1,0);int ret=0;
for(int i=1;i<=n;++i)ret=(ret+f[i][0])%MOD;
return ret;
}
int St[MAX<<1],fa[MAX];
set<ull> Vis;
Tree p;
void dfsTrees(int x,int c,int sp,int lim)
{
if(x==(lim-1)*2+1)
{
int now=0,tot=0;p.clear();
for(int i=1;i<x;++i)
if(St[i]==1)p.E[now]|=1<<(++tot),fa[tot]=now,now=tot;
else now=fa[now];
p.n=lim;p.Hash();
if(Vis.find(p.f[0])!=Vis.end())return;Vis.insert(p.f[0]);
ans=(ans+1ll*CalcValue(p,k)*CalcTimes(p))%MOD;
return;
}
if(c)St[x]=-1,dfsTrees(x+1,c-1,sp,lim);
if(sp<lim-1)St[x]=1,dfsTrees(x+1,c+1,sp+1,lim);
}
int main()
{
n=read();k=read();T.pre(n);
for(int i=1;i<n;++i)
{
int u=read(),v=read();dg[u]+=1,dg[v]+=1;
T.Add(u,v),T.Add(v,u);
}
SpecialCheck();
for(int i=2;i<MAX;++i)lg[i]=lg[i>>1]+1;
for(int i=1;i<MAX;++i)bul[i]=bul[i^lb(i)]+1;
for(int i=0;i<MAX;++i)C[i][0]=1;JC[0]=1;
for(int i=1;i<15;++i)JC[i]=1ll*JC[i-1]*i%MOD;
for(int i=1;i<MAX;++i)
for(int j=1;j<=i&&j<15;++j)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=1;i<=k+1;++i)dfsTrees(1,0,0,i),Vis.clear();
printf("%d\n",ans);
return 0;
}