2015 ACM Amman Collegiate Programming Contest L.Alternating StringsII

本文介绍了一种利用动态规划(DP)解决特定字符串割分问题的方法,并结合线段树进行优化,以实现高效的求解过程。文章详细解释了如何通过DP找到最优割分策略,以及使用线段树来加速状态转移的过程。


题目大意:

给你长度为N的一个01字符串,要求我们将其割分成若干个连续子序列,使得每个子序列的长度都不超过K,而且保证每个子序列,要么是单独的一个数,要么是非01间差排列的子序列(01间差排列:1010,010101,.....................)。


思路:


我们通过做这套题的D题的时候不难发现,对于可以割开的字符串,其具有单调性,显然字符串越长,越可能作为割分的子序列(就是非01间差排列的字符串).


我们设定Dp【i】,表示以i作为结尾的时候,需要割分的最小次数,那么显然有:

①Dp【i】=min(Dp【j】+1,Dp【i】);需要保证从j+1到位子i的排列是可行方案。

②Dp【i】=min(Dp【i-1】+1,Dp【i】);


考虑到①中,可行方案的位子具有单调性,所以我们二分一个点,使得这个点是从右往左扫,第一个构成可行方案的位子。

那么我们有:Dp【i】=min(Dp【i】,Dp【j】)【max(i-k,0)<=j<=pos】;

然后我们套个线段树加速转移即可。


时间复杂度O(nlogn)


Ac代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int INF=0x3f3f3f3f;
int sum[105550*5],lazy[105550*5];

void pushup(int rt){
    sum[rt]=min(sum[rt<<1],sum[rt<<1|1]);
}

void pushdown(int rt){
    if(lazy[rt]){
        lazy[rt<<1]+=lazy[rt];
        lazy[rt<<1|1]+=lazy[rt];
        sum[rt<<1]+=lazy[rt];
        sum[rt<<1|1]+=lazy[rt];
        lazy[rt]=0;
    }
}

void build(int rt,int l,int r){
    lazy[rt]=sum[rt]=0;
    if(l==r) {sum[rt]=0;return ;}
    int m = r+l >> 1;
    build(rt<<1  ,l  ,m);
    build(rt<<1|1,m+1,r);
    pushup(rt);
}

void update(int rt,int l,int r,int L,int R,int v){
    if(L<=l&&r<=R){
        lazy[rt]+=v;
        sum[rt]+=v;
        return ;
    }
    pushdown(rt);
    int m = r+l >> 1;
    if(L<=m) update(rt<<1  ,l  ,m,L,R,v);
    if(R> m) update(rt<<1|1,m+1,r,L,R,v);
    pushup(rt);
}
int query(int rt,int l,int r,int L,int R){
    if(L<=l&&r<=R) return sum[rt];
    pushdown(rt);
    int m = r+l >> 1,ans = INF;
    if(L<=m) ans = min(ans,query(rt<<1  ,l  ,m,L,R));
    if(R> m) ans = min(ans,query(rt<<1|1,m+1,r,L,R));
    pushup(rt);
    return ans;
}
int temp[150000];
int dp[150000];
char a[150000];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,k;
        scanf("%d%d",&n,&k);
        scanf("%s",a+1);
        dp[0]=0;
        memset(temp,0,sizeof(temp));
        for(int i=1;i<=n;i++)dp[i]=0x3f3f3f3f;
        for(int i=n;i>=1;i--)
        {
            int cnt=0;
            if(a[i]==a[i+1])cnt=1;
            if(i==n)temp[i]=0;
            else temp[i]=temp[i+1]+cnt;
        }
        temp[0]=temp[1];
        build(1,0,n);
        for(int i=1;i<=n;i++)
        {
            dp[i]=min(dp[i],dp[i-1]+1);
            int l=max(i-k+1-1,0);
            int r=i-1;
            int pos=-1;
            while(r-l>=0)
            {
                int mid=(l+r)/2;
                if(temp[mid+1]-temp[i]>0)
                {
                    pos=mid;
                    l=mid+1;
                }
                else r=mid-1;
            }
            if(pos!=-1)dp[i]=min(dp[i],query(1,0,n,max(i-k,0),pos)+1);
            update(1,0,n,i,i,dp[i]);
        }
        printf("%d\n",dp[n]-1);
    }
}










评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值