「LuoguP1430」 序列取数

题目描述

给定一个长为n的整数序列(n<=1000),由A和B轮流取数(A先取)。每个人可从序列的左端或右端取若干个数(至少一个),但不能两端都取。所有数都被取走后,两人分别统计所取数的和作为各自的得分。假设A和B都足够聪明,都使自己得分尽量高,求A的最终得分。

输入输出格式

输入格式:

第一行,一个正整数T,表示有T组数据。(T<=100)

接着T行,每行第一个数为n,接着n个整数表示给定的序列.

输出格式:

输出T行,每行一个整数,表示A的得分

输入输出样例

输入样例#1:
复制

2
1 -1
2 1 2

输出样例#1: 复制

-1
3

说明

时限:3s


题解

首先让我们试试暴力思路:

设A的得分为VA,B的得分为VB

那么在(VA-VB)取得最大值时,有VA最大。

证明:VA-VB=VA-(Sum-VA)=2*VA-Sum

设F[L][R]为当前还剩[L][R]时,(先手得分-后手得分)的最大值。



若当前正在处理F[L][R],那么存在三种选择方案:

 1.全取。

  F[L][R]=∑{ v[i]  |  L < = i < = R }

 2.从左边取一些。(保留L‘到R)

  F[L][R]=∑{ v[i] | L <= i <= L‘-1 }-F[L‘][R]

 3.从右边取一些。(保留L到R‘)

  F[L][R]=∑{ v[i] | R‘+1 <= i <= R }-F[L][R‘]

   关于减去F[L‘][R]:

   当区间转换到[L‘,R]时的F值,为转换后的先手-后手,也就是当前意义下的后手-先手,

   所以-(后手-先手)=先手-后手。

这样,我们从小到大枚举区间,每次在这三种决策中取一种最优决策,

最后的max(VA-VB)=F[1][n],

   max(VA)=(F[1][n]+Sum)/2。

于是我们可以很较为轻松的写出O(n^3)的暴力啦!

 1 /*
 2     qwerta
 3     P1430 序列取数
 4     Unaccepted
 5     40
 6     代码 C++,0.94KB
 7     提交时间 2018-09-19 18:57:13
 8     耗时/内存
 9     19351ms, 4628KB
10 */
11 #include<cmath>
12 #include<cstdio>
13 #include<cstring>
14 #include<iostream>
15 using namespace std;
16 #define R register
17 inline int read()
18 {
19     char ch=getchar();
20     int x=0;bool s=1;
21     while(!isdigit(ch)){if(ch==‘-‘)s=0;ch=getchar();}
22     while(isdigit(ch)){x=x*10+ch-‘0‘;ch=getchar();}
23     return s?x:-x;
24 }
25 int s[1007];
26 int f[1007][1007];
27 //int m[1007][1007];
28 int main()
29 {
30     //freopen("a.in","r",stdin);
31     int t=read();
32     while(t--)
33     {
34         int n=read();
35         for(R int i=1;i<=n;++i)
36         s[i]=s[i-1]+read();
37         for(R int len=1;len<=n;++len)
38         for(R int l=1;l+len-1<=n;++l)
39         {
40
41             int r=l+len-1;
42             f[l][r]=s[r]-s[l-1];
43             for(R int _l=l+1;_l<=r;++_l)
44             f[l][r]=max(f[l][r],s[_l-1]-s[l-1]-f[_l][r]);
45             for(R int _r=r-1;_r>=l;--_r)
46             f[l][r]=max(f[l][r],s[r]-s[_r]-f[l][_r]);
47
48         }
49         printf("%d\n",(f[1][n]+s[n])>>1);
50     }
51     return 0;
52 }

但是想要通过1e3的数据,O(n^3*t)的时间复杂度肯定是布星的。

所以我们还需要一点儿优化。

考虑状态数是n^2的,雷打不动,所以我们要将贼手伸向状态转移。

以从右取为例:

F[L][R]=max(S[R]-S[R‘]-F[L][R‘]) //(L <= R‘ <= R-1)

 =max(S[R]-(S[R‘]+F[L][R‘]))

如果我们能知道R‘在L到R-1上时,S[R‘]+F[L][R‘]的最小值,那么就能O(1)转移了。

Min[L][R]=min{S[R‘]+F[L][R‘] | L <= R‘ <= R }

那么有

Min[L][R-1]=min{S[R‘]+F[L][R‘] | L <= R‘ <= R-1 }

也就是说

Min[L][R]=min(Min[L][R-1],S[R]+F[L][R])

这样,在循环枚举区间的同时顺便维护一下Min[L][R],就可以实现O(1)转移了。

其实就是把暴力中间那两句话换了种说法而已。

 1 /*
 2     qwerta
 3     P1430 序列取数
 4     Accepted
 5     100
 6     代码 C++,1.63KB
 7     提交时间 2018-09-19 20:05:27
 8     耗时/内存
 9     4910ms, 12444KB
10 */
11 #include<cmath>
12 #include<cstdio>
13 #include<cstring>
14 #include<iostream>
15 using namespace std;
16 #define R register
17 inline int read()
18 {
19     char ch=getchar();
20     int x=0;bool s=1;
21     while(!isdigit(ch)){if(ch==‘-‘)s=0;ch=getchar();}
22     while(isdigit(ch)){x=x*10+ch-‘0‘;ch=getchar();}
23     return s?x:-x;
24 }//快读(这题略卡常)
25 int s[1007];
26 int f[1007][1007];
27 int ml[1007][1007];
28 int mr[1007][1007];
29 //设ml为固定L端时的min值,mr为固定R端时的max值
30 void write(int x)
31 {
32     if(x>9)write(x/10);
33     putchar(x%10+‘0‘);
34     return;
35 }//快写
36 int main()
37 {
38     //freopen("a.in","r",stdin);
39     int t=read();
40     while(t--)
41     {
42         int n=read();
43         for(R int i=1;i<=n;++i)
44         s[i]=s[i-1]+read();//前缀和
45         for(R int l=1;l<=n;++l)
46         {
47             f[l][l]=s[l]-s[l-1];
48             ml[l][l]=s[l]+f[l][l];
49             mr[l][l]=s[l-1]-f[l][l];
50         }//预处理
51         for(R int len=2;len<=n;++len)
52         for(R int l=1,r=len;r<=n;++l,++r)//枚举区间
53         {
54             f[l][r]=s[r]-s[l-1];
55             //mr
56             //for(R int _l=l+1;_l<=r;++_l)
57             //f[l][r]=max(f[l][r],s[_l-1]-s[l-1]-f[_l][r]);
58             f[l][r]=max(f[l][r],mr[l+1][r]-s[l-1]);
59             //ml
60             //for(R int _r=r-1;_r>=l;--_r)
61             //f[l][r]=max(f[l][r],s[r]-s[_r]-f[l][_r]);
62             f[l][r]=max(f[l][r],s[r]-ml[l][r-1]);
63             //
64             ml[l][r]=min(ml[l][r-1],s[r]+f[l][r]);
65             mr[l][r]=max(mr[l+1][r],s[l-1]-f[l][r]);
66         }
67         int x=(f[1][n]+s[n])>>1;
68         if(x<0){putchar(‘-‘);write(-x);}
69         else write(x);
70         putchar(‘\n‘);
71         //输出答案
72     }
73     return 0;
74 }

原文地址:https://www.cnblogs.com/qwerta/p/9683053.html

时间: 2024-08-02 05:52:51

「LuoguP1430」 序列取数的相关文章

LiberOJ #6007. 「网络流 24 题」方格取数 最小割 最大点权独立集 最大流

#6007. 「网络流 24 题」方格取数 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 在一个有 m×n m \times nm×n 个方格的棋盘中,每个方格中有一个正整数. 现要从方格中取数,使任意 2 22 个数所在方格没有公共边,且取出的数的总和最大.试设计一个满足要求的取数算法. 输入格式 文件第 1 11 行有 2 22 个正整数 m mm 和 n nn,分别表示棋盘的行数和列数

AC日记——「SDOI2017」序列计数 LibreOJ 2002

「SDOI2017」序列计数 思路: 矩阵快速幂: 代码: #include <bits/stdc++.h> using namespace std; #define mod 20170408 #define ll long long struct MatrixType { int n,m; ll ai[105][105]; void mem(int n_,int m_) { n=n_,m=m_; for(int i=0;i<=n;i++) for(int v=0;v<=m;v++

LibreOJ #2002. 「SDOI2017」序列计数

二次联通门 : LibreOJ #2002. 「SDOI2017」序列计数 /* LibreOJ #2002. 「SDOI2017」序列计数 线性筛 + 矩阵优化dp 先构造出全部情况的矩阵 用矩阵快速幂计算答案 再构造出全不是质数的矩阵 计算出答案 前一个答案减后一个答案即可 */ #include <cstdio> #include <iostream> #include <cstring> const int BUF = 12312312; char Buf[BU

「JSOI2014」序列维护

「JSOI2014」序列维护 传送门 其实这题就是luogu的模板线段树2,之所以要发题解就是因为被 \(\color{black}{\text{M}} \color{red}{\text{_sea}}\) 告知了一种比较NB的 \(\text{update}\) 的方式. 我们可以把修改操作统一化,视为 \(ax + b\) 的形式,然后我们按照原来的套路来维护两个标记,分别代表 \(a\) 和 \(b\) ,那么我们的更新就可以这么写: inline void f(int p, int at

洛谷 P1430 序列取数

如果按照http://www.cnblogs.com/hehe54321/p/loj-1031.html的$O(n^3)$做法去做的话是会T掉的,但是实际上那个做法有优化的空间. 所有操作可以分解为由两步组成的操作:第一步是在数列的某一端取一个数并加到自己的得分上,第二步是把下一步操作的权利给自己或对方.如果这次操作的前一次是对方的操作,那么在左端或右端取数没有限制:如果这次操作的前一次是自己的操作,那么必须与上一次在相同的一端操作. 令ans[l][r][0/1/2]表示l到r的子序列,上一次

「网络流 24 题」方格取数

大意: 给定$n*m$棋盘, 每个格子有权值, 不能选择相邻格子, 求能选出的最大权值. 二分图带权最大独立集, 转化为最小割问题. S与$X$连边权为权值的边, $X$与$Y$之间连$INF$, $Y$与$T$连边权为权值的边. 则最大权值为总权值-最小割. 残量网络中与$S$相连的或与$T$相连的表示选择, 否则表示不选. #include <iostream> #include <sstream> #include <algorithm> #include <

loj#2002. 「SDOI2017」序列计数(dp 矩阵乘法)

题意 题目链接 Sol 质数的限制并没有什么卵用,直接容斥一下:答案 = 忽略质数总的方案 - 没有质数的方案 那么直接dp,设\(f[i][j]\)表示到第i个位置,当前和为j的方案数 \(f[i + 1][(j + k) \% p] += f[i][j]\) 矩乘优化一下. #include<bits/stdc++.h> #define LL long long using namespace std; const int MAXN = 2e7 + 10, mod = 20170408,

「SDOI2015」序列统计

题目链接:Click here Solution: 容易得到这样一个\(dp\),设\(f[i][j]\)表示已经选了\(i\)个数,乘积\(mod \,\,m\)后为\(j\)的方案 \[ f[2\times i][j]=\sum_{a\times b\equiv j\,\,(mod\,\, m)} f[i][a]\times f[i][b] \] 考虑如何优化这个转移方程,注意到这道题的模数为\(1004535809\),这是一个很大的提示,考虑\(NTT\) 但是我们知道\(NTT\)处理

loj2051 「HNOI2016」序列

ref #include <algorithm> #include <iostream> #include <cstdio> #include <cmath> using namespace std; typedef long long ll; int n, q, a[100005], l[100005], r[100005], sta[100005], din, blc, bel[100005], st[100005][19], mii[17], mlg[