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。
解析:
稍微分析一下可以發現,除了號以外,其他號碼都沒有區別,而且所有號碼的位置對問題也沒有影響。於是我們統計初始時有多少位置上為。
當你以為這是一個蛋疼的組合數學問題的時候,這極有可能是一個——不知道名字的dalao
這道題就是一道。
我選擇正推式轉移,就是用當前狀態更新能夠到達的目標狀態,而不是用能夠達到當前狀態的狀態來更新當前狀態。
於是這道題唯一有一點組合味道的地方就是這裡,加法原理和乘法原理。
程式碼:
#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;
}