省选集训 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";
}

浙公网安备 33010602011771号