前言
花了一天多搞懂一道题目,最后的顿悟真的太舒服了
题目链接
1.题目大意:给出一个长度为
n
n
n的字符串,现在问最少修改多少次使得所有连续(顺序)出现的
1
1
1的间隔均为
k
k
k
2.如果两个连续出现的 1 1 1间隔为 k k k,那么中间的字符都是0,也就是说对于所有间隔为 k k k的字符,仅有一组能出现1,其余必须全部为0。那么我们首先将这个字符串分为 k k k组,从下标0开始每 k k k个字符取出为第一组,…,从下标 k − 1 k-1 k−1开始每 k k k个字符取出为第二组。也就是相当于每个下标对 k k k取模,余数相同的分为一组
3.考虑上面的 k k k组,显然只有其中一组能出现1,那么其它的都设置为0,这一步需要的次数为整个字符串1的个数减去当前组1的个数
4.但是还没结束,一组字符串中如果能出现1,这里的1必须是连续的1,即形如"000011110000","1111100000"这样的才是合法的,下面有两种方法:
方法一:前缀和
对于一组这样的字符串"001011101000010000"
首先我们对字符取前缀和(统计每个前缀中1的个数),那么考虑已经生成了最终的序列,连续1的区间为 [ L , R ] [L,R] [L,R],那么:
- L L L之前的字符都为0,即 L L L之前的1都需要变为0,即 s u m [ L − 1 ] sum[L-1] sum[L−1]
- R R R之后的字符都为0,即 R R R之后的1都需要变为0,即 s u m [ n ] − s u m [ R ] sum[n]-sum[R] sum[n]−sum[R]
- 区间 [ L , R ] [L,R] [L,R]之间的0都需要变为1,即 ( R − L + 1 ) − ( s u m [ R ] − s u m [ L − 1 ] ) (R-L+1)-(sum[R]-sum[L-1]) (R−L+1)−(sum[R]−sum[L−1])
最后的答案就是 m i n { s u m [ L − 1 ] + s u m [ n ] − s u m [ R ] + ( R − L + 1 ) − ( s u m [ R ] − s u m [ L − 1 ] ) } min\{ sum[L-1]+sum[n]-sum[R]+(R-L+1)-(sum[R]-sum[L-1]) \} min{sum[L−1]+sum[n]−sum[R]+(R−L+1)−(sum[R]−sum[L−1])}
整理一下,即 m i n { s u m [ n ] − 2 ∗ s u m [ R ] + R + [ 2 ∗ s u m [ L − 1 ] − ( L − 1 ) ] } min\{ sum[n]-2*sum[R]+R+[2*sum[L-1]-(L-1)] \} min{sum[n]−2∗sum[R]+R+[2∗sum[L−1]−(L−1)]}
那么需要枚举所有的区间吗?显然不是,对于一个区间我们只需固定右端点 R R R,那么 L L L取的是 [ 1 , R ] [1,R] [1,R]之间的 [ 2 ∗ s u m [ L − 1 ] − ( L − 1 ) ] [2*sum[L-1]-(L-1)] [2∗sum[L−1]−(L−1)]的最小值
方法二:dp
其实我补题时第一眼也想到了dp,因为我们只需考虑第一个为1和最后一个为1,然后这段区间内在保证合法性的情况下某个数既可以变为0又可以变为1,那么我们从左向右递推,并设置两个状态,具体见下面代码:
int dp[2][maxn];
int solve(){
int len=res.size();
int i=1,j=len-1;
memset(dp,0,sizeof dp);
while(i<=len-1 && !res[i]) i++;
while(j>=1 && !res[j]) j--;
for(int k=i;k<=j;k++){
if(res[k]){
dp[1][k]=min(dp[1][k-1],dp[0][k-1]);
dp[0][k]=dp[0][k-1]+1;
}else{
dp[0][k]=dp[0][k-1];
dp[1][k]=min(dp[1][k-1]+1,dp[0][k-1]+1);
}
}
return min(dp[0][j],dp[1][j]);
}
遗憾的是不知道为什么这个O(n)的dp超时了!
无奈之下去参考了Kanoon博主的文章,实际上一维的dp即可,但是也要像前面前缀和那样思考:
d
p
[
i
]
dp[i]
dp[i] 表示当前位置为 1,之前的字符串合法至少需要改变的字符个数
一开始需要判断当前字符是否为 1:
d
p
[
i
]
=
(
s
[
i
]
=
=
dp[i] = (s[i] ==
dp[i]=(s[i]== ‘
1
1
1’
)
)
)
当前位置为 1,之前的字符串合法有两种情况:
- 前一个位置为 1,前一个位置之前的字符串合法至少需要改变的字符个数: d p [ i − 1 ] dp[i - 1] dp[i−1]
- 前一个位置为 0,前一个位置之前的字符串合法至少需要改变的字符个数: p r e f − c u r pref - cur pref−cur
所以: d p [ i ] + = m i n ( d p [ i − 1 ] , p r e f − c u r ) dp[i] += min(dp[i - 1], pref - cur) dp[i]+=min(dp[i−1],pref−cur)
同时需要保证当前位置之后的字符串合法,即将当前位置之后的 1 都变为 0: a l l − p r e f all - pref all−pref
所以: a n s = m i n ( a n s , d p [ i ] + a l l − p r e f ) ans = min(ans, dp[i] + all -pref) ans=min(ans,dp[i]+all−pref)
代码
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <math.h>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const ll INF=1e18;
const int Mod=1e9+7;
const int maxn=1e6+10;
string s;
vector<int> res;
int a[maxn],d[maxn],sum[maxn];
int dp[maxn];
/*int dp[2][maxn];
int solve(){
int len=res.size();
int i=1,j=len-1;
memset(dp,0,sizeof dp);
while(i<=len-1 && !res[i]) i++;
while(j>=1 && !res[j]) j--;
//cout<<i<<" "<<j<<endl;
for(int k=i;k<=j;k++){
if(res[k]){
dp[1][k]=min(dp[1][k-1],dp[0][k-1]);
dp[0][k]=dp[0][k-1]+1;
}else{
dp[0][k]=dp[0][k-1];
dp[1][k]=min(dp[1][k-1]+1,dp[0][k-1]+1);
}
}
return min(dp[0][j],dp[1][j]);
}*/
int cal(int cnt){ //dp
memset(dp,0,sizeof dp);
int ans=cnt,n=res.size(),pre=0;
for(int i=0;i<n;i++){
int cur=res[i];
pre+=cur;
dp[i]=1-cur;
if(i>0) dp[i]+=min(dp[i-1],pre-cur);
ans=min(ans,dp[i]+cnt-pre);
}
return ans;
}
int f(int cnt){ //前缀和
int len=res.size()-1;
for(int i=1;i<=len;i++){
sum[i]=sum[i-1]+res[i];
}
int ans=sum[len],pre=0;
//sum[l-1]+sum[len]-sum[r]+(r-l+1)-(sum[r]-sum[l-1])
//上面等价于:sum[len]-2*sum[r]+r+[2*sum[l-1]-(l-1)]
for(int r=1;r<=len;r++){
ans=min(ans,sum[len]-2*sum[r]+r+pre);
pre=min(pre,2*sum[r]-r);
}
return ans;
}
int main(){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t,n,k;
cin>>t;
while(t--){
cin>>n>>k;
cin>>s;
int sum=0;
for(int i=0;i<k;i++){
a[i+1]=0;
res.clear();
res.push_back(-1); //为了使区间下标1开始设置无关的数第一个加入
for(int j=i;j<n;j+=k){
res.push_back(s[j]-'0');
if(s[j]=='1') a[i+1]++,sum++;
}
//d[i+1]=cal(a[i+1]);
d[i+1]=f(a[i+1]);
}
int ans=sum;
for(int i=1;i<=k;i++){
ans=min(ans,sum-a[i]+d[i]);
}
cout<<ans<<"\n";
}
return 0;
}