1. 程式人生 > >2018 ACM-ICPC 徐州邀請賽 B.Array 滾動dp+字首和+離線處理

2018 ACM-ICPC 徐州邀請賽 B.Array 滾動dp+字首和+離線處理

連結:

題意:

多組樣例,每組給一個1->n的陣列,問全排列方案中,有多少種方案會使得滿足i<j且ai>aj的實數對(i,j)的個數%1e9+7後為k

資料規模:

0<樣例數,n,k<=5000
記憶體限制:65536K
時間限制:1000ms

思路:

大佬說,遇事不決先打表,首先暴力打個表,ans陣列存方案數,下標為k。

#include <bits/stdc++.h>
using namespace std;
int ans[500];
int main(){
    for(int n = 1;n <= 10;n++){
        memset
(ans,0,sizeof(ans)); int num[n]; int maxm = 0; for(int i = 0;i < n;i++)num[i] = i+1; do{ int sum = 0; for(int i = 0;i < n;i++) for(int j = i+1;j < n;j++) if(num[i] > num[j])sum++; ans[sum]++; maxm = max(maxm,sum); }while
(next_permutation(num,num+n)); for(int i = 0;i <= maxm;i++) cout << ans[i] << " "; cout << endl; } return 0; }

打表結果:
打表結果
每個n一行,每列代表一個k(從0開始數)

選第八行丟到oeis裡,分析說是Weyl group的第八行。。隨後wiki查了半天沒搞懂這個group

目測找規律,從前幾列看出大概滿足a[i][j] = a[i-1][j] + a[i][j-1],可是第五行的22不滿足這個規律。

如果n時每個k的方案數已知(知道某一行),(n+1)時(下一行)逆序方案數可以看作n的全排列中新增(n+1)這個數,位置不定,所以找到能產生k對逆序數的排列:

將n插入到1,2,3……n-1中,如果插入到最左端,將產生新的n-1對逆序對,如果插入到n-1之後,將產生0對新的逆序對,所以當序列長度為n時,想要k對逆序對的排列方案數,只需在n-1時找到逆序對介於[k-n+1,k]的排列數即可。
即:a[i][j] = a[i-1][j-i+1] + a[i-1][j-i+2] + … + a[i-1][j]

如果j-i+1<0,從0開始處理。由於涉及到求陣列區間和,故採用字首和陣列儲存。

5000x5000的int所需記憶體約為95.36MB,大於限制記憶體,故動態規劃需要採用滾動陣列形式,由於每一行只由它的上一行得來,故滾動陣列開兩行即可。

由於多組樣例,n可能不按順序,為避免多次從頭開始dp,採用離線處理,讀取完全部詢問之後根據n排序,n從小到大,從1到5000跑一遍,得出全部詢問的結果後按序號順序輸出。

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int mod = (int)1e9+7;
int num[2][5010];
int ans[5010];
struct node{
    int id,n,k;
}p[5010];
bool cmp(const node &a,const node &b){
    if(a.n == b.n)return a.k < b.k;
    return a.n < b.n;
}
int main(){
    int t = 0;
    while(~scanf("%d %d",&p[t].n,&p[t].k)){
        p[t].id = t;
        t++;
    }
    sort(p,p+t,cmp);
    int now = 0;
    num[1][0] = num[1][1] = 1;
    while(1 == p[now].n){
        if(p[now].k == 0)ans[p[now].id] = 1;
        else ans[p[now].id] = 0;
        now++;
    }
    for(int i = 2;i <= 5000;i++){
        memset(num[i&1],0,sizeof(num[i&1]));
        num[i&1][0] = 1;
        int sum = (i-1)*i/2;
        for(int j = 1;j <= 5005 && j <= sum;j++){
            if(j+1 <= i)num[i&1][j] = num[(i-1)&1][j];
            else num[i&1][j] = num[(i-1)&1][j] - num[(i-1)&1][j-i];
            if(num[i&1][j] < 0)num[i&1][j] += mod;
            else if(num[i&1][j] >= mod)num[i&1][j] %= mod;
        }
        while(i == p[now].n){
            ans[p[now].id] = num[i&1][p[now].k];
            now++;
        }
        for(int j = 1;j <= 5005;j++){
            num[i&1][j] += num[i&1][j-1];
            if(num[i&1][j] >= mod)num[i&1][j] %= mod;
        }
    }
    for(int i = 0;i < t;i++)
    printf("%d\n",ans[i]);
    return 0;
}