一道数位dp题,状态有点复杂但是不难
补个范围:
题目大意:
给你三个数:a,b,c
你可以对这三个数的二进制进行任意排列使得最终满足:c=a+b
求最小的满足条件的c,如果没有输出-1
这道题,我们考虑先计算出a,b,c各种的二进制中有多少个"1",分别设为:A,B,C
在算出三个数中二进制长度最长的,设为n
那么,现在题目就相当于:
分别用A,B,C个1凑出长度为n的三个二进制数(允许前导0)满足上述和的关系,且要求c最小
于是我们就可设int 表示,我们还剩i位,C剩j个1,A剩k个1,B剩t个1能凑出满足条件的最小的数来
但,我们注意到,题目是“加法”,于是自然而然的产生了一种情况——进位,如果我们dp这么设的话,是无法保证答案正确的,所以我们再开一位:表示我们还剩i位,C剩j个1,A剩k个1,B剩t个1并且我们是否要向前1位进1(因为最大情况就是1+1+1(本身两个1加上后面进的1))
一开始有:
那么,我们就来考虑转换:
如果,我们现在不向前一位进1,那么有如下情况:
1.A,B都是0,且后一位不进1=>现在这一位就是0
2.A,B都是0,且后一位进1=>现在这一位就是1
3.A,B有一个是1,且后一位不进1=>现在这一位就是1
于是我们有转移方程:
(当然,如果j/k/t等于0的话,那么j/k/t就不存在需要-1的情况了)
同理,我们有向前一位进1的转移方程:
这样,我们就可以直接推出所有的dp的值了
按照意义,答案就是
复杂度
可以通过此题
代码(我打的记搜,2333):
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=50,inf=1e15;
long long sav[N];
long long dp[N][N][N][N][2];
bool vis[N][N][N][N][2];
inline int dfs(int now,int C,int A,int B,bool ned){
if(!now){
if(C==0&&A==0&&B==0&&ned==0){
return 0;
}
return inf;
}
if(vis[now][C][A][B][ned]){
return dp[now][C][A][B][ned];
}
vis[now][C][A][B][ned]=1;
int res=inf;
if(ned){
//0
if(A&&B){
res=min(res,dfs(now-1,C,A-1,B-1,0));
}
if(A){
res=min(res,dfs(now-1,C,A-1,B,1));
}
if(B){
res=min(res,dfs(now-1,C,A,B-1,1));
}
//1
if(C){
if(A&&B){
res=min(res,dfs(now-1,C-1,A-1,B-1,1)+sav[now]);
}
}
}else{
//0
res=min(res,dfs(now-1,C,A,B,0));
//1
if(C){
res=min(res,dfs(now-1,C-1,A,B,1)+sav[now]);
if(A){
res=min(res,dfs(now-1,C-1,A-1,B,0)+sav[now]);
}
if(B){
res=min(res,dfs(now-1,C-1,A,B-1,0)+sav[now]);
}
}
}
return dp[now][C][A][B][ned]=res;
}
signed main(){
sav[1]=1;
for(int i=2;i<=49;++i){
sav[i]=(sav[i-1]<<1);
}
long long a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
int A=0,B=0,C=0,tot=0,tol=0;
while(a){
A+=(a&1);a>>=1;++tot;
}
while(b){
B+=(b&1);b>>=1;++tol;
}
tot=max(tot,tol),tol=0;
while(c){
C+=(c&1);c>>=1;++tol;
}
tot=max(tot,tol);
memset(dp,0x3f3f,sizeof(dp));
dfs(tot,C,A,B,0);
if(dp[tot][C][A][B][0]>=sav[tot+1]){
puts("-1");
}else{
printf("%lld",dp[tot][C][A][B][0]);
}
return 0;
} 
京公网安备 11010502036488号