1. 程式人生 > >2018.09.15【校內模擬】點名器(DP)

2018.09.15【校內模擬】點名器(DP)

【題意】

ssoier在緊張的學習中,杜老師每天給他們傳授精妙的知識。 杜老師為了活躍氣氛,設計了一個點名器,這個點名器包含一個長度為M的陣列(下標1開始),每個元素是一個oier的名字,每次點名的時候,點名器會等概論隨機生成一個1到M的整數,對應的人就要回答問題。當然杜老師有喜歡的人,他會故意讓一些名字重複出現以增加某些人被點中的概率。 ldx感覺不是很好,他不希望被點中。他找到杜老師,不過杜老師表示他確實喜歡ldx,不過杜老師還是想給ldx一點好處,他被點名器的陣列給ldx看,然後讓ldx對陣列進行T此修改,每次修改ldx選擇的某個位置的名字,然後把他改成其他oier的名字(一定是換成另外人的名字,不允許不修改)。ldx希望讓自己在點名器的出現次數不超過K。 請你幫幫ldx計算有多少種滿足條件的修改方案。兩個方案不同當且僅當存在i,兩方案中的第i次修改操作不同。答案對1e9+7取模。

【輸入】

為了簡化問題,為每個OIer分配一個1 到N 的整數。LDX是1號。 第一行三個整數N,M,T,K,含義如上。 接下來一行M 個整數表示陣列元素對應的選手編號。

【輸出】

輸出一行一個整數表示答案。

【樣例輸入】

2 3 1 0 2 2 2

【樣例輸出】

0

【資料】

對於20% 的資料,N,M,T <= 10。 對於50% 的資料,N,M,T<= 50。 對於70% 的資料,N,M,T<=200。 對於100% 的資料,1<=M,T<=2000; 2<=N<=10^9; 0<=K<=M。

解析:

稍微分析一下可以發現,除了1號以外,其他號碼都沒有區別,而且所有號碼的位置對問題也沒有影響。於是我們統計初始時有多少位置上為1

當你以為這是一個蛋疼的組合數學問題的時候,這極有可能是一個DP——by不知道名字的dalao

這道題就是一道DP

我選擇正推式轉移,就是用當前狀態更新能夠到達的目標狀態,而不是用能夠達到當前狀態的狀態來更新當前狀態。

於是這道題唯一有一點組合味道的地方就是這裡,加法原理和乘法原理。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register #define gc getchar #define pc putchar #define cs const cs ll mod=1000000007; inline ll getint(){ re ll num=0; re char c; while(!isdigit(c=gc())); while(isdigit(c))num=(num<<1)+(num<<3)+(c^48),c=gc(); return num; } inline void outint(ll a){ static char ch[23]; if(a==0)pc('0'); while(a)ch[++ch[0]]=(a-a/10*10)^48,a/=10; while(ch[0])pc(ch[ch[0]--]); } ll N,M,T,K; int cnt; ll f[2001][2005]; signed main(){ N=getint(); M=getint(); T=getint(); K=getint(); for(int re i=1;i<=M;++i){ int x=getint(); if(x==1)++cnt; } f[0][cnt]=1; for(int re i=0;i<T;++i){ for(int re j=0;j<=M;++j){ if(j)(f[i+1][j-1]+=f[i][j]*j%mod*(N-1)%mod)%=mod; (f[i+1][j]+=f[i][j]*(M-j)%mod*(N-2)%mod)%=mod; (f[i+1][j+1]+=f[i][j]*(M-j)%mod)%=mod; } } ll ans=0; for(int re i=0;i<=K;++i)(ans+=f[T][i])%=mod; outint(ans); return 0; }