My Blogs
[ARC171C] Swap on Tree
科技改變生活。以 6ms 的速度拿下了目前最優解(
如果已經確定了 \(u\) 的一個兒子 \(v\) 內部的操作順序,考慮在某個時刻交換 \((u,v)\)。設 \(a[1,k]\) 是操作 \(v\) 子樹內部時 \(v\) 上面的顏色,可以發現在第 \(i\) 個時刻交換和在第 \(j\) 個時刻交換,方案本質不同的充要條件是 \(a_i\not= a_j\)。
考慮 \(i\) 和 \(i\) 的所有兒子,如果選擇了 \(S\) 作為要與 \(i\) 交換的子樹集合,則權值是 \(|S|!\prod_{i\in S}(\sum_j jf_{i,j})\prod_{i\notin S}(\sum_j f_{i,j})\),其中 \(f_{i,j}\) 表示 \(i\) 子樹內顏色改變次數是 \(j\) 的方案數。\(|S|!\) 是因為在確定 \(i\) 和集合內所有點的操作順序,\(jf_{i,j}\) 是因為對於在集合裡的點有顏色切換次數個本質不同的交換方案。
暴力做是 \(\mathcal O(n^2)\) 的。設 \(g_{i,0}=\sum_i f_{x,i},g_{i,1}=\sum_i if_{x,i}\)。設多項式 \(F_u(x)=g_{u,0}+g_{u,1}x\),則 \(f_{u,k+1}\) 即為 \(k![x^k]\prod_{v\in\text{son}(u)}F_v(x)\)。
注意到要乘的多項式的總個數是 \(\mathcal O(n)\) 的,所以可以用你喜歡的方法比如分治 NTT 或者先 ln 再 exp 做到 \(\mathcal O(n\log^2 n)\) 或者 \(\mathcal O(n\log n)\)。
int n,fr[200010],g[200010][2];
vi T[200010];
vp F;
vi solve(int l,int r)
{
if(l==r)return {F[l].fi,F[l].se};
int mid=(l+r)>>1;
return FFT(solve(l,mid),solve(mid+1,r));
}
void dfs(int x,int fa=0)
{
for(auto to:T[x])if(to!=fa)dfs(to,x);
F.clear();
for(auto to:T[x])if(to!=fa)F.eb(mp(g[to][0],g[to][1]));
if(F.empty())return g[x][0]=g[x][1]=1,void();
vi ve=solve(0,F.size()-1);
for(int i=1;i<=ve.size();++i)Mmul(ve[i-1],fr[i-1]),Madd(g[x][1],Cmul(ve[i-1],i)),Madd(g[x][0],ve[i-1]);
}
inline void mian()
{
read(n),init(),fr[0]=1;int x,y;
for(int i=1;i<=n;++i)fr[i]=Cmul(fr[i-1],i);
for(int i=1;i<n;++i)read(x,y),T[x].eb(y),T[y].eb(x);
dfs(1),write(g[1][0]);
}