#欧拉回路,状压dp,Floyd#洛谷 6085 [JSOI2013]吃货 JYY

题目传送门


分析

先用Floyd求出两点间的最短距离,包含必经边的欧拉回路,

先考虑欧拉回路的性质就是度数为偶数且连通,那如果有偶数个奇点可以两两配对。

\(g[S]\) 表示选择 \(S\) 中的点作为奇点时最少需要的代价,则 \(g[S|2^j|2^k]=\min\{g[i]+dis[j][k]\}\)

然后考虑怎样连起来,设 \(f[S]\) 表示状态为 \(S\) 的最小代价,三进制位的0表示没有与1连通,1表示连通且度数为奇数,2表示连通且度数为偶数。

这个状态度数并不包含必经边,直接枚举必经边的端点和连通的点转移一下,最后再将必经边的度数合起来,时间复杂度 \(O(3^nn^2)\)


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#include <queue>
using namespace std;
const int N=16;
struct node{int y,w,next;}e[N*10]; queue<int>q; int f[1600001],g[9001];
int n,m,ans=0x3f3f3f3f,dis[N][N],et=1,as[N],a[N],_2[N],_3[N],deg[N];
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void add(int x,int y,int w){
    e[++et]=(node){y,w,as[x]},as[x]=et;
	e[++et]=(node){x,w,as[y]},as[y]=et;;
}
int min(int a,int b){return a<b?a:b;}
void doit(){
	q.push(2); 
	while (!q.empty()){
		int x=q.front(),tot=0; q.pop();
		for (int i=0;i<n;++i)
		    if ((x/_3[i])%3) a[++tot]=i;
		for (int i=0;i<n;++i)
		if ((x/_3[i])%3==0){
			for (int j=as[i+1];j;j=e[j].next)
			if ((x/_3[e[j].y-1])%3){
				int y=x+_3[i]*2;
				if (f[y]<=f[x]) continue;
				if (f[y]==0x3f3f3f3f) q.push(y);
				f[y]=f[x];
			}
			for (int j=1;j<=tot;++j){
				int y=x+_3[i];
				if ((x/_3[a[j]])%3==1) y+=_3[a[j]];
				    else y-=_3[a[j]];
				if (f[x]+dis[i+1][a[j]+1]>=f[y]) continue;
				if (f[y]==0x3f3f3f3f) q.push(y);
				f[y]=f[x]+dis[i+1][a[j]+1];
			}
		}
	}
}
int main(){
	n=iut(),_2[0]=_3[0]=1;
	memset(dis,0x3f,sizeof(dis));
	memset(g,0x3f,sizeof(g)),g[0]=0;
	memset(f,0x3f,sizeof(f)),f[2]=0;
	for (int i=1;i<=n;++i) _2[i]=_2[i-1]*2,_3[i]=_3[i-1]*3;
	for (int T=iut();T;--T){
		int x=iut(),y=iut(),w=iut();
		dis[x][y]=dis[y][x]=w;
		++deg[x],++deg[y],add(x,y,w);
	}
	for (int T=iut();T;--T){
		int x=iut(),y=iut(),w=iut();
		dis[x][y]=dis[y][x]=min(dis[x][y],w);
	}
	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]);
	for (int i=0;i<_2[n];++i)
	for (int j=0;j<n;++j) if (!((i>>j)&1))
	for (int k=j+1;k<n;++k) if (!((i>>k)&1))
	    g[i|_2[j]|_2[k]]=min(g[i|_2[j]|_2[k]],g[i]+dis[j+1][k+1]);
    doit();
    for (int S=0;S<_3[n];++S){
    	bool flag=0;
    	for (int i=0;i<n;++i)
		if (as[i+1]&&(S/_3[i])%3==0){
			flag=1; break;
		}
		if (flag) continue;
		int now=S;
		for (int i=0;i<n;++i) if (deg[i+1]&1)
		    now+=((S/_3[i])%3==1)?_3[i]:-_3[i];
		int _S=0;
		for (int i=0;i<n;++i)
		if ((now/_3[i])%3==1)
		    _S|=_2[i];
		ans=min(ans,f[S]+g[_S]);
	}
	for (int i=2;i<=et;i+=2) ans+=e[i].w;
	return !printf("%d",ans);
}
posted @ 2022-03-23 08:40  lemondinosaur  阅读(34)  评论(0编辑  收藏  举报