CF1687B 题解

考虑模拟 kruskal 的过程来求最小生成树,即:

首先我们可以用 \(m\) 次询问求出每条边的边权,
再按边权从小到大的顺序尝试加入每条边。

对于当前考虑的边,设其权为 \(w\) 且连接了 \(u,v\)
则我们需要判断,

是否存在一条左右端点分别为 \(u,v\) 的链,满足这条链上所有边的权值都小于 \(w\)

如果不存在,则等价于当前边不在最小生成树中,
而这个条件的判定,我们可以用两次询问做到。

具体的,我们先查询出:保留所有边权不超过 \(w\) 的边的最大生成树的边权和 \(x\)

我们再查询出:保留所有边权小于 \(w\) 的边并查询出最大生成树的边权和 \(x'\)

那么 \(x-x'=w\) 就等价于不存在一条满足上面要求的链,
原因是所有边的权值都是正的。

注意到某次查询出的 \(x'\) 实际上就是上一次查询出的 \(x\)
所以一共用 \(2m\) 次查询,就能求出最小生成树的权值和。

code :

#include <bits/stdc++.h>

#define fi first
#define se second
#define vi vector
#define db double
#define ep emplace
#define mp make_pair
#define pb push_back
#define LL long long
#define emp emplace_back
#define pii pair < int , int >
#define SZ(x) ((int)(x.size()))
#define all(x) x.begin(), x.end()
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)

using namespace std;

int read (char ch = 0, int x = 0, int f = 1) {
	while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}
void write (int x) {
	if (x < 0) putchar ('-'), x = -x;
	static int stk[45], top = 0;
	do stk[++top] = x % 10, x /= 10; while (x);
	while (top) putchar (stk[top] + '0'), top--;
}

const int N (505);

int n, m;
string s;
pii e[N];
int rk[N];

int qry() {
	cout << "? " << s << endl, cout.flush();
	int x; cin >> x; return x;
}

int main() {
	cin >> n >> m;
	rep (i, 0, m - 1) s.pb ('0');
	rep (i, 1, m) {
		s[i - 1] = '1';
		e[i].fi = qry(), e[i].se = i;
		s[i - 1] = '0';
	}
	sort (e + 1, e + m + 1);
	int mst = 0, lst = 0;
	rep (i, 1, m) {
		s[e[i].se - 1] = '1';
		int x = qry();
		if (x == lst + e[i].fi) {
			mst += e[i].fi;
		}
		lst = x;
	}
	cout << "! " << mst << endl, cout.flush();
	return 0;
}
posted @ 2022-06-19 22:38  GaryH  阅读(35)  评论(0编辑  收藏  举报