3400. 【GDOI2014模拟】旅行

6 篇文章 0 订阅

题目大意

给你一个n个点m条边的图,然后需要选择若干条边,使得 i i i n − i + 1 n-i+1 ni+1 联通且 1 < = i < = k 1<=i<=k 1<=i<=k ( k < = 4 ) (k<=4) (k<=4)

解题思路

介于我们是找若干个联通块使得两个点联通,那么我们可以分成两步来做。
一、对于每个联通块分别处理出它的最小代价
其实就是一个斯坦纳树。
由于是求最小代价,所以一个联通块一定是一棵树。
具体来说,设 f i , S f_{i,S} fi,S 表示以第 i i i 个点为根,然后关键点间是否联通的状态为 S S S
然后列出方程:
T ∈ S T\in S TS ,则 f i , S = m i n { f i , T + f i , S ⊕ T } f_{i,S}=min\{f_{i,T}+f_{i,S\oplus T}\} fi,S=min{fi,T+fi,ST}
但同时,我们还需要更新 S S S 相同时的情况
那么 f v , S = m i n { f u , S + w v , u } f_{v,S}=min\{f_{u,S}+w_{v,u}\} fv,S=min{fu,S+wv,u} v v v u u u 有连边
这个转移十分像最短路的,由此,我们也应该用spfa或dij做
二、合并联通块
我们设 g [ S ] g[S] g[S] 表示使集合 S S S 联通的最小代价。
那么 g [ S ] = m i n { m i n i = 1 n { f i , S } , g [ T ] + g [ S ⊕ T ] } g[S]=min\{min_{i=1}^n\{f_{i,S}\},g[T]+g[S\oplus T]\} g[S]=min{mini=1n{fi,S},g[T]+g[ST]} S , T , S ⊕ T S,T,S\oplus T S,T,ST 满足k对城市要不都联通要不都不连通。

最后输出即可。

Code

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e4+5;
struct node{
	int to,next,w;
}e[2*N];
int cnt,n,k,m,INF;
int head[N],g[1<<10],f[N][1<<10];
bool vis[N];
queue<int>q;
void add(int u,int v,int w){
	e[++cnt].to=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
	return;
}
void spfa(int S){
	while (!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for (int i=head[u];i;i=e[i].next){
			int v=e[i].to;
			if (f[v][S]>f[u][S]+e[i].w){
				f[v][S]=f[u][S]+e[i].w;
				if (!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return;
}
bool pd(int S){
	for (int i=1;i<=k;++i)
		if (((S&(1<<(i-1)))>0)^((S&(1<<(2*k-i)))>0)) return 0;
	return 1;
} 
int main(){
	scanf("%d%d%d",&n,&m,&k);
	int x,y,z;
	for (int i=1;i<=m;++i){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	memset(f,127/3,sizeof(f));
	INF=f[0][0];
	for (int i=1;i<=k;++i)
		f[i][1<<(i-1)]=f[n-i+1][1<<(2*k-i)]=0;
	for (int S=1;S<(1<<(2*k));++S){
		while (!q.empty()) q.pop();
		for (int i=1;i<=n;++i){
			for (int T=(S-1)&S;T;T=(T-1)&S)
				f[i][S]=min(f[i][S],f[i][T]+f[i][S^T]);
			if (f[i][S]!=INF) q.push(i),vis[i]=1;
			else vis[i]=0;
		}
		spfa(S);
	}
	g[1]=INF;
	for (int S=1;S<(1<<(2*k));++S,g[S]=INF)
		for (int i=1;i<=n;++i)
			g[S]=min(g[S],f[i][S]);
	for (int S=1;S<(1<<(2*k));++S){
		if (!pd(S)) continue;
		for (int T=(S-1)&S;T;T=(T-1)&S){
			if (!pd(T)||!pd(S^T)) continue;
			g[S]=min(g[S],g[T]+g[S^T]);
		}
	}
	if (g[(1<<(2*k))-1]!=INF) printf("%d",g[(1<<(2*k))-1]);
	else puts("-1");
	return 0; 
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值