[第三场T3]Kronican

15 篇文章 0 订阅

题目描述

Mislav有N个无限体积的杯子,每一个杯子中都有一些水。Mislav想喝掉所有的水,但他不想喝超过K杯水。Mistrav能做的就是将一个杯子中的水倒入另一个杯子中。 不幸的是,挑选哪两个杯子进行倒水操作对Mislav来说很重要,因为并非所有的杯子都离他一样远。更准确地说,从i号杯子向j号杯子倒水所付出的代价为Cij。 帮助Mislav找到他需要付出的总代价的最小值。

输入

第一行输入包含整数N和K(1≤K≤N≤20)。表示水杯的总数和Mislav最多能喝多少杯。 接下来N行每行包含N个整数Cij(0≤Cij≤1e5)。第i+1行的第j个整数表示从第i个杯子第j个杯子倒水所需要付出的代价。保证Cii等于0。

输出

输出一个整数。表示Mislav需要付出的总代价的最小值。

样例输入

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

解题思路

n比较小,每个杯子只有有水无水两种情况,可用01字符串表示,很明显看出来状压了。

然而当时考我还不会。看着这道题我以为是最小生成树,然而后面发现不对劲,因为有些点你倒出去的不可能再有其他杯子倒进来,这样不可能最优。也不可能还可以倒到另外一些杯子,所以最小生成树是有问题的。当时没管这么多,搞了个裸的最小生成树,想要骗分,一开始本来写了判断点可不可以再连边的,后面删了,然而旁边那个人没删,骗了80分......

讲正解了。

其实也挺容易

dp[s - (1 << (i - 1))] = min(dp[s - (1 <<( i - 1))],dp[s] + a[i][j])

其实就相当于在s状态下把i号杯子的水倒入j号杯子。

那么s是啥,在本题s状态其实就是一个二进制数用十进制表示的状态,0表示当前杯子无水,1表示当前杯子有水。

所有杯子所组成的状态便是s

举个例子

如果共有6个杯子 1 3 5 号有水,其余无水。那么整个二进制便是010101(从6到1连起来)

十进制数便是21。

dp差不多是这样,但这个dp好像挺难实现的。写法其实感觉挺奇葩的,很不能理解。

如果你用1表示有水的话

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
int n,k,a[25][25];
int dp[2000005],ans = 0x3f3f3f3f;
int check(int x){
	int ret = 0;
	while (x){
		if (x % 2)
			ret ++;
		x /= 2;
	}
	return ret;
}
int main(){
	scanf ("%d%d",&n,&k);
	for (int i = 1;i <= n;i ++)
		for (int j = 1;j <= n;j ++)
			scanf ("%d",&a[i][j]);
	memset(dp,0x3f,sizeof(dp));
	dp[(1 << n) - 1] = 0;//都有水,花费为0
	for (int s = (1 << n) - 1;s >= 1;s --){//枚举当前状态(2进制01表示,这里转换为10进制)
		for (int i = 0;i < n;i ++){//要倒出的杯子
			if ((s & (1 << i))){//当前状态下,倒出杯子中的水不为空
				for (int j = 0;j < n;j ++){
					if (i != j && (s & (1 << j))){//当前状态下,倒入杯子处的水不为空(为空无意义)并且不相同。
						dp[s - (1 << i)] = min(dp[s - (1 << i)],dp[s] + a[i + 1][j + 1]);//状态转移
					}
				}
			}
		}
	}
	for (int s = 0;s < (1 << n);s ++){//找答案
		if (check(s) == k)
			ans = min(ans,dp[s]);
	}
	printf("%d",ans);
}

如果1表示无水的话

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
int n,k,a[25][25];
int dp[2000005],ans = 0x3f3f3f3f;
int check(int x){
	int ret = 0;
	while (x){
		if (x % 2)
			ret ++;
		x /= 2;
	}
	return ret;
}
int main(){
	scanf ("%d%d",&n,&k);
	for (int i = 1;i <= n;i ++)
		for (int j = 1;j <= n;j ++)
			scanf ("%d",&a[i][j]);
	for (int i = 1;i < (1 << n);i ++)//边界,dp[0] = 0
		dp[i] = 0x3f3f3f3f;
	for (int s = 0;s < (1 << n);s ++){//枚举当前状态(2进制01表示,这里转换为10进制)
		for (int i = 0;i < n;i ++){//要倒出的杯子
			if (!(s & (1 << i))){//当前状态下,倒出杯子中的水不为空
				for (int j = 0;j < n;j ++){
					if (i != j && !(s & (1 << j))){//当前状态下,倒入杯子处的水不为空(为空无意义)并且不相同。
						dp[s^(1 << i)] = min(dp[s^(1 << i)],dp[s] + a[i + 1][j + 1]);//状态转移
					}
				}
			}
		}
	}
	for (int s = 0;s < (1 << n);s ++){
		if (check(s) == n - k)
			ans = min(ans,dp[s]);
	}
	printf("%d",ans);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值