我是靠谱客的博主 合适白昼,这篇文章主要介绍2018 ACM-ICPC 徐州邀请赛 B.Array 滚动dp+前缀和+离线处理,现在分享给大家,希望可以做个参考。

链接:

Array

题意:

多组样例,每组给一个1->n的数组,问全排列方案中,有多少种方案会使得满足i<j且ai>aj的实数对(i,j)的个数%1e9+7后为k

数据规模:

0<样例数,n,k<=5000
内存限制:65536K
时间限制:1000ms
思路:

大佬说,遇事不决先打表,首先暴力打个表,ans数组存方案数,下标为k。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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跑一遍,得出全部询问的结果后按序号顺序输出。

代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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("%dn",ans[i]); return 0; }

最后

以上就是合适白昼最近收集整理的关于2018 ACM-ICPC 徐州邀请赛 B.Array 滚动dp+前缀和+离线处理的全部内容,更多相关2018内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(77)

评论列表共有 0 条评论

立即
投稿
返回
顶部