@description@

小 Q 和小 T 正在玩一种双人游戏。m 张木牌从左往右排成一排,第 i 张木牌上写着一个正整数 bi。小 Q 和小 T 轮流行动总计 m 轮,小 Q 先手。在每一轮中,行动方需要选择最左或者最右的一张木牌并将其拿走。游戏最后每个人的得分即为他拿走的木牌上写着的数字之和,得分较大的一方胜利。小 Q 和小 T 都是博弈老手,他们一定会按照最优策略去行动,即都希望自己的得分比对方的得分尽可能地高。

给定 n 张木牌上的数字 a1,a2,…,an,这些木牌从左往右排成一排。小 Q 和小 T 将会依次进行 q 局独立的游戏。在第 i 局游戏开始前,他们会选定两个正整数li,ri(1≤li≤ri≤n),然后将第 li 到第 ri 张木牌拿出来,在这 ri−li+1 张木牌上进行这一局游戏。每局游戏结束后,他们又会重新整理好这些木牌,把木牌按照 a1,a2,…,an 的样子恢复原状。

作为旁观者的你不免感到每局游戏有些漫长,于是你决定写一个程序来预测最终的游戏结果。

请写一个程序,对于每局游戏计算小 Q 先手且双方都采取最优策略的情况下,游戏结束时先手和后手的得分分别是多少。

input

第一行包含两个正整数 n,q,分别表示木牌的数量以及游戏的局数。

第二行包含 n 个正整数 a1,a2,…,an,依次表示每张木牌上写着的数字。

接下来 q 行,每行两个正整数 li, ri,依次描述每局游戏。

output

对于每局游戏,输出一行两个整数,即该局游戏结束时先手和后手的得分。

sample input

5 4

7 9 3 5 2

1 5

3 5

2 4

1 2

sample output

12 14

5 5

12 5

9 7

对于100%的数据,1≤li≤ri≤n≤100000,所有 ai 都在 [1,10^9] 中随机产生,q≤200000。

@solution@

这道题最简单的解法就是 O(n^2) 的区间 dp,但显然。。。

首先我们可以将问题转换为先手分数减后手分数的最大值,又可以知道先手分数与后手分数的和(区间总和),分别解出先手分数与后手分数。

然后,嗯其实它是一道结论题。

(1)如果遇到相邻的三个数 A, B, C,满足 A <= B 且 B >= C(形成山峰状)时,可以将它合成一个数 A + C - B。

为什么?题解里大致写了会儿,但我没懂其中的逻辑。我大致BB一下:

假如此时 Q 先手,Q 此时可以拿取 A 与最右端的某个数。如果 Q 此时分析得到拿取 A 比拿最右更有优势:

则 T 如果拿最右,说明最右更有优势,故 Q 一开始会拿最右。故 T 必须拿 B。

如果 Q 此时拿最右,则 Q 可以在第一时刻拿最右,如此操作就会比先拿 A 获得更大优势。

(以上全是感性证明)

(2)将原数列按照(1)中所做后,得到的新数列要么递增,要么递减,要么先递减后递增(形成山谷状)。

递增与递减是对称的,我们只需要考虑一种。可以归纳法验证先手总是先选取最大的是最优的方案。

山谷状时,两边总比中间高,故先手也总可以选取最大的数,一样归纳法验证。

故可以将新数列排序。

(3)因为数是随机生成的,所以新数列最多 O(log n) 个数(注定了这是一道神仙题)

题解里没说。我也不会证。但我会写代码验证:

  1. #include<cstdio>
  2. #include<cstdlib>
  3. #include<ctime>
  4. typedef long long ll;
  5. const int MAXN = 1E6;
  6. ll a[MAXN + 5]; int cnt = 0;
  7. int main() {
  8. ll sum = 0;
  9. for(int T=1;T<=1000;T++) {
  10. srand(time(NULL)); cnt = 0;
  11. for(int i=1;i<=MAXN;i++) {
  12. a[++cnt] = rand();
  13. while( cnt >= 3 && a[cnt-2] <= a[cnt-1] && a[cnt-1] >= a[cnt] )
  14. a[cnt-2] = a[cnt] + a[cnt-2] - a[cnt-1], cnt = cnt - 2;
  15. }
  16. //printf("%d\n", cnt);
  17. sum += cnt;
  18. }
  19. printf("avg : %lf\n", 1.0*sum/1000);
  20. }

实现上,可以使用分治,每次处理跨越 mid 的区间。没有跨越 mid 的区间分成左右两个子问题。

跨越 mid 的区间 [l, r] 拆成 [l, mid-1] 与 [mid, r]。预处理 [1...mid-1, mid-1] 的新数列,然后从 mid 开始向右扫描并维护。

如果遇到询问,就暴力合并。

所以这道题肯定是离线搞啊怎么可能在线。

@accepted code@

  1. #include<cstdio>
  2. #include<vector>
  3. #include<algorithm>
  4. using namespace std;
  5. typedef long long ll;
  6. const int MAXN = 200000 + 5;
  7. const int MAXQ = 200000 + 5;
  8. struct query{
  9. int l, r, n;
  10. query(int _l=0, int _r=0, int _n=0):l(_l), r(_r), n(_n){}
  11. }qry[MAXQ], tmp[MAXQ];
  12. int l[MAXQ], r[MAXQ]; ll ans[MAXQ];
  13. ll a[MAXN], s[MAXN];
  14. vector<ll>vec[MAXN], tmp2, tmp3;
  15. vector<query>Q[MAXN];
  16. void insert(vector<ll>&v, ll x) {
  17. v.push_back(x);
  18. while( v.size() >= 3 && v[v.size()-2] >= v[v.size()-1] && v[v.size()-2] >= v[v.size()-3] ) {
  19. ll x = v[v.size()-1] + v[v.size()-3] - v[v.size()-2];
  20. v.pop_back(), v.pop_back(), v.pop_back(), v.push_back(x);
  21. }
  22. }
  23. void solve(int L, int R, int le, int ri) {
  24. // printf("%d %d %d %d\n", L, R, le, ri);
  25. if( L > R || le > ri ) return ;
  26. int mid = (L + R) >> 1, p = le, q = ri;
  27. for(int i=mid;i<=R;i++)
  28. Q[i].clear();
  29. for(int i=le;i<=ri;i++)
  30. if( qry[i].r < mid ) tmp[p++] = qry[i];
  31. else if( qry[i].l > mid ) tmp[q--] = qry[i];
  32. else Q[qry[i].r].push_back(qry[i]);
  33. vec[mid].clear();
  34. for(int i=mid-1;i>=L;i--)
  35. vec[i] = vec[i+1], insert(vec[i], a[i]);
  36. for(int i=L;i<mid;i++)
  37. reverse(vec[i].begin(), vec[i].end());
  38. tmp2.clear();
  39. for(int i=mid;i<=R;i++) {
  40. insert(tmp2, a[i]);
  41. for(int j=0;j<Q[i].size();j++) {
  42. tmp3 = vec[Q[i][j].l];
  43. for(int k=0;k<tmp2.size();k++)
  44. insert(tmp3, tmp2[k]);
  45. sort(tmp3.begin(), tmp3.end());
  46. for(int k=tmp3.size()-1,f=1;k>=0;k--,f*=-1)
  47. ans[Q[i][j].n] += tmp3[k]*f;
  48. }
  49. }
  50. for(int i=le;i<=ri;i++)
  51. qry[i] = tmp[i];
  52. solve(L, mid - 1, le, p - 1);
  53. solve(mid + 1, R, q + 1, ri);
  54. }
  55. int main() {
  56. int n, q; scanf("%d%d", &n, &q);
  57. for(int i=1;i<=n;i++) {
  58. scanf("%lld", &a[i]);
  59. s[i] = s[i-1] + a[i];
  60. }
  61. for(int i=1;i<=q;i++) {
  62. scanf("%d%d", &l[i], &r[i]);
  63. qry[i] = query(l[i], r[i], i);
  64. }
  65. solve(1, n, 1, q);
  66. for(int i=1;i<=q;i++)
  67. printf("%lld %lld\n", (s[r[i]]-s[l[i]-1]+ans[i])/2, (s[r[i]]-s[l[i]-1]-ans[i])/2);
  68. }

@details@

康复计划 - 5。

推导一晚上,代码一小时。

神仙题与只给结论不带(详细)证明就跑的题解。

@noi.ac - 490@ game的更多相关文章

  1. # NOI.AC省选赛 第五场T1 子集,与&最大值

    NOI.AC省选赛 第五场T1 A. Mas的童年 题目链接 http://noi.ac/problem/309 思路 0x00 \(n^2\)的暴力挺简单的. ans=max(ans,xor[j-1 ...

  2. NOI.ac #31 MST DP、哈希

    题目传送门:http://noi.ac/problem/31 一道思路好题考虑模拟$Kruskal$的加边方式,然后能够发现非最小生成树边只能在一个已经由边权更小的边连成的连通块中,而树边一定会让两个 ...

  3. NOI.AC NOIP模拟赛 第五场 游记

    NOI.AC NOIP模拟赛 第五场 游记 count 题目大意: 长度为\(n+1(n\le10^5)\)的序列\(A\),其中的每个数都是不大于\(n\)的正整数,且\(n\)以内每个正整数至少出 ...

  4. NOI.AC NOIP模拟赛 第六场 游记

    NOI.AC NOIP模拟赛 第六场 游记 queen 题目大意: 在一个\(n\times n(n\le10^5)\)的棋盘上,放有\(m(m\le10^5)\)个皇后,其中每一个皇后都可以向上.下 ...

  5. NOI.AC NOIP模拟赛 第二场 补记

    NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...

  6. NOI.AC NOIP模拟赛 第一场 补记

    NOI.AC NOIP模拟赛 第一场 补记 candy 题目大意: 有两个超市,每个超市有\(n(n\le10^5)\)个糖,每个糖\(W\)元.每颗糖有一个愉悦度,其中,第一家商店中的第\(i\)颗 ...

  7. NOI.AC NOIP模拟赛 第四场 补记

    NOI.AC NOIP模拟赛 第四场 补记 子图 题目大意: 一张\(n(n\le5\times10^5)\)个点,\(m(m\le5\times10^5)\)条边的无向图.删去第\(i\)条边需要\ ...

  8. NOI.AC NOIP模拟赛 第三场 补记

    NOI.AC NOIP模拟赛 第三场 补记 列队 题目大意: 给定一个\(n\times m(n,m\le1000)\)的矩阵,每个格子上有一个数\(w_{i,j}\).保证\(w_{i,j}\)互不 ...

  9. NOI.AC WC模拟赛

    4C(容斥) http://noi.ac/contest/56/problem/25 同时交换一行或一列对答案显然没有影响,于是将行列均从大到小排序,每次处理限制相同的一段行列(呈一个L形). 问题变 ...

随机推荐

  1. 【流水调度问题】【邻项交换对比】【Johnson法则】洛谷P1080国王游戏/P1248加工生产调度/P2123皇后游戏/P1541爬山

    前提说明,因为我比较菜,关于理论性的证明大部分是搬来其他大佬的,相应地方有注明. 我自己写的部分换颜色来便于区分. 邻项交换对比是求一定条件下的最优排序的思想(个人理解).这部分最近做了一些题,就一起 ...

  2. jsp必填项加红色星号

    <th><font color=red>*</font>文字:</th>

  3. hdu 1296 Polynomial Problem(多项式模拟)

    Problem Description We have learned how to obtain the value of a polynomial when we were a middle sc ...

  4. 洛谷P2903 [USACO08MAR]麻烦的干草打包机The Loathesome Hay Baler

    P2903 [USACO08MAR]麻烦的干草打包机The Loathesome Hay Baler 题目描述 Farmer John has purchased the world's most l ...

  5. deque简单解析

    deque是支持双端插入删除的容器,oi中用来维护单调队列 声明方式 deque<int> d1;//声明一个叫d1的双向队列 deque<int> d2(d1);//声明一个 ...

  6. Vue--使用watch、computed、filter方法来监控

    watch与computed.filter: watch:监控已有属性,一旦属性发生了改变就去自动调用对应的方法 computed:监控已有的属性,一旦属性的依赖发生了改变,就去自动调用对应的方法 f ...

  7. selenium(5):常用的8种元素定位

    selenium的webdriver提供了18种(注意不是8种)的元素定位方法,比较常用的定位方法是如下8种,xpath和css定位更加灵活,需要重点掌握其中一个. 经常会用到的8种定位:1.id定位 ...

  8. Linux iptables开放特定端口

    如果系统已安装则调过安装步骤 查找安装包 yum list | grep iptables 安装iptables yum install iptables-services 重启防火墙使配置文件生效 ...

  9. 安装docker报错问题

    安装docker容易出现错误的几种情况: 1.网络问题,无法下载完成的docker容器 2.linux内核版本必须是3.10及以上 3.可以选择使用aliyun的yum源,更好用 4.

  10. js中字符串的加密base64

    base64编码主要用在传输,存储表示二进制的领域,还可以进行加密和解密.其实就是字符串的编码和解码 btoa与atob 只能加密ascii,不能加密汉字. var str = 'I LOVE YOU ...