bzoj2064分裂(dp)

7 篇文章 0 订阅

题目大意:
给定一个初始集合和目标集合,有两种操作:1.合并集合中的两个元素,新元素为两个元素之和 2.分裂集合中的一个元素,得到的两个新元素之和等于原先的元素。要求用最小步数使初始集合变为目标集合,求最小步数。

其中初始集合和目标集合的元素个数都不超过10个

这是一道非常值得纪念的好题

首先一看到这个数据范围,第一反应就是状压dp了

我们首先这么考虑

如果说直接暴力的合并和分裂的话,最多需要的次数是 n + m − 2 n+m-2 n+m2次,那么我们是不是可以在这个基础上进行优化呢。举个例子来看 1 , 5 , 7 , 2 1,5,7,2 1,5,7,2–> 2 , 4 , 3 , 6 2,4,3,6 2,4,3,6那么我们完全可以把 1 , 5 1,5 1,5单独处理,不用将他们再跟 2 , 7 2,7 2,7合并了,也不用额外的删除,那么这样其实就是相当于分组了。

那我们对于整体的集合,分成几个小集合,每个小集合内部又可以做同样的优化,这不就是dp的最优子结构吗

假设初始和目标都可以分成k组,那么答案应该就是 n − m − 2 × k n-m-2\times k nm2×k了这里应该没有问题吧。

我们令 f [ i ] [ j ] f[i][j] f[i][j]表示初始集合中选择的元素集合是 i i i,目标集合是 j j j的分成的组数的最大值

对于 f [ i ] [ j ] f[i][j] f[i][j]我们先令他等于能转移到 f [ i ] [ j ] f[i][j] f[i][j]的状态中的最大值,如果当前的 s a [ i ] = = s b [ j ] sa[i]==sb[j] sa[i]==sb[j]那么就说明他可以自己单独成一组,就可以++

但这里其实有一个问题就是,当 i i i j j j s u m sum sum不相同的时候,其实也有可能会++但是我们不考虑,这里有一种理解方式,是的,实际上就是因为这中间存在很多状态的合成,我们把无用的合成的状态忽略,却不会忽略用来合成它的状态,所以最后答案不会遗漏最优情况,换句话说,可以理解为就是每个不相等的情况 一定可以被不相等到相等再到不相等。其实本质是一样的

直接看代码吧

感觉这个题还是不太好理解呀

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

const int maxn = 11;

int n,m;
int sa[1 << maxn],sb[1 << maxn];
int f[1 << maxn][1 << maxn];
int a[maxn],b[maxn];

int counta(int x)
{
	int cnt=0;
	for (int i=1;i<=n;i++)
	if (x & (1 << (i-1))) cnt+=a[i];
	return cnt;
}

int countb(int x)
{
	int cnt=0;
	for (int i=1;i<=m;i++)
	if (x & (1 << (i-1))) cnt+=b[i];
	return cnt;
}

int main()
{
  scanf("%d",&n);
  for (int i=1;i<=n;i++) scanf("%d",&a[i]);
  m=read();
  for (int i=1;i<=m;i++) b[i]=read();
  for (int i=1;i<(1 << n);i++) sa[i]=counta(i);
  for (int i=1;i<(1 << m);i++) sb[i]=countb(i);
  for (int i=1;i<(1 << n);i++)
    for (int j=1;j<(1 << m);j++)
    {
    	for (int k =1;k<=n;k++)
    	{
    		int p = 1 << (k-1);
    		if (p&i) f[i][j]=max(f[i][j],f[i-p][j]);
		}
		for (int k=1;k<=m;k++)
		{
			int p = 1 << (k-1);
			if (p&j) f[i][j]=max(f[i][j],f[i][j-p]);
		}
		if (sa[i]==sb[j]) f[i][j]++;
	}
  printf("%d",n+m-2*f[(1<<n)-1][(1<<m)-1]);
  return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值