有 n n n支球队比赛,每两支球队之间踢一场比赛,如果平局,则两支球队各得 1 1 1分;否则,胜利的球队得 3 3 3分,败者不得分。现在给出所有比赛结束后每支球队的得分,求有多少种可能的比赛过程。输出答案模 1 0 9 + 7 10^9+7 109+7后的值。
3 ≤ n ≤ 10 3\leq n\leq 10 3≤n≤10,数据保证至少存在一组解。
我们发现 n n n比较小,可以考虑搜索。按编号从小到大来处理球队,假设当前处理到第 i i i支球队,则继续搜索并处理 i i i和 i + 1 ~ n i+1\sim n i+1~n的比赛结果,然后继续处理第 i + 1 i+1 i+1支球队。
不过,直接搜索肯定是不行的,我们要加一些剪枝。
如果一个球队当前的分数大于其总分,则结束搜索。
如果当前枚举的这支球队在之后的比赛中全赢也达不到其应得的分数,则结束搜索。
令所有队伍的总得分为 s u m sum sum,设能分出胜负的比赛场数为 w n wn wn,平局的场数为 d r dr dr,于是我们可以列出方程组
{ w n + d r = n ( n ? 1 ) 2 3 w n + 2 d r = s u m \left\{\begin{matrix} wn+dr=\dfrac{n(n-1)}{2} \\ \qquad \\ 3wn+2dr=sum \end{matrix}\right. ? ? ??wn+dr=2n(n?1)?3wn+2dr=sum?
得到 w n = s u m ? n ( n ? 1 ) wn=sum-n(n-1) wn=sum?n(n?1), d r = n ( n ? 1 ) 2 ? w n dr=\dfrac{n(n-1)}{2}-wn dr=2n(n?1)??wn。在搜索的时候,要保证分出胜负的比赛的次数不超过 w n wn wn,平局的场数不超过 d r dr dr。
我们发现,在处理完前 i i i个人之后,前 i i i个人的分数在之后的比赛中是不会改变的,那么前 i i i个人当前的分数都应该和其应得的分数相同。对于第 i + 1 ~ n i+1\sim n i+1~n支球队,我们可以用哈希来表示这些人的分数,然后用记忆化搜索来将对应哈希值的答案存下来。因为不同的球队是等价的,我们可以在求哈希值的时候将第 i + 1 ~ n i+1\sim n i+1~n支球队的分数从小到大排序,这样可以减少状态数。
注意为了避免前导零的影响,求哈希值的时候每一位都要 + 1 +1 +1。
#include<bits/stdc++.h>
using namespace std;
const int base=29;
const long long mod=1e9+7;
int n,sum=0,ans=0,a[15],b[15];
map<long long,long long>mp;
long long dfs(int i,int j,int wn,int dr){
if(i==n) return 1;
if(a[i]-3*(n-j+1)>0) return 0;
if(j>n){
for(int o=1;o<=i;o++){
if(a[i]) return 0;
}
for(int o=i+1;o<=n;o++) b[o]=a[o];
sort(b+i+1,b+n+1);
long long hsh=0;
for(int o=i+1;o<=n;o++){
hsh=hsh*base+b[o]+1;
}
if(mp.count(hsh)) return mp[hsh];
return mp[hsh]=dfs(i+1,i+2,wn,dr);
}
long long re=0;
if(a[i]>=3&&wn){
a[i]-=3;
re+=dfs(i,j+1,wn-1,dr);
a[i]+=3;
}
if(a[j]>=3&&wn){
a[j]-=3;
re+=dfs(i,j+1,wn-1,dr);
a[j]+=3;
}
if(a[i]>=1&&a[j]>=1&&dr){
--a[i];--a[j];
re+=dfs(i,j+1,wn,dr-1);
++a[i];++a[j];
}
return re%mod;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
int wn,dr;
wn=sum-n*(n-1);
dr=n*(n-1)/2-wn;
printf("%lld",dfs(1,2,wn,dr));
return 0;
}