版权声明:未经过同意不得转载 https://blog.csdn.net/qq_42500298/article/details/89515884
我们要求的最后状态就是:
直到下标超过n
做一个转换,让每m个一组
就变成了
1 2 3 … m
m+1 m+2 m+3 … 2m
… … … …
翻转1个硬币就是把矩阵中的一个0变1 1变0
翻转矩阵中的前k行
那么对于同样的k执行操作是有意义的吗?
显然是没有意义的,例如 k=2 那就把前2m个操作异或1,再异或1,就是取反再取反,就回到原来状态了
那翻三次呢,显然就相当于翻一次
翻多次是没有意义的,对于相同的k最多执行1次
于是
当m>=√n时
行数<=√n≈18
kmax=18
枚举行的最后状态,枚举每一次k执不执行操作
暴力取把花费的代价取min
当m<=√n时
列数18行
这时行数很多很多,所以枚举列的最后状态
考虑状压DP
f[i][j][k]表示执行第i行的状态为j当前是否翻转状态
从前往后每个子任务斗鱼前一个有联系,有后效性,所以从后往前推,看k于j有多少个不同,只要是不同就是第一个状态
#include <bits/stdc++.h>
using namespace std;
const int MAX_N=305;
int n,m;
string s;
namespace subtask_1
{
int f[400][300000][2];
int main()
{
for(int i=0;i<n;i++)s[i]-='0';
int ans=1<<30;
int k=n/m;
if(n%m)k++;
for(int i=0;i<(1<<m);i++)
{
for(int j=(k-1)*m;j<n;j++)
{
if((s[j]<<(j%m))^(i&(1<<(j%m))))f[k-1][i][0]++;
else f[k-1][i][1]++;
}
for(int kk=k-2;kk>=0;kk--)
{
for(int j=kk*m;j<(kk+1)*m;j++)
{
if((s[j]<<(j%m))^(i&(1<<(j%m))))f[kk][i][0]++;
else f[kk][i][1]++;
}
f[kk][i][0]+=min(f[kk+1][i][0],f[kk+1][i][1]+1);
f[kk][i][1]+=min(f[kk+1][i][1],f[kk+1][i][0]+1);
}
ans=min(ans,min(f[0][i][0],f[0][i][1]));
}
printf("%d\n",ans);
return 0;
}
}
namespace subtask_2
{
int siz;
int B[MAX_N][MAX_N];
int cnt[MAX_N][2];
void flip(int row)
{
for(int i=0;i<m;i++)
{
if(B[row][i]==-1)continue;
B[row][i]^=1;
}
}
int main()
{
memset(B,-1,sizeof(B));
siz=n/m;
int ans=INT_MAX;
for(int i=0;i<(1<<siz);i++)
{
for(int j=0;j<n;j++)B[j/m][j%m]=s[j]-'0';
for(int j=0;j<siz;j++)
{
if(i&(1<<j))
{
for(int k=0;k<=j;k++)
{
flip(k);
}
}
}
for(int j=0;j<n;j++)cnt[j%m][B[j/m][j%m]]++;
int sum=__builtin_popcount(i);
for(int i=0;i<m;i++)sum+=min(cnt[i][0],cnt[i][1]);
ans=min(ans,sum);
}
cout<<ans<<endl;
return 0;
}
}
int main()
{
cin>>s>>m;
n=s.size();
if(m*m<=n)subtask_1::main();
else subtask_2::main();
return 0;
}