一道重要题
做题时要根据部分分不断往前推进,而不是上来就干100分,有一些错误的想法不要立刻
否定,一些错误的想法根据修正也能给我们提供重要的信息。
3278 -- Catch That Cow (poj.org)
(前提)别用搜索...
(提示)这道题的贪心是错误的。
贪心思路:
转换成二进制然后将A变成B的前缀,然后不断在二进制后面加零,加一(即\(×2,+1\))。
但这个贪心是错误的,我比如说
\[A=(111)_2
\\B=(1111111)_2
\]
用我们的想法操作次数是\(8\),但这个样例有一个更优的做法。先给A加上1变成\(A=(1000)_2\),然后我们在加零,加到比原来多一个,再减一就好了。
\[A=(1000)_2//加1\\
A=(10000000)_2//×2操作\\
A=(1111111)_2//减1操作
\]
我们可以知道下面要比上面的优,所以我们之前的想法假了,然而我们不能直接放弃我们的第一种想法,因为在很多时候我们的第一种想法是最优的,所以这告诉我们前缀和前缀+1一样重要。这样我们就可以设计转移方程了。
先列出状态:\(f[i][0]\)表示把A变成长度为\(i\)的前缀,\(f[i][1]\)表示把A变成长度为\(i\)的前缀\(+1\)。
这道题告诉我们,我们要多方面考虑,然后遇到假的想法,也要把他订正,看看能不能成为有效信息。
我们设计一下转移方程
if(第i位为0){
f[i][0]=min(f[i-1][0]+1,f[i-1][1]+2);
f[i][1]=min(f[i-1][0]+2f[i-1][1]+2);
}
else{
f[i][0]=min(f[i-1][0]+2,f[i-1][1]+2);
f[i][1]=min(f[i-1][0]+2,f[i-1][1]+1);
}
很多人可能有疑惑,在最后一个方程中的\(f[i-1][1]\)为什么只加1,我也是问了才会的...
f[i-1][0]:10001 f[i-1][1]:100010
f[i][0]:100011 f[i][1]:100100
其实就是\(f[i-1][1]×2\) \(...\)
答案是\(min(f[n][0],f[n][1]+1)\)。
code:
#include <iostream>
#include <cstdio>
#include <bitset>
#include <cmath>
using namespace std;
//#define int long long
const int maxn=1e6+5;//注意题目要求
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*w;
}
int n,m;
bitset<30> nn;
int dp[1000000][2];
void DP(){
nn=n;
int mxn=29;while(nn[mxn]==0) --mxn;
memset(dp,0x3f,sizeof(dp));
int tmp=0;
for(int i=0;i<=mxn;i++){
tmp=tmp*2+nn[mxn-i];
dp[i][0]=abs(m-tmp);
dp[i][1]=abs(m-tmp-1);
}
for(int i=1;i<=mxn;i++){
if(nn[mxn-i]==0){
dp[i][0]=min(dp[i][0],min(dp[i-1][0]+1,dp[i-1][1]+2));
dp[i][1]=min(dp[i][1],min(dp[i-1][0]+2,dp[i-1][1]+2));
}
else {
dp[i][0]=min(dp[i][0],min(dp[i-1][0]+2,dp[i-1][1]+2));
dp[i][1]=min(dp[i][1],min(dp[i-1][1]+1,dp[i-1][0]+2));
}
}
cout<<min(dp[mxn][0],dp[mxn][1]+1)<<endl;
}
int main(){
m=read(),n=read();
if(m>=n){cout<<m-n<<endl;return 0;}
else DP();
return 0;
}