组合计数
痛恨一切形式的数数题。
考虑点对 \((x_1,y_1)\) 和 \((x_2,y_2)\) ,先计算它们的 \(x\) 的贡献,假设 \(x_2-x_1 = d\) ,那么 \(x_1\) 可以放在 \([1,n-d]\) 行,总共 \(n-d\) 种情况。
而除了行数,还需要考虑列数,发现两个点所在的列是有序的,所以共有 \(m^2\) 种选择,而贡献为 \(d\) ,所以只考虑两个点的总贡献为 \(d \times (n-d) \times m^2\) 。
除了这两个点,其他的点都是可以随便放的,所以只考虑 \(x\) 的总答案为 \(d \times (n-d) \times m^2 \times C_{nm-2}^{k-2}\) 。
对于 \(y\) 同理,加和即可。
故可知最后的答案为 \((\sum_{d=1}^{n-1}d \times (n-d) \times m^2+\sum_{d=1}^{m-1}d \times (m-d) \times n^2)\times C_{nm-2}^{k-2}\) 。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+7;
const int N=2e5+5;
ll n,m,k;
ll fac[N],inv[N];
ll qpow(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
void pre(int n){
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2);
for(int i=n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
ll C(int n,int m){ return fac[n]*inv[m]%mod*inv[n-m]%mod; }
ll ans;
int main(){
scanf("%lld%lld%lld",&n,&m,&k);
pre(n*m);
for(int d=1;d<n;d++) ans=(ans+d*(n-d)%mod*m%mod*m%mod)%mod;
for(int d=1;d<m;d++) ans=(ans+d*(m-d)%mod*n%mod*n%mod)%mod;
ans=ans*C(n*m-2,k-2)%mod;
printf("%lld",ans);
return 0;
}
先打一个暴力,容易想到根据 \(a_i\) 从小到大放,判断可行性,将答案乘上方案数算入总贡献。
亲测可得 \(20pts\) 。
#include <bits/stdc++.h>
using namespace std;
const int N=35;
const int mod=998244353;
int n,m,k;
int v[N],a[N],c[N];
int C(int n,int m){
int c=1;
for(int i=1;i<=n;i++) c*=i;
for(int i=1;i<=m;i++) c/=i;
for(int i=1;i<=n-m;i++) c/=i;
return c;
}
int ans;
void dfs(int x){
if(x==n+1){
int res=0,tmp=1,p=0;
for(int i=1;i<=n;i++) res+=(1ll<<a[i]);
if(__builtin_popcount(res)>k) return;
res=1;
for(int i=1;i<=n;i++) c[a[i]]++,res=1ll*res*v[a[i]]%mod;;
for(int i=0;i<=m;i++) tmp=1ll*tmp*C(n-p,c[i])%mod,p+=c[i];
for(int i=0;i<=m;i++) c[i]=0;
ans=(ans+1ll*res*tmp%mod)%mod;
return;
}
for(int i=a[x-1];i<=m;i++){
a[x]=i;
dfs(x+1);
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<=m;i++) scanf("%d",&v[i]);
dfs(1);
printf("%d",ans);
return 0;
}
设 \(dp(i,j,k,l)\) 表示 \(a\) 序列已经放了 \(i\) 个数,其中进位到的最高一位为 \(j\),也就是说填到了 \(j-1\) 位(注意到这里的进位不能引起连锁反应,即进位到的最高一位上的数有可能大于 \(1\)),除了最高位的其他数位的 \(1\) 的个数为 \(k\) ,最高位有 \(l\) 个 \(1\) 等待进位时的贡献总和。
考虑用刷表法转移,假设现在有一个长度为 \(t\) 的连续段都填了 \(j\)。
所以 \(dp(i,j,k,l)\) 能转移到 \(dp(i+t,j+1,k+(t+l)\mod 2,\lfloor \frac{t+l}{2} \rfloor)\) 。
这些数能贡献 \(v_{j}^t\times C_{n-i}^{t} \times dp(i,j,k,l)\) ,累加即可。
注意数组别开小了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=35;
const int mod=998244353;
ll dp[N][105][N][N];
ll v[105],pw[105][N];
int n,m,K;
void add(ll &x,ll y){ x=(x+y)%mod; }
ll C[N][N],ans;
void pre(){
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
int main(){
scanf("%d%d%d",&n,&m,&K);
for(int i=0;i<=m;i++){
scanf("%lld",&v[i]);
pw[i][0]=1;
for(int j=1;j<=n;j++) pw[i][j]=pw[i][j-1]*v[i]%mod;
}
pre();
dp[0][0][0][0]=1;
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=K;k++){
for(int l=0;l<=n/2;l++){
if(!dp[i][j][k][l]) continue;
for(int t=0;i+t<=n;t++) add(dp[i+t][j+1][k+(t+l)%2][(t+l)>>1],dp[i][j][k][l]*pw[j][t]%mod*C[n-i][t]%mod);
}
}
}
}
for(int k=0;k<=K;k++)
for(int l=0;l<=n/2;l++)
if(k+__builtin_popcount(l)<=K) add(ans,dp[n][m+1][k][l]);
printf("%lld",ans);
return 0;
}
upd:看懂了,我爱 dp 计数。
首先,只有一行是可做的。
设 \(f_{i,j}\) 表示填到第 \(i\) 个位置时用了 \(j\) 种颜色的方案数,\(j\) 种颜色是一个升序排列。
\(f_{i,j}=f_{i-1,j} \times (j-1) + f_{i-1,j-1}\) ,意义表示为这个点可以选前面出现过的颜色,但不能和前一个位置是同一种颜色,共有 \(j-1\) 种选择方案,当然也可以新开一个颜色,这里没有乘任何数的原因在于我定义颜色必须为一个升序排列。
这就不得不提到这个题,题解区有两种写法,第一种是乘了的,第二种是没乘的。是 dp 的定义导致了这样的问题,具体来说第一种写法定义的就是前 \(i\) 个球放在 \(j\) 个盒子里的方案数,盒子是无序的,也就是在讨论当新增一个盒子的时候,我可以把这个盒子随机插到前面的 \(j\) 个空中。第二种写法则定义为前 \(i\) 个球放在 \(j\) 个从小到大的盒子中的方案数,盒子是有序的,所以不能插空,只能放在尾部。
由于盒子不能为空,所以在第二种写法的最后会乘上一个排列,两种写法殊途同归,但对于本题,第二种写法在行与行之间转移更为方便。
所以一行的总方案就为 \(\sum_{j=1}^{min(l_i,m)}f_{l_i,j} \times A_{m}^{j}\) 。
于是再考虑多行,但不管颜色集合是否会发生冲突。
设 \(dp_{i,j}\) 表示前 \(i\) 行,第 \(i\) 行选了 \(j\) 种颜色的方案数,需要注意的一点是,这里的 \(j\) 种颜色升序但不一定是排列。
所以 \(dp_{i.j}=\sum_{k=1}^{min(l_{i-1},m)} dp_{i-1,k} \times (f_{l_i,j} \times C_{m}^{j})\) 。
那如果集合颜色不能相同呢,需要在这一行的方案中减掉第 \(i\) 行和第 \(i-1\) 行颜色集合相同的情况,应该有 \(dp_{i,j}=\sum_{k=1}^{min(l_{i-1},m)} dp_{i-1,k} \times (f_{l_i,j} \times C_{m}^{j})-dp_{i-1,j} \times f_{l_i,j}\) 。
这里我关于 \(dp_{i-1,j} \times f_{l_i,j}\) 为什么不用乘 \(C_{m}^{j}\) 想了好久,原因在于如果乘了,就减多了,意义就变了。
具体来说,看一组数据,假设有两行,以下是是第一行 \(4\) 种颜色选 \(3\) 种的情况。
1 2 3
1 2 4
1 3 4
2 3 4
如果乘了 \(C_{m}^{j}\),即减掉了 \(dp_{i-1,j} \times f_{l_i,j} \times C_{m}^{j}\),那么进行到第二行时,就相当于把 1 2 3 看作和1 2 3、1 2 4、1 3 4、2 3 4颜色集合相同了。
这显然是不合法的,所以减多了。
你以为这就结束了?这只是升序但不一定是排列的情况,但这道题也不一定是升序啊。
其实这个很好处理,我只需要改一下 \(dp\) 数组的定义,定义为表示前 \(i\) 行,第 \(i\) 行选了 \(j\) 种颜色,颜色无序的总方案数。
那只需要在转移的时候乘一个 \(j!\) 即可,也就是说得到了最终的状态转移方程为:
最后答案为 \(\sum_{j=1}^{min(l_n,m)} dp_{n,j}\) 。
预处理一下即可。
滚动数组优化空间时别忘了清空上一次的数组。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5,M=5e3+5;
int n,m,mod,mx;
ll fac[N],A[N];
ll f[M][M],dp[2][M];
void add(ll &x,ll y){ x=(x+y)%mod; }
int l[N];
void pre(){
fac[0]=1;
for(int i=1;i<=m;i++) fac[i]=fac[i-1]*i%mod;
A[0]=1;
for(int i=1;i<=m;i++) A[i]=A[i-1]*(m-i+1)%mod;
f[0][0]=1;
for(int i=1;i<=mx;i++)
for(int j=1;j<=min(i,m);j++)
add(f[i][j],(f[i-1][j]*(j-1)+f[i-1][j-1])%mod);
}
ll ans;
int main(){
scanf("%d%d%d",&n,&m,&mod);
for(int i=1;i<=n;i++) scanf("%d",&l[i]),mx=max(mx,l[i]);
pre();
int pre=0,cur=1;
dp[cur][0]=1;
for(int i=1;i<=n;i++){
ll sum=0;
swap(pre,cur);
for(int j=0;j<=min(l[i-1],m);j++) add(sum,dp[pre][j]);
for(int j=0;j<=min(l[i],m);j++){
if(j>l[i-1]) dp[pre][j]=0;
ll tmp=sum*f[l[i]][j]%mod*A[j]%mod-dp[pre][j]*f[l[i]][j]%mod*fac[j]%mod;
tmp=(tmp+mod)%mod;
add(dp[cur][j]=0,tmp);
}
}
for(int i=0;i<=min(l[n],m);i++) add(ans,dp[cur][i]);
printf("%lld",ans);
return 0;
}
会了,十分感谢这篇题解。
实在是太牛逼了。
#include <bits/stdc++.h>
using namespace std;
const int N=105;
const int mod=1e9+7;
int n,m;
int dp[N][N][3005];
void add(int &x,int y){ x=(x+y)%mod; }
int main(){
scanf("%d%d",&n,&m);
dp[1][0][0]=dp[1][1][2]=1;
for(int i=1;i<n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=m;k++){
if(!dp[i][j][k]) continue;
add(dp[i+1][j][k+2*j],dp[i][j][k]);
if(j){
add(dp[i+1][j][k+2*j],2ll*j*dp[i][j][k]%mod);
add(dp[i+1][j-1][k+2*(j-1)],1ll*j*j*dp[i][j][k]%mod);
}
add(dp[i+1][j+1][k+2*(j+1)],dp[i][j][k]);
}
}
}
printf("%d",dp[n][0][m]);
return 0;
}

浙公网安备 33010602011771号