T1 题解
题意:有一张 n 个点的有标号无向图,分为了 k 个连通块,第 i 个连通块的大小是 si,每个连通块都是完全图(节点之间两两有边)。要加 k−1 条边使得图连通,计算所有连边方案的权值和。假设第 i 个连通块被多加了 di 条边,那么该连边方案的权值为 k∏i=1di!
sol:首先把原图看成缩点后的一棵树,假设现在 d 序列已经确定了对应的权值也就确定了。但是对应了多种树的形态。要求一个确定的 d 序列对应了多少种树的形态,转化为求 prufer 序列的个数即:
(k−2)!k∏i−1(di−1)!
但是原图中第 i 个连通块连的边有 si 个点可以选择去连所以一个确定的 d 序列在原图中对应的连边方式为:
(k−2)!k∏i−1(di−1)!×k∏i=1sdii
贡献为:
(k−2)!k∏i−1(di−1)!×k∏i=1sdii×k∏i=1di!
=(k−2)!×k∏i=1sdiidi
然后(k−2)! 是个定值,后面的考虑 DP ,我们设 fi,j 表示前 i 个连通块 i∑l=1dl=j 的 k∏i=1sdiidi 这个值。显然 f1,j=j×sj1 转移也很简单:
fi,j=j−1∑t=1t×sti×fi−1,j−t
但是这个是O(k3) 的,考虑怎么优化,我们试着把fi,j 拆一下。
fi,j−1=si×fi−1,j−2+2s2i×fi−1,j−3+3s3i×fi−1,j−4……
fi,j=si×fi−1,j−1+2s2i×fi−1,j−2+3s3i×fi−1,j−3……
fi,j+1=si×fi−1,j+2s2i×fi−1,j−1+3s3i×fi−1,j−2……
我们把 fi,j−1 乘一个系数 si ,把 fi,j+1 乘上一个系数 1si 就会发现一件神奇的事情!
sifi,j−1=0×fi−1,j+0×fi−1,j−1+s2i×fi−1,j−2+2s3i×fi−1,j−3……
fi,j=0×fi−1,j+si×fi−1,j−1+2s2i×fi−1,j−2+3s3i×fi−1,j−3……
1sifi,j+1=1×fi−1,j+2si×fi−1,j−1+3s2i×fi−1,j−2+4s3i×fi−1,j−3……
然后我们就可以抵消掉一大堆东西得到:
sifi,j−1+1sifi,j+1=2fi,j+fi−1,j
fi,j+1=2sifi,j+sifi−1,j−s2ifi,j−1
换一下元
fi,j=2sifi,j−1+sifi−1,j−1−s2ifi,j−2
然后就可以 O(k2) 递推了。
code:
using namespace std;
typedef long long ll;
const ll N=7005,mod=998244353;
inline ll rd()
{
ll f=1;char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1;
ll x=c^48;
while(isdigit(c=getchar())) x=(x<<3)+(x<<1)+(c^48);
return f*x;
}
ll n,k,fac[N]={1},g[N],s[N];
int f[N][N<<1];
int main()
{
n=rd(),k=rd();ll d=2*(k-1);
for(int i=1;i<=k;i++) fac[i]=fac[i-1]*i%mod,s[i]=rd();g[0]=1;
for(int i=1;i<=d;i++) g[i]=g[i-1]*s[1]%mod;
for(int i=1;i<=d;i++) f[1][i]=g[i]*i%mod;
for(int i=2;i<=k;i++)
for(int j=i;j<=d;j++)
f[i][j]=(2*s[i]*f[i][j-1]%mod-s[i]*s[i]%mod*f[i][j-2]%mod+s[i]*f[i-1][j-1]%mod+mod)%mod;
cout<<(1ll*f[k][d]*fac[k-2]%mod+mod)%mod<<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架