数学口胡记录
[ABC213G] Connectivity 2
\(tag\):计数,\(dp\),容斥
\(n\)比较小,考虑状压。
设\(dp_s\)表示只有\(s\)中的点都联通的方案数。\(g_s\)表示\(s\)的所有子图个数,若\(s\)中有\(cnt\)条边,那么\(g_s=2^{cnt}\)。
考虑计算\(dp_s\),可以用总方案减去不合法方案,对于不合法方案数枚举\(s\)的子集\(t\),钦定\(t\)中节点联通,其余\(s-t\)中的点随意。
那么有\(dp_s=g_s-\sum\limits_{t \in s}{dp_t*g_{s \oplus t}}\)
但是发现这样实际上会算重,可能一个点集在枚举中是联通的,在任意连时也是联通的。
因此考虑在枚举\(s\)时强制将某个点选入,这样就一定不会重复。取任意点答案都是一样的,如取\(1\)即可。
那么有\(dp_s=g_s-\sum\limits_{1\in t \in s}{dp_t*g_{s \oplus t}}\)
设\(up\)为全集,答案就是\(ans_i=\sum\limits_{1\in s ,i \in s}dp_s*g_{up \oplus s}\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=25;
const int MAXM=405;
const int MAXS=5e5+5;
int n,m;
vector <int> G[MAXN];
#define ll long long
const ll MOD=998244353;
ll g[MAXS],dp[MAXS];
ll mul2[MAXM],ans[MAXN];
int main(){
mul2[0]=1;
for (int i=1;i<MAXM;i++) mul2[i]=mul2[i-1]*2ll%MOD;
cin>>n>>m;
for (int i=1;i<=m;i++){
int u,v;
scanf("%d %d",&u,&v);
G[u].push_back(v);G[v].push_back(u);
}
int up=(1<<n)-1;
for (int mask=0;mask<=up;mask++){
int cnt=0;
for (int i=1;i<=n;i++){
if (!(mask&(1<<(i-1)))) continue;
for (const auto &j:G[i]){
if (mask&(1<<(j-1))) cnt++;
}
}
cnt/=2;
g[mask]=mul2[cnt];
}
dp[0]=1;
for (int mask=1;mask<=up;mask++){
dp[mask]=g[mask];
for (int mask2=mask;mask2;mask2=(mask2-1)&mask){
if (mask2==mask) continue;
if (mask2&1){
dp[mask]-=dp[mask2]*g[mask^mask2]%MOD;
dp[mask]%=MOD;dp[mask]+=MOD;dp[mask]%=MOD;
}
}
}
for (int mask=0;mask<=up;mask++){
for (int i=2;i<=n;i++){
if ((mask&1)&&(mask&(1<<(i-1)))) ans[i]+=dp[mask]*g[up^mask]%MOD,ans[i]%=MOD;
}
}
for (int i=2;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}
树上竞技
题意:给定一个\(n\)个点的树,每次选择\(m\)个点,设\(s\)为到这\(m\)个点总距离和最小的点,这个大小为\(m\)的集合的价值即为\(m\)个点到\(s\)距离和。求所有大小为\(m\)的集合的价值之和,对\(998244353\)取模。\(1 \leq m \leq n \leq 10^6\)
\(tag\):排列组合,推式子
发现价值不好从点入手,因此考虑每条边的贡献。
称在集合中的点为关键点,对于每条边,枚举其两个端点子树中包含的关键点个数\(i\),显然让关键点更少的一边向关键点更多的一边走更优,因此这条边贡献为\(\min ( i,m-i )\)。
所以对一条边,设其两个端点子树大小分别为\(s\),\(n-s\)那么贡献为:
\(\sum\limits_{i=1}^{m-1} \binom{s}{i} \binom{n-s}{m-i} \times \min ( i,m-i )\)
发现\(min\)难以处理,考虑将其拆开。发现这个结构是对称的,令\(k=\frac{m-1}{2}\)因此有:
\(=2\times \sum\limits_{i=1}^{k} \binom{s}{i} \binom{n-s}{m-i} \times i+ \left[m\pmod 2 \equiv 0 \right] \binom{s}{\frac{m}{2}} \binom{n-s}{\frac{m}{2}}\)
后面那部分可以直接算,考虑快速计算前面一部分。
有吸收公式有:
\(=2s\times \sum\limits_{i=1}^{k} \binom{s-1}{i-1} \binom{n-s}{m-i}\)
设\(g(s)=\sum\limits_{i=1}^k \binom{s-1}{i-1} \binom{n-s}{m-1}\),发现具有组合意义,可以看做从\(n-s\)个数中选出\(m-1\)个数,并且要求前\(s-1\)个数中选的不超过\(k-1\)个
那么\(g(s)\)可以递推,具体来说,考虑从\(g(s)\)到\(g(s+1)\)中从合法变为不合法的个数,显然只有前\(s-2\)个中恰好选\(k-1\)个,第\(s-1\)个钦定选择,剩下\(n-s\)中再选\(m-k-1\)个不符合。
所以有\(g(1)=\binom{n-1}{m-1},g(i)=g(i-1)-\binom{i-2}{k-1}\times\binom{n-i}{m-k-1}\)
然后就做完了,时间复杂度\(O(n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
template <class T>
void read(T &x){
x=0;char c=getchar();bool f=0;
while(!isdigit(c)) f=c=='-',c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
x=f? (-x):x;
}
const int MAXN=1e6+5;
#define ll long long
const ll MOD=1e9+7;
int n,m;
vector <int> G[MAXN];
ll fac[MAXN],inv[MAXN];
ll g[MAXN];
int siz[MAXN];
ll power(ll a,ll b,ll MOD){
ll ret=1;
for (;b;b>>=1){
if (b&1) ret=(ret*a)%MOD;
a=(a*a)%MOD;
}
return ret;
}
void init(int n){
fac[0]=1;
for (int i=1;i<=n;i++) fac[i]=fac[i-1]*(ll)i%MOD;
inv[n-1]=power(fac[n-1],MOD-2,MOD);
for (int i=n-1;i>=1;i--){
inv[i-1]=inv[i]*(ll)i%MOD;
}
}
ll C(int n,int m){
if (m>n||m<0) return 0;
return fac[n]*inv[n-m]%MOD*inv[m]%MOD;
}
ll ans;
void dfs(int u,int fa){
siz[u]=1;
for (const auto &v:G[u]){
if (v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
ans+=(g[siz[v]]+g[n-siz[v]])%MOD;
ans%=MOD;
if (m%2==0) ans+=C(siz[v],m/2)*C(n-siz[v],m/2)%MOD*(m/2)%MOD,ans%=MOD;
}
}
int main(){
init(MAXN);
read(n);read(m);
for (int i=2;i<=n;i++){
int v;
read(v);
G[i].push_back(v);G[v].push_back(i);
}
int k=(m-1)/2;
if (k>=1) g[1]=C(n-1,m-1);
else g[1]=0;
for (int i=2;i<=n;i++){
g[i]=(g[i-1]-C(i-2,k-1)*C(n-i,m-k-1)%MOD);
g[i]%=MOD;g[i]+=MOD;g[i]%=MOD;
}
for (int i=1;i<=n;i++) g[i]*=(ll)i,g[i]%=MOD;
dfs(1,0);
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号