【CodeForces】【生成树】【状压DP】1149D Abandoning Roads

CodeForces 1149D Abandoning Roads

题目大意

给定一张无向图,边权只有 A , B ( A < B ) A,B(A<B) A,B(A<B)两种情况,要求求出在这张图上的所有最小生成树中,从 1 1 1 i i i的路径的最小值。

分析

结论(1): 边权为 A A A的边必须被选做生成树上的边。

证明(1): 根据 Kruskal 算法求解生成树的过程显然可得。

这样一来整张图就被分成了许多个连通块。问题就变为用边权为 B B B的边连接这些散着的连通块,使得 1 1 1 i i i的路径长度最小。

结论(2): 若两个点之间的路径中有一条只有 A A A边,那么两个点在生成树上的路径上不可能有 B B B边。

证明(2): 由生成树的性质可得显然正确。

结论(3): 一条路径可能在最小生成树上当且仅当这条路径没有离开当前连通块再回来。注意若一个连通块中有 B B B边也不能够走。

这样一来就可以用状压 DP 来做了。

考虑状压 DP ,设状态 f ( S , u ) f(S,u) f(S,u)为当前在点 u u u,已经经过了 S S S中的所有连通块的路径长度最小值。

由于 f ( S , u ) f(S,u) f(S,u) f ( S , v ) f(S,v) f(S,v)会互相影响,所以我们用最短路来转移。

时间复杂度为 O ( 2 N N log ⁡ 2 ( 2 N N ) ) O(2^NN\log_2(2^NN)) O(2NNlog2(2NN)),但由于 N ≤ 70 N\le 70 N70,这样做显然时间和空间都要炸裂。

考虑优化:

结论(4): 连通块中点数小于等于 3 3 3的可以不压进状态中。

证明(4): 由于离开一个连通块再回来必须经过至少两条边权为 B B B的边(除非是直接用 B B B边连起来的)。而当连通块的点数小于等于 3 3 3时连通块中任意两个点都可以通过不超过两条 A A A边互相到达。显然对于这样的一个连通块,我们走 A A A边比走 B B B边更好。

这样一来我们需要压进状态中的连通块个数就不会超过 18 18 18个了。于是复杂度变成大约 O ( 2 18 N log ⁡ 2 ( 2 18 N ) ) O(2^{18}N\log_2(2^{18}N)) O(218Nlog2(218N))

参考代码

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int Maxn = 70;
const int Maxm = 200;

struct DSU {
	int fa[Maxn + 5];
	void init(int n) {
		for(int i = 1; i <= n; i++)
			fa[i] = i;
	}
	int find(int u) {
		return u == fa[u] ? u : fa[u] = find(fa[u]);
	}
	bool unite(int u, int v) {
		u = find(u), v = find(v);
		if(u == v) return false;
		fa[u] = v;
		return true;
	}
};

struct Edges {
	int u, v, w;
	bool operator < (const Edges &rhs) const {return w < rhs.w;}
};

struct QNode {
	int sta, u;
	int dis;
	QNode(){}
	QNode(int _sta, int _u, int _dis) {sta = _sta, u = _u, dis = _dis;}
	bool operator < (const QNode &rhs) const {
		return dis > rhs.dis;
	}
};

struct Edge {
	int to, dis;
	Edge *nxt;
};
Edge pool[Maxm * 2 + 5];
Edge *G[Maxn + 5], *ecnt = &pool[0];
void addedge(int u, int v, int w) {
	Edge *p = ++ecnt;
	p->to = v, p->dis = w;
	p->nxt = G[u], G[u] = p;
}

int N, M, A, B;
Edges e[Maxm + 5];

DSU st;
void Kruskal() {
	st.init(N);
	sort(e + 1, e + M + 1);
	for(int i = 1; i <= M; i++) {
		int u = e[i].u, v = e[i].v;
		if(e[i].w == B || !st.unite(u, v)) continue;
	}
}

int siz[Maxn + 5];
int id[Maxn + 5], nid[Maxn + 5];

int ans[Maxn + 5];

int dis[Maxn + 5][(1 << 18) + 5];
bool vis[Maxn + 5][(1 << 18) + 5];
void Dijkstra() {
	priority_queue<QNode> q;
	memset(dis, 0x3f, sizeof dis);
	memset(vis, false, sizeof vis);
	q.push(QNode(0, 1, 0)), dis[1][0] = 0;
	while(!q.empty()) {
		QNode tmp = q.top();
		q.pop(), ans[tmp.u] = min(ans[tmp.u], tmp.dis);
		if(vis[tmp.u][tmp.sta]) continue;
		int u = tmp.u, s = tmp.sta;
		vis[u][s] = true;
		for(Edge *p = G[u]; p != NULL; p = p->nxt) {
			int v = p->to;
			if(id[v] == id[u] && p->dis == B) continue;
			//不能在连通块内部走边权为 B 的边
			if(nid[v] && (s & (1 << (nid[v] - 1)))) continue;
			//不能经过重复的连通块
			int t = s;
			if(nid[u] && nid[v] != nid[u]) t |= (1 << (nid[u] - 1));
			if(dis[v][t] > dis[u][s] + p->dis) {
				dis[v][t] = dis[u][s] + p->dis;
				q.push(QNode(t, v, dis[v][t]));
			}
		}
	}
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %d %d %d", &N, &M, &A, &B);
	for(int i = 1; i <= M; i++) {
		scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);
		addedge(e[i].u, e[i].v, e[i].w);
		addedge(e[i].v, e[i].u, e[i].w);
	}
	Kruskal();
	int cnt = 0;
	for(int i = 1; i <= N; i++) {
		siz[st.find(i)]++;
		if(st.find(i) == i) id[i] = ++cnt;
	}
	for(int i = 1; i <= N; i++) id[i] = id[st.find(i)];
	cnt = 0;
	for(int i = 1; i <= N; i++)
		if(siz[i] >= 4) nid[i] = ++cnt;
	for(int i = 1; i <= N; i++) nid[i] = nid[st.find(i)];
	memset(ans, 0x3f, sizeof ans);
	Dijkstra();
	for(int i = 1; i < N; i++)
		printf("%d ", ans[i]);
	printf("%d\n", ans[N]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值