【CodeForces】【生成樹】【狀壓DP】1149D Abandoning Roads

CodeForces 1149D Abandoning Roads

題目大意

給定一張無向圖,邊權只有A,B(A<B)A,B(A<B)兩種情況,要求求出在這張圖上的所有最小生成樹中,從11ii的路徑的最小值。

分析

結論(1): 邊權爲AA的邊必須被選做生成樹上的邊。

證明(1): 根據 Kruskal 算法求解生成樹的過程顯然可得。

這樣一來整張圖就被分成了許多個連通塊。問題就變爲用邊權爲BB的邊連接這些散着的連通塊,使得11ii的路徑長度最小。

結論(2): 若兩個點之間的路徑中有一條只有AA邊,那麼兩個點在生成樹上的路徑上不可能有BB邊。

證明(2): 由生成樹的性質可得顯然正確。

結論(3): 一條路徑可能在最小生成樹上當且僅當這條路徑沒有離開當前連通塊再回來。注意若一個連通塊中有BB邊也不能夠走。

這樣一來就可以用狀壓 DP 來做了。

考慮狀壓 DP ,設狀態f(S,u)f(S,u)爲當前在點uu,已經經過了SS中的所有連通塊的路徑長度最小值。

由於f(S,u)f(S,u)f(S,v)f(S,v)會互相影響,所以我們用最短路來轉移。

時間複雜度爲O(2NNlog2(2NN))O(2^NN\log_2(2^NN)),但由於N70N\le 70,這樣做顯然時間和空間都要炸裂。

考慮優化:

結論(4): 連通塊中點數小於等於33的可以不壓進狀態中。

證明(4): 由於離開一個連通塊再回來必須經過至少兩條邊權爲BB的邊(除非是直接用BB邊連起來的)。而當連通塊的點數小於等於33時連通塊中任意兩個點都可以通過不超過兩條AA邊互相到達。顯然對於這樣的一個連通塊,我們走AA邊比走BB邊更好。

這樣一來我們需要壓進狀態中的連通塊個數就不會超過1818個了。於是複雜度變成大約O(218Nlog2(218N))O(2^{18}N\log_2(2^{18}N))

參考代碼

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章