【状压DP】BZOJ4479吃货jyy

分析:

把问题转化一下,所谓的回路只需要满足:每个点得度数均为偶数即可。
然后可以先把必走的边忽略,让这些边联通且满足上述条件。
3 n 3^n 3n的DP,表示每一个点:不连通/度数为奇/度数为偶。(这里要特别注意必走边,尽管我们忽略了它们,但是它们是实际存在的,要参与连通性计算,只不过代价为0,因为它们的代价我们会最后加上)

然后再搞一个 2 n 2^n 2n的DP,表示链接某些点的最小代价。

然后对每个 3 n 3^n 3n的状态,找到哪些点应该补度数。求出一个对应的 2 n 2^n 2n的状态。两个状态之和加上最开始忽略的必走边就是答案。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define SF scanf
#define PF printf
#define MAXS 1594423
#define MAXN 14
#define INF 0x3f3f3f3f
#define MAXB 8292
using namespace std;
int n,m,w1;
int W;
struct node{
	int x;
	node *nxt;	
}edge[MAXN*10];
node *head[MAXN],*ncnt=edge;
int b[MAXN],p[MAXN];
int dp[MAXS],f[MAXB];
void add_edge(int x,int y){
	ncnt++;
	ncnt->x=y;
	ncnt->nxt=head[x];
	head[x]=ncnt;
}
int c(int s,int pos){
	return (s/p[pos])%3;	
}
int tr(int s,int pos){
	if(c(s,pos)==1)
		return s+p[pos];
	else
		return s-p[pos];
}
int dist[MAXN][MAXN];
void prepare(){
	for(int k=0;k<n;k++)
		for(int i=0;i<n;i++)
			for(int j=0;j<n;j++)
				dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
	b[0]=1;
	p[0]=1;
	for(int i=1;i<=n;i++){
		b[i]=b[i-1]*2;
		p[i]=p[i-1]*3;	
	}
	memset(f,0x3f,sizeof f);
	f[0]=0;
	for(int mask=0;mask<b[n];mask++){
		if(mask==INF)
			continue;
		for(int i=0;i<n;i++){
			if(mask&b[i])
				continue;
			for(int j=0;j<n;j++){
				if((mask&b[j])||j==i)
					continue;
				f[mask|b[i]|b[j]]=min(f[mask|b[i]|b[j]],f[mask]+dist[i][j]);
			}
		}
	}
}
void Update(int &x,int y){
	x=min(x,y);	
}
int used[MAXN];
queue<int> q;
void solve(){
	memset(dp,INF,sizeof dp);
	dp[2]=0;
	q.push(2);
	while(!q.empty()){
		int mask=q.front();
		q.pop();
		int tmp=0;
		for(int i=0;i<n;i++)
			if(c(mask,i)!=0)
				used[tmp++]=i;
		for(int i=0;i<n;i++)
			if(c(mask,i)==0){
				int mask1=mask+2*p[i];
				for(node *v=head[i];v!=NULL;v=v->nxt)
					if(c(mask1,v->x)!=0){
						if(dp[mask1]==INF)
							q.push(mask1);
						Update(dp[mask1],dp[mask]);
					}
				for(int j=0;j<tmp;j++){
					if(dist[i][used[j]]==INF)
						continue;
					mask1=tr(mask,used[j])+p[i];
					if(dp[mask1]==INF)
						q.push(mask1);
					Update(dp[mask1],dp[mask]+dist[i][used[j]]);
				}
			}
	}
}
bool check(int mask){
	for(int i=0;i<n;i++)
		if(head[i]!=NULL&&c(mask,i)==0)
			return 0;
	return 1;
}
int d[MAXN];
int get_ans(){
	int res=INF;
	for(int mask=2;mask<p[n];mask++){
		if(check(mask)==0)
			continue;
		int t=0;
		for(int i=0;i<n;i++){
			if((d[i]^((mask/p[i])%3==1))==1){
				t|=b[i];
			}
		}
		//PF("{%d %d %d %d %d %d %d}\n",mask,t,((mask/p[0])%3==1)^d[0],d[1],d[2],d[3],d[4]);
		Update(res,dp[mask]+f[t]);
	}
	return res;
}
int main(){
	SF("%d%d",&n,&m);
	memset(dist,INF,sizeof dist);
	int u,v;
	for(int i=1;i<=m;i++){
		SF("%d%d%d",&u,&v,&w1);	
		u--;
		v--;
		W+=w1;
		Update(dist[u][v],w1);
		dist[v][u]=dist[u][v];
		add_edge(u,v);
		add_edge(v,u);
		d[u]^=1;
		d[v]^=1;
	}
	SF("%d",&m);
	for(int i=1;i<=m;i++){
		SF("%d%d%d",&u,&v,&w1);
		u--;
		v--;
		Update(dist[u][v],w1);
		dist[v][u]=dist[u][v];
	}
	for(int i=0;i<n;i++)
		dist[i][i]=0;
	prepare();
	solve();
	PF("%d",get_ans()+W);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值