吃货JYY[JSOI2013][状压][欧拉回路]

文章目录

题目

n n n 个点 m m m 条边无向连通图,每条边有权值,指定 K K K 条边必须选,每条边可以经过多次,问从 1 1 1 出发遍历完必须边最后回到 1 1 1 的最小花费?
n ≤ 13 , 0 ≤ K ≤ 78 , 2 ≤ m ≤ 200 n\le 13,0\le K\le 78,2\le m\le 200 n13,0K78,2m200
吃货JYY

思路

边走多次相当于是添加边/加度数
显然类似欧拉回路,经过点集 S S S 的度数为偶数就能完成
发现限制还是蛮多的:

  • 走完必须边
  • 走回路
  • 费用最小

然后根据条件我们可以把点分成三类:未遍历的,奇点,偶点
由于指定 K K K 条边必须选,我们可以先将他们的贡献提取出来,转移时候只考虑他们对度数影响
考虑处理两点间最短路 d i s dis dis
f S f_S fS 表示点的状态集合为 S S S 的最小代价, f 2 = 0 f_2=0 f2=0
我们一个一个将未遍历的点加入集合
f t r a n s ( S , i , j ) = f S + d i s i , j , i ∈ S , j ∉ S f_{trans(S,i,j)}=f_S+dis_{i,j},i\in S,j\not \in S ftrans(S,i,j)=fS+disi,j,iS,jS
或者直接有必须边
f t r a n s ( S , i , j ) = f S , n d i , j & i ∈ S , j ∉ S f_{trans(S,i,j)}=f_S,nd_{i,j}\And i\in S,j\not \in S ftrans(S,i,j)=fS,ndi,j&iS,jS
然后我们再处理 g S g_S gS 表示将 S S S 中奇点两两连接最小代价
a n s = m i n ( f S + g S ′ + m u s t ) ans=min(f_S+g_{S'}+must) ans=min(fS+gS+must)
时间复杂度 O ( 3 n n 2 ) O(3^nn^2) O(3nn2)

代码


#include<set>
#include<map>
#include<cmath>
#include<deque>
#include<stack>
#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int read(){
	bool f=0;int x=0;char c=getchar();
	while(c<'0'||'9'<c){if(c=='-')f=1;c=getchar();}
	while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return !f?x:-x;
}
#define fi first
#define se second
#define mp make_pair
const int MAXN=13;
const int MAXK=78;
const int MAXM=200;
const int MAXS=1594323;
const int INF=0x3f3f3f3f;
int pw3[15];
bool vis[MAXS+5];
int Q[MAXS+5],hd,tl;
int dis[MAXN+5][MAXN+5];
bool atc[MAXN+5][MAXN+5]; 
int f[MAXS+5],g[(1<<MAXN)+5];
int U[MAXM+5],V[MAXM+5],W[MAXM+5];
int n,K,val,Mu[MAXK+5],Mv[MAXK+5],Mw[MAXK+5];
void Watch(int s,int b){
	int tmp=s;
	for(int i=0;i<n;i++)
		printf("%d ",s%b),s/=b;
	puts("");
	printf("%d\n",b==2?g[tmp]:f[tmp]); 
	return;
}
int main(){//f[s]:0/1/2:是否在集合内/奇偶性 时候最小代价 
	pw3[0]=1;
	n=read(),K=read(),val=0;
	for(int i=1;i<=n;i++)
		pw3[i]=3*pw3[i-1];
	memset(dis,0x3f,sizeof(dis));
	for(int i=1;i<=n;i++)
		dis[i][i]=0;
	int ms=0,key=0;
	for(int i=1;i<=K;i++){
		Mu[i]=read(),Mv[i]=read(),Mw[i]=read(),val+=Mw[i];
		atc[Mu[i]][Mv[i]]=1;
		atc[Mv[i]][Mu[i]]=1;
		dis[Mu[i]][Mv[i]]=min(Mw[i],dis[Mu[i]][Mv[i]]),dis[Mv[i]][Mu[i]]=min(dis[Mv[i]][Mu[i]],Mw[i]);
		ms|=(1<<(Mu[i]-1)),ms|=(1<<(Mv[i]-1));
		key^=(1<<(Mu[i]-1)),key^=(1<<(Mv[i]-1));
	}
	int m=read();
	for(int i=1;i<=m;i++)
		U[i]=read(),V[i]=read(),W[i]=read(),dis[U[i]][V[i]]=min(dis[U[i]][V[i]],W[i]),dis[V[i]][U[i]]=min(dis[V[i]][U[i]],W[i]);
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	memset(g,0x3f,sizeof(g));
	memset(f,0x3f,sizeof(f));
	g[0]=0,f[2]=0;
	for(int s=0;s<(1<<n);s++)
		for(int x=0;x<n;x++)
			if(!(s&(1<<x)))
				for(int y=x+1;y<n;y++)
					if(!(s&(1<<y)))
						g[s|(1<<x)|(1<<y)]=min(g[s|(1<<x)|(1<<y)],g[s]+dis[x+1][y+1]);
	hd=0,tl=1,Q[++hd]=2,vis[2]=1;
	while(hd<=tl){
		int s=Q[hd++];
		//Watch(s,3);
		for(int i=0;i<n;i++)
			if(s/pw3[i]%3==0)
				for(int j=0,b;j<n;j++)
					if((b=s/pw3[j]%3)){
						int id=s+pw3[i]+(b&1?1:-1)*pw3[j];
						f[id]=min(f[id],f[s]+dis[i+1][j+1]);//!!!!
						if(!vis[id]&&f[id]!=INF)
							vis[id]=1,Q[++tl]=id;
						id=s+2*pw3[i];
						if(atc[i+1][j+1]){
							f[id]=min(f[id],f[s]);
							if(!vis[id]&&f[id]!=INF)
								vis[id]=1,Q[++tl]=id;
						}
					}
	}
	int ans=INF;
	for(int s=2;s<pw3[n];s++)
		if(f[s]!=INF){
			int ns=0;
			for(int i=0;i<n;i++)
				if(((ms>>i)&1)&&!(s/pw3[i]%3))
					goto Break;
			for(int i=0,t;i<n;i++){
				t=s/pw3[i]%3;
				if(t==1) ns|=(1<<i);
				if((key>>i)&1)
					ns^=(1<<i);
			}
			ans=min(ans,f[s]+g[ns]+val);
			Break:;
		}
	printf("%d\n",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值