题目来源:http://codeforces.com/gym/100712/attachments
dp+线段树维护。
f[i]表示[1,i]区间内最少要切多少次。f[i]=min(f[j])+1,(i-k=<j<=i-1,[j+1,i]满足条件)。
j若从i-1开始至i-k循环,若出现"00"或"11"的情况则开始更新。可以用前缀和sum[i]来维护[1,i]中出现"00"或"11"的次数。
这样找寻满足题意的j的区间可用二分查找。
min(f[j])可以用线段树维护,复杂度降为(logn)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+10;
int n,k;
char s[maxn];
int sum[maxn];
ll f[maxn];
struct tree{
int left,right;
ll minv;
}c[maxn<<2];
void build(int id,int l,int r)
{
c[id].left=l;c[id].right=r;
if(l==r)return;
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
c[id].minv=min(c[id<<1].minv,c[id<<1|1].minv);
}
ll query_min(int id,int l,int r)
{
if(c[id].left==l&&c[id].right==r)return c[id].minv;
int mid=(c[id].left+c[id].right)>>1;
if(r<=mid)return query_min(id<<1,l,r);
else if(l>mid)return query_min(id<<1|1,l,r);
else return min(query_min(id<<1,l,mid),query_min(id<<1|1,mid+1,r));
}
void update(int id,int pos,ll v)
{
if(c[id].left==c[id].right)c[id].minv=v;
else
{
int mid=(c[id].left+c[id].right)>>1;
if(pos<=mid)update(id<<1,pos,v);
else update(id<<1|1,pos,v);
c[id].minv=min(c[id<<1].minv,c[id<<1|1].minv);
}
}
int main()
{
//ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _;scanf("%d",&_);
while(_--)
{
memset(f,63,sizeof(f));
scanf("%d%d",&n,&k);
scanf("%s",s+1);
for(int i=1;i<=n*4;++i)c[i].minv=1e18;
build(1,1,n);
for(int i=1;i<=n;++i)
{
sum[i]=sum[i-1];
if(s[i]==s[i-1])sum[i]++;
}
// for(int i=1;i<=n;++i)cout<<sum[i];cout<<endl;
f[0]=0;
for(int i=1;i<=n;++i)
{
if(i!=1)f[i]=f[i-1]+1;
else f[i]=0;
int l=0,r=i-1;
while(l<r)
{
int mid=(l+r+1)/2;
if(sum[mid]!=sum[i])l=mid;
else r=mid-1;
}
// cout<<l;
if(i<=k)
{
if(sum[i])f[i]=0;
update(1,i,f[i]);
continue;
}
if(l>i-k)
{
ll t=query_min(1,i-k,l-1);
f[i]=t+1;
}
update(1,i,f[i]);
}
// cout<<endl;
// for(int i=1;i<=n;++i)cout<<f[i];cout<<endl;
printf("%lld\n",f[n]);
}
return 0;
}