Idea
随便想的,来源出自读错题。如果有原或者觉得我的sol错了可以评论或私信。
1:
\(n\) 个点的无根树,每次可以选一个非空联通块删掉,要求删掉这个联通块后剩余部分仍然联通。对于所有 \(k \in [1,n]\),求有多少种方案恰好 \(k\) 次操作后删空。\(n \le 500\)
solution
先划分联通块,然后相当于每次选一个叶子删掉。
无根树可以先钦定根,发现每一种方案都被算了根所在联通块大小次,利用点边容斥使这个联通块的系数变成 \(1\)。
复杂度 \(O(n^3)\)
未经验证的代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=410,mod=1e9+7;
vector<int> e[N];
int sz[N];
PII E[N];
int C[N][N];
int f[N][N];
int g[N];
int Ans[N];
int n;
void init(int n)
{
for(int i=0;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
void Add(int &a,int b)
{
if(a+b>=mod) a=a-mod+b;
else a=a+b;
}
void Dp(int u,int p,int t=0)
{
memset(f[u],0,sizeof f[u]);
f[u][0]=1;
sz[u]=0;
for(auto v:e[u])
{
if(v==p) continue;
Dp(v,u);
memset(g,0,sizeof g);
for(int j=0;j<=sz[u];j++)
{
for(int k=0;k<=sz[v];k++)
{
Add(g[j+k],f[u][j]*f[v][k]%mod*C[j+k][k]%mod);
}
}
memcpy(f[u],g,sizeof g);
sz[u]+=sz[v];
}
memset(g,0,sizeof g);
for(int j=0;j<=sz[u];j++)
{
if(t!=2) Add(g[j],f[u][j]);
if(t!=1) Add(g[j+1],f[u][j]);
}
sz[u]+=1;
memcpy(f[u],g,sizeof g);
}
signed main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
cin>>n;
init(n);
for(int i=1,a,b;i<n;i++)
{
cin>>a>>b;
e[a].push_back(b);
e[b].push_back(a);
E[i]={a,b};
}
for(int i=1;i<=n;i++)
{
Dp(i,0,2);
for(int j=1;j<=n;j++) Add(Ans[j],f[i][j]);
}
for(int i=1;i<n;i++)
{
int a=E[i].first,b=E[i].second;
Dp(a,b,1),Dp(b,a,1);
for(int j=0;j<=sz[a];j++)
{
for(int k=0;k<=sz[b];k++)
{
Add(Ans[j+k+1],mod-f[a][j]*f[b][k]%mod*C[j+k][j]%mod);
}
}
}
for(int i=1;i<=n;i++) cout<<Ans[i]<<"\n";
return 0;
}
2:
\(n\) 个点的有根树,所有边的方向从祖先指向儿子。
\(m\) 次询问,每次添加一条有向边 \((u,v)\),问你合法拓扑序个数。
\(n \le 1e5,q \le 100\)
solution(正确性未知)
首先外向树的合法拓扑序个数是容易的,这个等于 \(\frac{n!}{\prod sz_i}\)。
添加一条边,相当于要求了这两个点的先后关系,我们求出这个的概率乘上树合法拓扑序个数即可。
这个概率相当于从 LCA 开始,如果扩展的点不在这两个儿子的方向,不管。否则就是更靠近某个儿子一步。
可以认为有两个栈,每次等概率弹掉一个栈顶,问某个栈先空的概率。
直接 dp:\(f_{i,j}=\frac{f_{i-1,j}+f_{i,j-1}}{2}\).
其中 \(f_{0,i}=1\)。
不知道能不能做到比 \(O(min(n^2,nq))\) 好?

浙公网安备 33010602011771号