【状态压缩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[i−1][j−1]
如果要倒的话…
e
m
m
m
.
.
.
emmm...
emmm...
我们发现这个地方不好转移 我们划分的子问题是有后效性的 (好像是这么个说法
我们之前选择的倒在哪些杯子里面,现在哪些杯子里面有水哪些没有,是会影响转移的
也就是说,我们还需要知道这些杯子的状态
瞅一眼数据范围
N
<
=
20
N<=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
<
<
n
)
−
1
(1<<n)-1
(1<<n)−1,表示含有n个元素的全集
初始化就是
d
p
[
(
1
<
<
n
)
−
1
]
=
0
dp[(1<<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个元素的全集