1. 程式人生 > >一道非常傻逼的題:

一道非常傻逼的題:

總數 ace rom from 個數 ini color style isp

https://codeforces.com/gym/102007

E:

我計劃預習五個小時離散,然後hmc補了這道他自認為非常的裸並且很傻逼自己可以一眼秒的簡單題,然後給我講了講,然後我失去了一整晚的生命跡象。

首先我們可以發現一個神奇的現象,啊,先排個序,然後我們會發現,一個數 是 合法的(指左邊的全部小於等於它,右邊的全部大於等於它),當且僅當它在自己拍完序的位置上。

先不考慮很多相同的。 所以我們可以怎麽做呢。從左到右枚舉每個 合法的數,然後從左到右 枚舉 起點。這樣考慮,用dp[n]表示 從 1到 n ,n是合法的數的時候的方案數,ans[n]表示從1到n , 不合法的方案數。 c[n] 表示從 1 到 n 的 排列總數。 顯然 。。。好難描述。

唔,顯然我們這樣枚舉會有很多重復的情況對吧。 艹,我先把hmc講給我我聽懂了的復述一下,一個數是 合法的 方案數, 就是 它左邊的數xjb排和右邊的數xjb排然後乘起來吧。

好啊其實我覺得他就說了這一局有用的。

所以我們可以采用 總排列數-所有合法情況。然後合法情況會有重復的,這個時候就要進行類似容斥的操作對不對。ex: 1,2 1,2;被計算了兩次

所以我們可以 計算出 左邊 不合法的 方案數。 用不合法的 再去乘 右邊合法的 就一定不會和 之前的重復了,因為之前計算的是左邊的合法的。

那麽首先我們要知道每個子區間的排列總數,可以邊計算順便枚舉,也可以先預處理出來。我比較傻逼混在一起就神誌不清了就預處理出來的。

然後我們用 ans[i] 表示 到i 為止 的答案, dp[i]表示到 i為止 合法的 方案數。

第一層枚舉 現在的區間 ,[1,i]; 第二層枚舉 子區間, j from 1 to i ;

然後維護就好了。

emmm你要是不知道費馬小定理的話,,,我也木有辦法 也可以用遞推式求對不對。

技術分享圖片
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const ll mod = 1e9+9;
 5 const int N = 5005;
 6 ll inv[N];
 7 ll n,c[N][N],a[N];
8 ll qpow(ll a,ll x){ 9 ll res = 1; 10 while (x){ 11 if(x&1) 12 res=res*a%mod; 13 a=a*a%mod; 14 x>>=1; 15 } 16 return res; 17 } 18 void init(){ 19 inv[1]=1; 20 for(int i=2;i<=5000;i++) 21 inv[i]=qpow(i,mod-2); 22 } 23 void slove(int n){ 24 map<int,int> mp; 25 for(int i=1;i<=n;i++){ 26 c[i][i-1]=1; 27 mp.clear(); 28 for(int j=i;j<=n;j++){ 29 c[i][j]=c[i][j-1]*(j-i+1)%mod*inv[++mp[a[j]]]%mod; 30 } 31 c[i+1][i]=1; 32 } 33 } 34 ll dp[N],ans[N]; 35 int main(){ 36 init(); 37 ios::sync_with_stdio(false); 38 cin>>n; 39 for(int i=1;i<=n;i++){ 40 cin>>a[i]; 41 } 42 sort(a+1,a+1+n); 43 slove(n); 44 ans[0]=1; 45 for(int i=1;i<=n;i++){ 46 for(int j=1;j<=i;j++){ 47 dp[i]=(dp[i]+ans[j-1]*c[j+1][i])%mod; 48 } 49 //cout<<dp[i]<<‘ ‘; 50 ans[i]=(c[1][i]-dp[i]+mod)%mod; 51 //cout<<ans[i]<<endl; 52 } 53 cout<<ans[n]<<endl; 54 }
View Code

一道非常傻逼的題: