状压DP思路好题
网上齐刷刷一片“只可意会不可言传”,这让意会能力差的人(比如在下)怎么活啊…于是为了攒人品来简单写一下方法,表示自己言传能力比较差,希望大家不要在意。
最差的次数是n+m-2次,这是很显然的。
然后我们想想如何可以减少次数?如果我们能把初始状态和结束状态分别分为两堆,然后两边分别对应相等,就可以用n+m-4次操作完成。这只要将初始的第一堆变成结束第一堆,初始第二堆变成结束第二堆。
所以如果能将初始状态和结束状态分别分为k堆,两边对应相等,就可以用n+m-2*k次操作完成。
于是问题就转化成求k的最小值。
这就可以用状压DP完成了。我们将初始状态和结束状态混在一起,初始的权值不变,结束的权值变为相反数。然后一个状态x,二进制第i位等于1/0对应的第i个数取/不取。
这样如果某一个子集的和等于0,就表示它对应的初始和结束状态的和相等,也就是可以互相变化。
然后用f[i]表示i这个状态的所有真子集中k的最大值,如果sum[i]=0则f[i]++。
最后答案等于n+m-2*f[2^(n+m)-1]。
啊,这道题思路太妙了
#include
#include
#include
#include
#include
#include
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define maxn 2000000
using namespace std;
int n,m,f[maxn],sum[maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main()
{
n=read();F(i,1,n) sum[1<<(i-1)]=read();
m=read();F(i,1,m) sum[1<<(n+i-1)]=-read();
int num=(1<<(n+m))-1;
F(i,1,num)
{
int tmp=i&(-i);
sum[i]=sum[tmp]+sum[i-tmp];
F(j,1,n+m) if (i&(1<<(j-1))) f[i]=max(f[i],f[i-(1<<(j-1))]);
if (!sum[i]) f[i]++;
}
printf("%d\n",n+m-2*f[num]);
return 0;
}