天天看點

51Nod1231 記分牌 動态規劃

​​題目傳送門 - 51Nod1231​​題意

51Nod1231 記分牌 動态規劃

題解

  顯然是一個競賽圖相關的題。

  我們首先證明一個結論:

    一個出度序列存在對應的 $n$ 個點的競賽圖的充分必要條件是:這個出度序列的所有元素之和為 $\cfrac{n(n-1)}{2}$ ,且 對于這個出度序列中任意 $k$ 個元素,滿足他們的和 $\geq \cfrac{k(k-1)}{2}$ 。

  由于我懶得寫證明(證明需要用構造法,自行百度),這個結論的證明略去。

  于是我們隻需要保證最終的出度序列的總和為 $\cfrac{n(n-1)}{2}$ ,并且将其排序後,對于所有 $k \in [1,n]$ ,前 $k$ 個元素之和 $\geq \cfrac{k(k-1)}2$ 即可。

  我們按照數值從小到大填。

  我們令 $dp[i][j][k]$ 表示前 $i$ 個數,目前最後一個數為 $j-1$ ,前 $i$ 個數的總和為 $k$ 的方案總數。然後大力 DP 即可。

  dp 複雜度的上限是 $O(n^5)$ 的,但是由于有很多無用的狀态,是以 20 組資料仍然可以跑過去。

代碼

#include <bits/stdc++.h>
using namespace std;
const int N=45,mod=1e9+7;
int T,n,a[N],C[N][N],dp[N][N][N*N],cnt[N],tot[N];
int calc(int x){
  return x*(x-1)/2;
}
void add(int &x,int y){
  x+=y;
  if (x>=mod)
    x-=mod;
}
int solve(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
  sort(a+1,a+n+1);
  memset(cnt,0,sizeof cnt);
  for (int i=1;i<=n;i++)
    if (a[i]>=0)
      cnt[a[i]]++;
  memset(tot,0,sizeof tot);
  for (int i=n;i>=0;i--)
    tot[i]=tot[i+1]+cnt[i];
  memset(dp,0,sizeof dp);
  dp[0][0][0]=1;
  for (int i=0;i<n;i++)
    for (int j=0;j<n;j++)
      for (int k=0;k<=calc(n);k++){
        int v=dp[i][j][k];
        if (!v)
          continue;
        for (int t=0;i+t<=n-tot[j+1];t++){
          int _k=k+t*j;
          if (_k>calc(n)||calc(i+t)>_k)
            break;
          if (t<cnt[j])
            continue;
          add(dp[i+t][j+1][_k],1LL*v*C[n-i-tot[j]][t-cnt[j]]%mod);
        }
      }
  int ans=0;
  for (int i=0;i<=n;i++)
    add(ans,dp[n][i][calc(n)]);
  return ans;
}
int main(){
  for (int i=0;i<N;i++)
    C[i][0]=C[i][i]=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;
  scanf("%d",&T);
  while (T--)
    printf("%d\n",solve());
  return 0;
}
/*
dp[i][j][k]表示前 i 個數,最後一個數是 j , 所有數的總和為 k 的序列總數
dp[i+t][j+1][k+t*j]+=C[n-i][t]*dp[i][j][k]
  j<n,i+t<=n
  k+t*j<=n*(n-1)/2
  forall t' in [0,t]  , (i+t')*(i+t'-1)/2<=k+t*j
*/