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;
}