题目描述
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分......
讲正解了。
其实也挺容易
其实就相当于在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);
}