idea的一些技巧

iOS 多线程 GCD

  返回  

【状压DP】吃货JYY(luogu 6085/jzoj 3290)

2021/7/21 8:04:53 浏览:

正题

luogu 6085
jzoj 3290


题目大意

给你一个无向图,其中有一些边是必须走的,问你从1开始走,经过所有必须走的边,然后回到1的最短路径


解题思路

n很小,可以先用Floyd跑出两个点之间的最短路

然后状压DP,每个点存三种状态:
0.不在连通图内
1.在连通图内且连边为奇数
2.在连通图内且连边为偶数

遍历所有状态,每个状态找不在连通图内的点,然后和连通图内一点连边,然后记录奇偶性(必要的边不记录)

枚举完所有状态后,把必要的边加进里面,然后对于连边为奇数的,找到一种最小代价连接方式,这样可以构造一条回路


代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 1594323
using namespace std;
int n, m, k, x, y, z, p, MX, nec, sum, ans, tot, h[100], v[100], g[10000], deg[100], dis[100][100];
int f[N + 10];
queue<int>d;
struct rec
{
	int to, l, next;
}e[200];
void add(int x, int y, int z)
{
	e[++tot].to = y;
	e[tot].l = z;
	e[tot].next = h[x];
	h[x] = tot;
}
int main()
{
	memset(dis, 127/3, sizeof(dis));
	memset(g, 127/3, sizeof(g));
	memset(f, 127/3, sizeof(f));
	MX = g[0];
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i)
	{
		scanf("%d%d%d", &x, &y, &z);
		x--;
		y--;
		add(x, y, z);
		add(y, x, z);
		dis[x][y] = dis[y][x] = min(dis[x][y], z);
		nec ^= (1<<x) ^ (1<<y);
		deg[x]++;
		deg[y]++;
		sum += z;
	}
	scanf("%d", &m);
	for (int i = 1; i <= m; ++i)
	{
		scanf("%d%d%d", &x, &y, &z);
		x--;
		y--;
		dis[x][y] = dis[y][x] = min(dis[x][y], z);
	}
	v[0] = 1;
	dis[0][0] = 0;
	for (int i = 1; i <= n; ++i)
		v[i] = v[i - 1] * 3, dis[i][i] = 0;
	for (int k = 0; k < n; ++k)
		for (int i = 0; i < n; ++i)
			for (int j = 0; j < n; ++j)
				dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);//Floyd
	g[0] = 0;
	for (int now = 0; now < (1<<n); ++now)
		for (int i = 0; i < n; ++i)
			if (!(now & (1<<i)))
				for (int j = i + 1; j < n; ++j)
					if (!(now & (1<<j)))
					{
						k = now ^ (1<<i) ^ (1<<j);
						g[k] = min(g[k], g[now] + dis[i][j]);//预处理奇数点的连边
					}
	d.push(2);
	f[2] = 0;
	while(!d.empty())
	{
		int now = d.front();
		d.pop();
		for (int i = 0; i < n; ++i)
		{
			if (now / v[i] % 3) continue;
			k = now + v[i] * 2;//必须的连边不算奇偶性
			for (int j = h[i]; j; j = e[j].next)
			{
				if (!(now / v[e[j].to] % 3)) continue;
				if (f[k] >= MX) d.push(k);
				f[k] = min(f[k], f[now]);
			}
			for (int j = 0; j < n; ++j)//不必须的连边
			{
				if (!(now / v[j] % 3)) continue;
				k = now + v[i];
				if (now / v[j] % 3 == 2) k -= v[j];//计算奇偶性
				else k += v[j];
				if (f[k] >= MX) d.push(k);
				f[k] = min(f[k], f[now] + dis[j][i]);
			}
		}
	}
	ans = MX;
	for (int now = 0; now <= v[n]; ++now)
	{
		p = 0;
		k = 0;
		for (int i = 0; i < n; ++i)
		{
			if (!(now / v[i] % 3) && deg[i])//有必须的点不在连通图内
			{
				p = 1;
				break;
			}
			if (now / v[i] % 3) k ^= (1<<i) * (now / v[i] % 3 == 1? 1: 0);//计算奇偶性
		}
		if (p) continue;
		k ^= nec;
		ans = min(ans, f[now] + g[k]);
	}
	printf("%d", sum + ans);
	return 0;
}

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号