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;
}