CF587D Duff in Mafia

洛谷题面

\(\verb!riddle!\) 外的另一道前后缀优化建图题qwq。

题目大意

给你一张 \(n\) 个点 \(m\) 条边的无向图,边有颜色和边权。你要从中删去一些边,满足:

  1. 任意两条删掉的边没有公共的顶点。

  2. 任意两条剩余的、颜色相同的边没有公共的顶点。

  3. 删去的边的边权最大值最小。

求出这个最小值,并输出方案。

题目分析

通过“最大值最小”我们不难想到使用二分答案求解,我们二分一个值 \(mid\),这个 \(mid\) 就是当前我们认为的删去边权中的最大值,所以只能删去边权小于等于 \(mid\) 的边。

如果我们删了一些边,那么跟这条边有公共顶点的边都不能删。这启发我们使用 \(\rm 2-SAT\) 求解。

为方便表示,我们用 \(x_i\) 表示删第 \(i\) 条边,\(x_i'\) 表示不删第 \(i\) 条边。

不妨先考虑暴力算法:

如果第 \(i\) 条边的权值大于 \(mid\) 了,那说明这条边肯定不能连,我们连边 \(<x_i,x_i'>\)。在这些边中,对于一个点 \(v\),它在无向图中连出 \(x_1,x_2,\cdots,x_k\) (这些边都是满足条件的,并且这些边的颜色都相同),连边 \(<x_i',x_j>,<x_j',x_i>\)

删除的边中,对于一个点 \(u\),它在无向图中连出 \(x_1,x_2\cdots,x_l\) (这些边都是满足条件的),连边 \(<x_i,x_j'>,<x_j,x_i'>\),表示连 \(x_i\) 就不能连其他边了。

惊奇的发现这样子复杂度是 \(\mathcal{O(m^2)}\) 的,然后就要用到前后缀优化建图了。


按照前面的算法连出的图是这样的:

我们发现连的边太多了,每新加一个点就会再连几条边,我们要找到一种连边总数更少的建图方式。

引入“前后缀优化建图”——

我们连边可以优化一下,如图:

显然 \(9\to 15\) 等价于 \(9\to 10\to 15\),而类似的,我们会发现只要将第二层和第三层每一层的每个节点之间都互相连边就可以了,然后再稍稍优化下得到:

(上图中的 \(n\) 改为 \(m\)

这样,如果我们要新加一个点,那么直接连这个点在“前缀层”中的前一个点,以及这个点在“后缀层”中的后一个点,这和之前的暴力连边是等效的。

代码

口胡起来似乎很简单?但我还是调了 \(2h+\)/kk

// Problem: CF587D Duff in Mafia
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF587D
// Memory Limit: 250 MB
// Time Limit: 6000 ms
// Date:2022-05-29 08:03
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <numeric>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#define enter putchar(10)
#define debug(c,que) std::cerr << #c << " = " << c << que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i = (st);i <= (ed); ++ i) std::cout << arr[i] << w;
#define speed_up() std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0)
#define mst(a,k) memset(a,k,sizeof(a))
#define stop return(0)
const int mod = 1e9 + 7;
inline int MOD(int x) {
	if(x < 0) x += mod;
	return x % mod;
}
namespace Newstd {
	inline int read() {
		int ret = 0,f = 0;char ch = getchar();
		while (!isdigit(ch)) {
			if(ch == '-') f = 1;
			ch = getchar();
		}
		while (isdigit(ch)) {
			ret = (ret << 3) + (ret << 1) + ch - 48;
			ch = getchar();
		}
		return f ? -ret : ret;
	}
	inline double double_read() {
		long long ret = 0,w = 1,aft = 0,dot = 0,num = 0;
		char ch = getchar();
		while (!isdigit(ch)) {
			if (ch == '-') w = -1;
			ch = getchar();
		}
		while (isdigit(ch) || ch == '.') {
			if (ch == '.') {
				dot = 1;
			} else if (dot == 0) {
				ret = (ret << 3) + (ret << 1) + ch - 48;
			} else {
				aft = (aft << 3) + (aft << 1) + ch - '0';
				num ++;
			}
			ch = getchar();
		}
		return (pow(0.1,num) * aft + ret) * w;
	}
	inline void write(int x) {
		if(x < 0) {
			putchar('-');
			x = -x;
		}
		if(x > 9) write(x / 10);
		putchar(x % 10 + '0');
	}
}
using namespace Newstd;

const int N = 5e4 + 5;
struct Edges {
	int u,v,col,w,id;
} edges[N];
struct Graph {
	int v,nxt;
} gra[N * 25];
int a[N],head[N * 10],dfn[N * 10],low[N * 10],col[N * 10],pre[N],suc[N],print[N];
bool in_stack[N * 10];
std::stack<int>st;
std::vector<int>G[N];
//G[i]:记录与 i 相连的边的编号 
int n,m,idx,num,cnt;
inline bool cmp1(Edges x,Edges y) {
	return x.w < y.w;
}
inline bool cmp2(int x,int y) {
	return edges[x].col < edges[y].col;
}
inline void add(int u,int v) {
	gra[++ idx] = (Graph){v,head[u]},head[u] = idx;
}
inline void tarjan(int now) {
	dfn[now] = low[now] = ++ num,in_stack[now] = true;
	st.push(now);
	for (register int i = head[now];i;i = gra[i].nxt) {
		int v = gra[i].v;
		if (!dfn[v]) {
			tarjan(v);
			low[now] = std::min(low[now],low[v]);
		} else if (in_stack[v]) {
			low[now] = std::min(low[now],dfn[v]);
		}
	}
	if (dfn[now] == low[now]) {
		cnt ++;
		int u;
		do {
			u = st.top();st.pop();
			in_stack[u] = false,col[u] = cnt;
		} while (u != now);
	}
}
inline void init() {
	idx = num = cnt = 0;
	while (!st.empty()) st.pop();
	mst(head,0),mst(gra,0),mst(dfn,0),mst(low,0),mst(col,0),mst(in_stack,false);
}
inline bool check(int now) {
	init();
	for (register int i = now + 1;i <= m; ++ i) add(i,i + m);
	int num_idx = 2 * m;
	for (register int i = 1;i <= n; ++ i) {
		int num_ver = 0;
		for (auto &item : G[i]) a[++ num_ver] = item;
		std::sort(a + 1,a + num_ver + 1,cmp2);
		for (register int j = 1;j <= num_ver; ++ j) pre[j] = ++ num_idx,suc[j] = ++ num_idx;
		for (register int j = 1;j <= num_ver; ++ j) {
			add(a[j],pre[j]),add(suc[j],a[j] + m);
		}
		for (register int j = 2;j <= num_ver; ++ j) {
			add(pre[j - 1],pre[j]),add(suc[j],suc[j - 1]);
			add(pre[j - 1],a[j] + m),add(a[j],suc[j - 1]);
		}
		for (register int j = 1;j <= num_ver; ++ j) {
			pre[j] = ++ num_idx,suc[j] = ++ num_idx;
		}
		int r;
		for (register int j = 1;j <= num_ver;j = r + 1) {
			r = j;
			while (r < num_ver && edges[a[j]].col == edges[a[r + 1]].col) r ++;
			for (register int k = j;k <= r; ++ k) {
				add(pre[k],a[k]),add(a[k] + m,suc[k]);
			}
			for (register int k = j + 1;k <= r; ++ k) {
				add(a[k] + m,pre[k - 1]),add(suc[k - 1],a[k]);
				add(pre[k],pre[k - 1]),add(suc[k - 1],suc[k]); 
			}
		}
	}
	for (register int i = 1;i <= num_idx; ++ i) {
		if (!dfn[i]) {
			tarjan(i);
		}
	}
	for (register int i = 1;i <= m; ++ i) {
		if (col[i] == col[i + m]) {
			return false;
		}
	}
	return true;
}
int main(void) {
	n = read(),m = read();
	for (register int i = 1;i <= m; ++ i) {
		edges[i].u = read(),edges[i].v = read(),edges[i].col = read(),edges[i].w = read();
		edges[i].id = i;
	}
	std::sort(edges + 1,edges + m + 1,cmp1);
	for (register int i = 1;i <= m; ++ i) {
		G[edges[i].u].push_back(i),G[edges[i].v].push_back(i);
	}
	int l = 0,r = m,ans = -1;
	while (l <= r) {
		int mid = l + r >> 1;
		if (check(mid) == true) r = mid - 1,ans = mid;
		else l = mid + 1;
	}
	if (ans == -1) {
		puts("No");
	} else {
		puts("Yes");
		check(ans);
		int asks = 0;
		for (register int i = 1;i <= m; ++ i) {
			if (col[i] < col[i + m]) {
				print[++ asks] = edges[i].id;
			}
		}
		printf("%d %d\n",edges[ans].w,asks);
		for (register int i = 1;i <= asks; ++ i) printf("%d ",print[i]);
	}
	
	return 0;
}
posted @ 2022-05-29 12:41  Coros_Trusds  阅读(23)  评论(0编辑  收藏  举报