省选集训 7 - 数学问题

[ARC147D] Sets Scores

\(S_i\) 相对于 \(S_{i-1}\) 的变化看作一次改变某个元素存在状态的操作。

观察到对于同一个操作序列初始选取和不选取第一个数的贡献和为 \(n\)

那记对于某个操作序列考虑 \(i\) 个元素的答案为 \(f_i\),有 \(f_i=f_{i-1}\times n\)

所以对于任意一个操作序列答案均为 \(n^m\),又一共有 \(m^{n-1}\) 种操作序列,所以答案为 \(n^mm^{n-1}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
const int mod=998244353;
int quick_pow(int x,int y,int res=1){
	for(;y;x=x*x%mod,y>>=1)  if(y&1)  res=res*x%mod;
	return res;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>m,cout<<quick_pow(n,m)*quick_pow(m,n-1)%mod;
}

[CERC2015] Frightful Formula

\(i\) 行第 \(1\) 列元素的贡献:\(\binom{2n-i-2}{i-2}\times a^{n-1}\times b^{n-i}\times x\)

\(1\) 行第 \(j\) 列元素的贡献:\(\binom{2n-j-2}{j-2}\times a^{n-j}\times b^{n-1}\times x\)

\(i\) 行第 \(j\) 列加 \(c\) 的贡献:\(\binom{2n-i-j}{n-i}\times a^{n-j}\times b^{n-i}\times c\)

优化 \(c\) 贡献的求和过程即可,手推了一遍不想敲公式了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define LL long long
const LL mod=1000003;
LL n,a,b,c,ans,sum,dp[N],poa[N],pob[N],pab[N],fac[N],inv[N];
LL quick_pow(LL x,LL y,LL res=1){
    for(;y;y>>=1,x=x*x%mod)  if(y&1)  res=res*x%mod;
    return res;
}
LL C(LL n,LL m){return (n<m)?0:fac[n]*inv[m]%mod*inv[n-m]%mod;}
int main(){
    scanf("%lld%lld%lld%lld",&n,&a,&b,&c),fac[0]=inv[0]=poa[0]=pob[0]=pab[0]=1;
    for(int i=1;i<=n*2;i++)  poa[i]=poa[i-1]*a%mod;
    for(int i=1;i<=n*2;i++)  pob[i]=pob[i-1]*b%mod;
    for(int i=1;i<=n*2;i++)  fac[i]=fac[i-1]*i%mod;
    for(int i=1;i<=n*2;i++)  pab[i]=pab[i-1]*(a+b)%mod;
    for(int i=0;i<=n-2;i++)  sum=(sum+pab[i])%mod;
    for(int i=1;i<=n*2;i++)  inv[i]=quick_pow(fac[i],mod-2);
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);if(i==1)  continue;
        ans=(ans+C(2*n-i-2,n-i)*poa[n-1]%mod*pob[n-i]%mod*x)%mod;
    }
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);if(i==1)  continue;
        ans=(ans+C(2*n-i-2,n-i)*poa[n-i]%mod*pob[n-1]%mod*x)%mod;
    }
    for(int i=1;i<=n-2;i++)  dp[n-1]=(dp[n-1]+C(n-1,i)*pob[i]%mod*poa[n-i-1])%mod;
    for(int i=n;i<=n*2-4;i++){
        dp[i]=dp[i-1]*(a+b)%mod;
        dp[i]=(dp[i]-C(i-1,n-2)*poa[i-n+1]*pob[n-1]%mod+mod)%mod;
        dp[i]=(dp[i]-C(i-1,i-n+1)*poa[n-1]*pob[i-n+1]%mod+mod)%mod;
    }
    for(int i=n-1;i<=n*2-4;i++)  sum=(sum+dp[i])%mod;
    printf("%lld\n",(ans+sum*c)%mod);
}

[ARC139D] Priority Queue 2

考虑差分计算贡献,令 \(a_i\) 表示 \(\geq i\) 的数的个数,答案显然等于 \(\sum a_i\)

加入一个数 \(x\) 相当于将 \(a_1,a_2,\cdots,a_x\)\(1\),删除第 \(x\) 小相当于将 \(>n-x+1\)\(a_i\)\(1\)

考虑计算 \(a_i\) 被加了 \(j\) 次的贡献,令 \(x'=n-x+1\)

若初始 \(a_i\leq x'\),则最后 \(a'_i=\min(a_i+j,x')\),因为当 \(a_i\) 大于 \(x'\) 的时候会马上被减掉。

若初始 \(a_i> x'\),则最后 \(a'_i=\max(a_i-k+j,x')\),因为当 \(a_i\) 小于等于 \(x'\) 时就不会被减了。

然后枚举 \(i\)\(j\),组合系数为有 \(j\)\(\geq i\) 的方案数,即 \(\binom{k}{j}\times (m-i+1)^j\times (i-1)^{k-j}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 2005
#define int long long
const int mod=998244353;
int n,m,k,x,xp,ans,a[N],C[N][N],po[N][N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	for(int i=0;i<N;i++)  C[i][0]=po[i][0]=1;
	for(int i=0;i<N;i++)
		for(int j=1;j<N;j++)
			po[i][j]=po[i][j-1]*i%mod;
	for(int i=1;i<N;i++)
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	cin>>n>>m>>k>>x,xp=n-x+1;
	for(int i=1,x;i<=n;i++)  cin>>x,a[x]++;
	for(int i=m;i>=1;i--)  a[i]+=a[i+1];
	for(int i=1;i<=m;i++){
		for(int j=0,tmp;j<=k;j++){
			if(a[i]<=xp) tmp=min(a[i]+j,xp);
			else  tmp=max(a[i]-k+j,xp);
			int sub=po[m-i+1][j]*po[i-1][k-j]%mod;
			ans=(ans+C[k][j]*sub%mod*tmp)%mod;
		}
	}
	cout<<ans<<"\n";
}

[CF1097G] Vladislav and a Great Legend

将幂用斯特林数表示:\(n^m=\sum_i\binom{n}{i}{m \brace i}i!\)

左边的组合意义可以为将用 \(n\) 种颜色涂 \(m\) 个下标的方案数。

右边的组合意义是选 \(i\) 个颜色,将 \(m\) 个下标分配到 \(i\) 种颜色里去,所以是相等的。

那只需要对于每个 \(i\) 求出 \(\sum_S{f(S) \brace i}\) 就可以了,相当于在每个生成树中选出 \(i\) 条边的方案和。

\(f_{u,i}\) 表示在以 \(u\) 为根的子树中选出一棵非空生成树,并从树根到 \(u\) 的路径与树中共选出 \(i\) 条边的方案数。

为什么这样定义呢?

因为这样就刚好能处理包含这个生成树顶点和 \(u\) 的树,即在这个生成树的 \(S\) 中添加 \(u\)

\(f_{u,i}\)\(f_{v,j}\) 转移到新的 \(f_u\)\(g\) 的过程分三种:

只包含 \(u\) 中顶点:\(g_{i}\leftarrow f_{u,i}\)

只包含 \(v\) 中顶点,枚举是否选 \((u,v)\)\(g_{j+0/1}\leftarrow f_{v,j}\)

同时包含 \(u\)\(v\) 中顶点,枚举是否选 \((u,v)\)\(g_{i+j+0/1}\leftarrow f_{u,i}\times f_{v,j}\)

统计答案时时可能出现某个生成树在 \(u\) 到根路径上选了边但又没增加点的情况。

所以只在以 \(u\) 为根的子树统计答案,即只统计转移三就能规避这个问题。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define add(x,y) (x)=((x)+(y))%mod
const int N=100005,K=205,mod=1000000007;
vector<int> v[N];
int n,k,res,ans[K],f[K],sz[N],fac[K],dp[N][K],S[K][K];
void dfs(int x,int fa){
	dp[x][0]=sz[x]=1;
	for(auto y:v[x]){
		if(y==fa)  continue;
		dfs(y,x),copy(dp[x],dp[x]+min(sz[x],k+1),f);
		for(int j=0;j<=min(sz[y]-1,k);j++){
			add(f[j],dp[y][j]);
			if(j!=k)  add(f[j+1],dp[y][j]);
		}
		for(int i=0;i<=min(sz[x]-1,k);i++){
			for(int j=0;j<=min(k-i,sz[y]-1);j++){
				add(f[i+j],dp[x][i]*dp[y][j]);
				add(ans[i+j],dp[x][i]*dp[y][j]);
				if(i+j!=k){
					add(f[i+j+1],dp[x][i]*dp[y][j]);
					add(ans[i+j+1],dp[x][i]*dp[y][j]);
				}
			}
		}
		copy(f,f+min(sz[x]+sz[y],k+1),dp[x]);
		fill(f,f+min(sz[x]+sz[y],k+1),0),sz[x]+=sz[y];
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>k,S[0][0]=fac[0]=1;
	for(int i=1;i<=k;i++)  fac[i]=fac[i-1]*i%mod;
	for(int i=1,x,y;i<n;i++){
		cin>>x>>y;
		v[x].push_back(y),v[y].push_back(x);
	}
	for(int i=1;i<=k;i++)
		for(int j=1;j<=k;j++)
			S[i][j]=(S[i-1][j]*j+S[i-1][j-1])%mod;
	dfs(1,0);
	for(int i=1;i<=k;i++)  add(res,ans[i]*S[k][i]%mod*fac[i]);
	cout<<res<<"\n";
}
posted @ 2026-01-05 16:23  tkdqmx  阅读(30)  评论(0)    收藏  举报