『图论·状态压缩DP』AT2657:Mole and Abandoned Mine

P r o b l e m \mathrm{Problem} Problem

给一个n个点m条边的无向连通图(不存在自环或重边),每条边有一个边权,要求割掉若干条边,使1到n只有1条路径(不经过重复点),问割掉的边权和最小是多少。

D a t a \mathrm{Data} Data

在这里插入图片描述

S o l u t i o n \mathrm{Solution} Solution

很显然根据数据规模我们可以确定是状态压缩,我们考虑如何转移。

状态很简单,用 f [ s ] [ i ] f[s][i] f[s][i] 表示点即为 s s s ,且 1 1 1 号点最终停留到 j j j 号点,且 i i i j j j 路径只有一条的最大保留数。

状态的转移分成两种:

  • 当前转台转移到新的点: f [ i ] [ j ] + v ( j , k ) → f [ i ∪ k ] [ k ] f[i][j]+v(j,k)→f[i∪k][k] f[i][j]+v(j,k)f[ik][k]
  • 当两个联通块合并且以当前状态为终点时: f [ i ] [ j ] + S [ k ] → f [ i ∪ k ] [ j ] f[i][j]+S[k]→f[i∪k][j] f[i][j]+S[k]f[ik][j]
    S S S表示保留点集为 S S S的所有边的边权和。

接下来考虑如何求解 S S S,显然我们可以采用递推的方法.比如当前状态为 S S S,可以枚举 j j j,用S更新 S ∪ j S∪j Sj

C o d e \mathrm{Code} Code

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>

using namespace std;
const int N = 20;

int read(void)
{
	int s = 0, w = 0; char c = getchar();
	while (c < '0' or c > '9') w |= c == '-', c = getchar();
	while (c >= '0' and c <= '9') s = s*10+c-48, c = getchar();
	return w ? -s : s;
}

int n, m, sum = 0;
int f[1<<N][N], S[1<<N], val[N][N], in[1<<N];

vector <int> a[N];

int main(void)
{
	n = read(), m = read();
	memset(f,-1,sizeof f);
	for (int i=1,x,y,v;i<=m;++i)
	{
		x = read()-1, y = read()-1, v = read();
		val[x][y] = val[y][x] = v;
		sum += v;
		a[x].push_back(y);
		a[y].push_back(x);
	}
	for (int i=0;i<1<<n;++i)
	    for (int j=0;j<n;++j)
			if (((i>>j) & 1) == 0 and in[i|(1<<j)] == 0) {
	        	int cnt = S[i];
	        	for (int k=0;k<a[j].size();++k)
	        	    if ((i >> a[j][k]) & 1) cnt += val[j][a[j][k]];
	        	S[i|(1<<j)] = cnt;
	        }
	f[1][0] = 0;
	for (int i=0;i<1<<n;++i)
	    for (int j=0;j<n;++j) if (((i >> j) & 1) and f[i][j] != -1)
	    {
	    	if (f[i][j] == -1) continue;
	    	for (int k=0;k<n;++k) if (((i >> k) & 1) == 0 && val[j][k]) 
	    	    f[i|(1<<k)][k] = max(f[i|(1<<k)][k],f[i][j]+val[j][k]);
	    	int t = (1<<j) | (((1<<n)-1)^i);
	    	for (int k=t;k;k=(k-1)&t) 
	    		if ((k >> j) & 1) f[i|k][j] = max(f[i|k][j],f[i][j]+S[k]);
	    }  
	cout<<(sum - f[(1<<n)-1][n-1]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值