• 「LuoguP1430」 序列取数(区间dp


    题目描述

    给定一个长为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
    ",(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('
    ');
    71         //输出答案
    72     }
    73     return 0;
    74 }
  • 相关阅读:
    pig安装
    [2013.10.29][Luogu OJ P1873]砍树
    [转帖]OIer之魂、
    [转帖]我们是OIer、
    10007:[2013.10.25]P1.滚土豆(potato.pas/c/cpp)
    [2013.10.18]P2.传作业 (pass.pas/c/cpp)
    [Luogu OJ P1619]解一元二次方程的烦恼
    [Luogu OJ P1433][2013.10.18]DFS基础题-吃奶酪
    高精度加减乘法小程序 Ver 0.9.5 beta
    [2013.10.11]P3.和为零
  • 原文地址:https://www.cnblogs.com/qwerta/p/9683053.html
Copyright © 2020-2023  润新知