Loading

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;
}
posted @ 2021-03-07 14:29  SmilingKnight  阅读(54)  评论(0编辑  收藏  举报