3 1 2 3 3 1 1 1 1 10
0 3 impossible
思路:dp[x][y],从右向左数,第x位1是偶数状态下的进位状态y.然后就是判断各种条件了。
我们的DP就是从右边到左边来做的,首先保存每一列的状态,也是用二进制来表示的,如 7,5,0,0(假设右边是低位)
那么我们设 f[ i ][ j ]表示右边起第 i 位 保证这一列的 1 的个数是偶数的情况下,进位的状态为 j (进位的状态与上面的每列状态一样,就是每个数产生进位的状态)
那么有 f[ i ][ j ]=min{ f[ i-1][ k ]+满足进位后这一列的1 的个数为偶数,并且进位状态为1 需要加的数的和}
具体的一堆判断用位运算来做,
tmp=s[ i ]&k表示第i列一定产生的进位
s[ i ]^k 表示加上之前的进位后这一列的状态
j^tmp表示需要再进位的是哪几位
s[ i ]^k & j^tmp == j^tmp 只有满足需要进的位都为1 才能成功进位(如果为0 ,加1 后还不能进位,加2 的情况可以转换成高一位的计算)
s[ i ]^k ^ j^tmp 表示满足进位后的状态,这个状态必须满足 1 的个数为偶数,如果为 奇数并且个数少于n ,则再加随便再0的位置加个1就行,不过答案要多加一次
摘自 fp_hzq
注意:凡是带&等操作的,都需要加括号,(就这,调了进一个钟头,饭都没吃。。。。)
#include<iostream> #include<cstring> #include<cstdio> #define gb(x) (1<<x) using namespace std; const int mm=1<<11; int dp[25][mm],s[25],bit[mm],f[25]; int n; int getbit(int x) { int ret=0; while(x) { ret+=x&1; x>>=1; } return ret; } int main() { for(int i=0; i<mm; ++i) bit[i]=getbit(i); while(~scanf("%d",&n)) { for(int i=0; i<n; ++i) scanf("%d",&f[i]); if(n<2) { puts("impossible"); continue; } int m; memset(s,0,sizeof(s)); for(int i=1; i<25; ++i) { for(int j=0; j<n; ++j) if(f[j]&gb(i-1)) s[i]|=gb(j); if(s[i])m=i+1; } memset(dp,0x3f,sizeof(dp)); int ans=dp[0][0]; dp[0][0]=0; int tmp; for(int i=1; i<=m; ++i) for(int j=0; j<gb(n); ++j) if(dp[i-1][j]<ans) { tmp=j&s[i];///已产生的进位 for(int k=tmp; k<gb(n); ++k) ///k 进位状态 { if(((k&tmp)==tmp)&&///包含已产生的 ((s[i]^j)&(tmp^k))==(tmp^k)&&///和需要包括进位 ((bit[s[i]^j^tmp^k]&1)==0||bit[s[i]^j^tmp^k]<n)/// 需要加上 tmp^k ) { ///if(k==0)puts("www"); int z=bit[tmp^k]+(bit[s[i]^j^tmp^k]&1); dp[i][k]=std::min(dp[i][k],dp[i-1][j]+(bit[tmp^k]+(bit[s[i]^j^tmp^k]&1))*gb(i-1)); ///cout<<dp[i][k]<<" i k "<<i<<" "<<k<<" "<<j<<" "<<z<<" "<<tmp<<endl; } } } for(int i=0; i<gb(n); ++i) if((bit[i]&1)==0) { ans=std::min(ans,dp[m][i]); } printf("%d\n",ans); } }