51Nod 1391 DP + Hash

题目链接


思路:首先可以将题目进行转化:

对于每一个位置i,求出其左区间0比1多的最远端点,和其右区间1比0的最远端点。
而如何刻画0和1的数量关系呢?
这是一个经典的转化思想,将0用-1替代,维护前缀和,若区间的前缀和>0,则说明该区间1比0多,<0的话0比1多

考虑DP:
D P [ i ] DP[i] DP[i] : 以i结尾,前缀和<0的最长长度

首先考虑左区间的情况,若从1–>i的前缀和<0,则: D P [ i ] = i DP[i] = i DP[i]=i (即最远端点就是起点)

若前缀和 S u m &gt; = 0 Sum &gt;= 0 Sum>=0 由前缀和的性质: S u m [ b ] − S u m [ a − 1 ] Sum[b] - Sum[a-1] Sum[b]Sum[a1]可以代表区间 [ a , b ] [a,b] [a,b] 的和
故我们找到 i i i 之前 S u m + 1 Sum+1 Sum+1 第一次出现的位置 j j j ,此时 [ j + 1 , i ] [j+1,i] [j+1,i] 一定是 &lt; 0 &lt;0 <0 的,因为 S u m Sum Sum 最大只有$1e6 $ 故可以Hash一下,记录每个大于0的值第一次出现的位置。

因为每次前缀和只能加减1 故前缀和的值一定是连续的,故如果存在前缀和 S u m + 2 Sum+2 Sum+2的区间,那么 S u m + 1 Sum + 1 Sum+1 是一定存在的,且一定在其左边更优的位置,故当前缀和大于0时,找 S u m + 1 Sum+1 Sum+1一定是最优解

右区间的解法类似左区间,换一下遍历方向和维护前缀和大于0即可。


代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 1e6 + 10;
char s[A];
int a[A],dp_1[A],dp_2[A],Hash[A];
// Hash[i] : 前缀和i第一次出现的位置
// dp_1[i] : 以i结尾,前缀和<0的最长长度
// dp_2[i] : 以i开头,后缀和>0的最长长度
// 故 Ans = max{dp_1[i] + dp_2[i+1]}  (1<=i<len)

int main(){
    int sum;
    scanf("%s",s);
    int len = strlen(s);
    for(int i=0 ;i<len ;i++){
        if(s[i] == '0') a[i+1] = -1;
        else            a[i+1] = 1;
    }

    memset(Hash,-1,sizeof(Hash));
    sum = 0;
    for(int i=1 ;i<=len ;i++){
        sum += a[i];
        if(sum < 0) dp_1[i] = i;
        else{
            if(Hash[sum+1] != -1){
                dp_1[i] = i - Hash[sum+1];
            }
            else dp_1[i] = 0;
            if(Hash[sum] == -1) Hash[sum] = i;
        }
    }

    memset(Hash,-1,sizeof(Hash));
    sum = 0;
    for(int i=len ;i>=1 ;i--){
        sum += a[i];
        if(sum>0) dp_2[i] = len - i + 1;
        else{
            if(Hash[-sum+1] != -1){
                dp_2[i] = Hash[-sum+1] - i;
            }
            else dp_2[i] = 0;
            if(Hash[-sum] == -1) Hash[-sum] = i;
        }
    }

    int ans = 0;
    for(int i=1 ;i<len ;i++){
        //printf("%d : dp_1 = %d dp_2 = %d\n",i,dp_1[i],dp_2[i]);
        if(dp_1[i] && dp_2[i+1]) ans = max(ans,dp_1[i] + dp_2[i+1]);
    }
    printf("%d\n",ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值