101
Sample Output
2
解法:(find)我们记录以i结尾的且是交替串的最长长度为pre,可以看到在计算f[i]时,有以下几种情况:
1.s[i] == s[i - 1] 这时候没有以i结尾交替出现的串,重置pre=1,f[i] = min{f[i - k], ..., f[i - 1]} + 1 }
2.s[i] != s[i - 1] 这时候pre++,由于最后一个与前一个形成交替串,所以需要比较pre与k的关系:
a. 若pre >= k 或 pre == i,着说明连续出现的交替串长度>k,或者前i个全是01串,那么此时f[i]只能由f[i - 1]转移(相当于在i-1与i之间切一刀)
b.否则的话pre + 1这个串([i - pre - 1, i])一定不是交替串,那么此时[i - k, i - pre - 1]这个区间的所有点都可以转移到i(最后pre+1个不是交替串,那么最后pre+1+x个一定也不是),所以有f[i] = min{f[i - k], ..., f[i - pre - 1]} + 1,所以可以使用线段树查询区间最小值,复杂度NlogN
总结:对于10^5一般对应于NlogN的算法。对于区间查询类的dp,我们可以考虑用线段树来优化。线段树的id必须从1开始,区间可以从0-n。
AC:
#include <stdio.h>
#include <algorithm>
#include<iostream>
using namespace std;
struct s
{
int left;
int right;
int mi;
}tree[800008];
char s[800008];
void build(int id,int l,int r)
{
tree[id].left=l;
tree[id].right=r;
if(l==r)
{
tree[id].mi=0;
}
else{
int mid=(l+r)/2;
build(2*id,l,mid);
build(2*id+1,mid+1,r);
tree[id].mi = min(tree[id*2].mi,tree[2*id+1].mi);
}
}
void update(int id,int x,int y)
{
if(tree[id].left ==tree[id].right )
{
tree[id].mi =y;
}
else
{
int mid=(tree[id].left +tree[id].right )/2;
if(x>mid) update(2*id+1,x ,y);
else update(2*id,x,y);
tree[id].mi =min(tree[2*id].mi,tree[2*id+1].mi);
}
}
int query(int id,int l,int r)
{
if(tree[id].left >=l&&tree[id].right <=r)
return tree[id].mi;
else
{
int mid=(tree[id].left +tree[id].right )/2;
if(l>mid) return query(id*2+1,l,r);
else if(r<=mid) return query(id*2,l,r);
else return min(query(id*2+1,l,r),query(id*2,l,r));
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int pre,val,n,k;
pre=1;
scanf("%d%d",&n,&k);
scanf("%s",s+1);
build(1,0,n);
update(1,1,1);
for(int i=2;i<=n;i++)
{
if(s[i]==s[i-1])
{
pre=1;
val=query(1,max(i-k,0),i-1);
}
else {
pre++;
if(pre>=k||pre==i)
{
val=query(1,i-1,i-1);
}
else val=query(1,max(i-k,0),i-1-pre);
}
update(1,i,val+1);
}
printf("%d\n",query(1,n,n)-1);
}
}