把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【状态压缩DP】Kronican--7.2测试 COCI

样例
3 3
0 1 1
1 0 1
1 1 0

0



3 2
0 1 1
1 0 1
1 1 0

1



5 2
0 5 4 3 2
7 0 4 4 4
3 3 0 1 2
4 3 1 0 5 
4 5 5 5 0 

5

分析

在考场上最先想到的是dp
定义 d p [ i ] [ j ] 为 dp[i][j]为 dp[i][j]前i杯水变成j杯的最小代价
转移的话 如果当前这杯水不倒 就是 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]
如果要倒的话…
e m m m . . . emmm... emmm...
我们发现这个地方不好转移 我们划分的子问题是有后效性的 (好像是这么个说法
我们之前选择的倒在哪些杯子里面,现在哪些杯子里面有水哪些没有,是会影响转移的
也就是说,我们还需要知道这些杯子的状态
瞅一眼数据范围 N &lt; = 20 N&lt;=20 N<=20
答案好像呼之欲出!
对!状压DP!
但是考试的时候…我发现自己忘记了关于集合状态的表示方法 q w q qwq qwq

继续补:集合的二进制整数表示

关于这个我还想再在这里重新补充一下:
集合运算中| 和 ^是有区别的(废话
|是随便有一个是1结果就是1 所以用来取∪
而^是不一样为1 一样为0 和1异或可以用来取反
这道题如果定义1表示有水 0表示没有水的话 就要用它


所以考场上搞了一个骗分
先把每个杯子互相倒的最小代价(有可能会通过其它杯子
然后算其中最小的n-k个最小代价 累加起来(骗分过样例

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define LL long long
#define MAXN 25
#define INF 0x3f3f3f3f
int n,k,cnt;
LL ans;
int c[MAXN][MAXN],edge[MAXN*MAXN];
bool vis[MAXN];
queue<int>Q;;
void work()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i==j) continue;
			for(int k=1;k<=n;k++)
			{
				if(k==i||k==j) continue;
				c[i][j]=min(c[i][j],c[i][k]+c[k][j]);
			}
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
		{
			if(i==j) continue;
			edge[++cnt]=min(c[i][j],c[j][i]);
		}
	sort(edge+1,edge+1+cnt);
}
int main()
{
	//freopen("kronican.in","r",stdin);
	//freopen("kronican.out","w",stdout);
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&c[i][j]);
	if(n==k){printf("0\n"); return 0;}
	work();
	for(int i=1;i<=n-k;i++)
		ans+=edge[i];
	printf("%lld\n",ans);
	return 0;
}

我觉得转移可以直接看代码了
定义 d p [ S ] dp[S] dp[S] S S S状态下的最小代价
我定义的0表示有水 1表示没有水(好像有点奇怪

如果1表示有水 0表示没水的话 初始状态就是 ( 1 &lt; &lt; n ) − 1 (1&lt;&lt;n)-1 (1<<n)1,表示含有n个元素的全集
初始化就是 d p [ ( 1 &lt; &lt; n ) − 1 ] = 0 dp[(1&lt;&lt;n)-1]=0 dp[(1<<n)1]=0 其它为 I N F INF INF

倒水都是从一个有水的杯子到另一个有水的杯子
如果从有水到没水 那就做了无用功
把接受水的那个杯子里的水倒出去是没有用的
所以每次转移都枚举两个有水且不相同的两个杯子 进行倒水
因为遍历完了所有的杯子所以不用颠倒顺序 再更新从 j j j-> i i i
(就直接想成 第一层枚举倒水的杯子 第二层枚举接受水的杯子

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 20
#define INF 0x3f3f3f3f
#define LL long long
int n,k;
int c[MAXN+5][MAXN+5];
int dp[1<<MAXN],ans;
int bitcnt(int S)
{
	int res=0;
	while(S)
	{
		S&=(S-1);
		res++;
	}
	return res;
}
int main()
{
	freopen("kronican.in","r",stdin);
	freopen("kronican.out","w",stdout);
	scanf("%d %d",&n,&k);
	if(n==k){printf("0\n"); return 0;}
	for(int i=0;i<n;i++)//状压的写法 后面是用的第0位 第1位 ...所以从0开始 
		for(int j=0;j<n;j++)
			scanf("%d",&c[i][j]);
	memset(dp,INF,sizeof(dp));
	dp[0]=0;//0表示有水 1表示没有水 
	for(int S=0;S<(1<<n);S++)
	{
		for(int i=0;i<n;i++)
			if(!(S&(1<<i)))//没得交集 i有水 
			{
				for(int j=0;j<n;j++)
					if(!(S&(1<<j))&&i!=j)//j也有水 而且贪心地想 倒水只会从有水到有水 不会有水到没水 
						dp[S|(1<<i)]=min(dp[S|(1<<i)],dp[S]+c[i][j]);
			}
	}
	ans=INF;
	for(int S=0;S<(1<<n);S++)
		if(bitcnt(S)>=n-k)
			ans=min(ans,dp[S]); 
	printf("%d",ans);
}
//如果1表示有水 0表示没水的话  初始状态是(1<<n)-1表示含有n个元素的全集


 
posted @ 2019-07-03 09:43  Starlight_Glimmer  阅读(13)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end