51nod1231 记分牌
51nod1231 记分牌
\(N\)个选手参加一场比赛,赛制为循环赛,每2个人都要比1场,获胜的人得到\(1\)分,失败的人不得分。比赛结束后会有一个积分榜,每个选手都有一个总的积分。
然而,某些选手的积分成绩被擦掉了。那么根据剩下的数据,由你统计一下,积分榜共有多少种不同的情况。其中被擦掉的成绩用\(-1\)表示。
\(T\)组数据,由于结果很大,输出对\(1000000007\)取模的结果即可。
\(T\leq 20,N\leq 40\).
首先有题面可知N个选手的胜负关系构成了一张竞赛图。
每个选手的出度即胜利次数。
Landau定理
整数序列\((s_1,s_2,s_3,...,s_n)\)当且仅当满足以下条件时为得分序列:
\[0\leq s_1\leq s_2\leq \cdots\leq s_n\\
i\in[1,n],\sum_{i=1}^{i}s_i\geq \binom{i}{2}\\
\sum_{i=1}^{n}s_i=\binom{n}{2}
\]
具体证明还没学会,咕咕咕~
所以只要统计出有多少序列满足兰道定理即可。
设计\(dp\)状态\(dp[i][j][k]\)表示放至第\(i\)个点,第一个还未放的数是\(j\),前i个数总和为\(k\)的合法方案数。
每次转移时枚举要放多少个等于\(j\)的数,显然至少要放原序列中出现的次数个,并且转移后的\(k\)必须满足兰道定理的第二个要求,在所有这些数中,原序列中未出现的数是可以自由选择位置的,而当前状态下前面也已经有一些自由的数相对位置确定,所以可以用插板法,最后\(dp[n][m][\tbinom{n}{2}]\)就是答案,转移时间复杂度\(\Omicron(n)\),总时间复杂度\(\Omicron(n^5)\)。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=45;
const int mod=1e9+7;
int T,n,cnt[maxn],a[maxn],dp[maxn][maxn][maxn*maxn];
int suf[maxn],c[maxn][maxn];
int main(){
scanf("%d",&T);
c[0][0]=1;
for (int i=1;i<maxn;i++){
c[i][i]=c[i][0]=1;
for (int j=1;j<i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
while(T--){
memset(dp,0,sizeof(dp));
scanf("%d",&n);
fill(cnt,cnt+n+1,0);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]!=-1) cnt[a[i]]++;
}
suf[n+1]=0;
for (int i=n;~i;i--) suf[i]=suf[i+1]+cnt[i];
dp[0][0][0]=1;
int MX=n*(n-1)/2;
for (int i=0;i<=n;i++)
for (int j=0;j<=n;j++)
for (int k=i*(i-1)/2;k<=MX;k++){
ll v=dp[i][j][k];
if(v){
for (int k_=cnt[j];i+k_+suf[j+1]<=n;k_++){
if(k+k_*j>MX) break;
if(k+k_*j<(i+k_)*(i+k_-1)/2) continue;
dp[i+k_][j+1][k+k_*j]=(dp[i+k_][j+1][k+k_*j]+v*c[n-i-suf[j]][k_-cnt[j]]%mod)%mod;
}
}
}
printf("%d\n",dp[n][n][MX]);
}
return 0;
}