考点:最小生成树kruskal、交互
题意:现有 n n n 个点 m m m 条边的无向图,一共可提出 2 m 2m 2m 次疑问,提问方式为每次输出一个长度为 m m m 由字符 1 / 0 1/0 1/0 组成的字符串 s s s , s [ i ] = 0 s[i] = 0 s[i]=0 表示第 i i i 条边不存在, s [ i ] = 1 s[i] = 1 s[i]=1 表示第 i i i 条边存在。系统会给出由当前边所组成的最大森林(边权之和最大的森林),我们需要在 2 m 2m 2m 次提问中找出由所有边所构成的最小生森林((边权之和最大的森林))。
思路:题目是要求为求森林的值,在讨论的时候可以化繁为简,将最小森林变为最小树。以一个连通图为例
1.先利用 m m m 次的查询,获得每条边的权值 c n t i cnt_i cnti。2.我们可以发现最大生成树的权值为 2 + 4 + 8 + 5 = 19 2+4+8+5 = 19 2+4+8+5=19 ,在这里会发现一个现象,在成环的情况下会有不纳入计算中的边,同样由 k r u s k a l kruskal kruskal 算法可以得知,当某两点已经在同一个连通块中的时候,若还有关联这两点的边,则直接舍去。
3.创建一个集合 S S S 以及 r e s res res 记录当前集合所能构成的最小生成树的值,运用 k r u s k a l kruskal kruskal 的思想,将边权从小到大依次加入到集合 S S S 中,每次加入集合后,则进行一次询问,便会获得一个值 w i w_i wi ,如果 w i = c n t i + r e s w_i = cnt_i + res wi=cnti+res ,则表示当前边是构成最小生成树的边,然后 r e s = w i res = w_i res=wi。如果 w i ≠ c n t i + r e s w_i \not= cnt_i + res wi=cnti+res ,则表示当前边不是构成最小生成树的边,而且此时出现了环,所以需要将边 l i l_i li 从集合中去除。最后得到的 r e s res res 便是最小生成树的权值。
4.森林的做法与此相同。
#include<stdio.h>
#include<algorithm>
using namespace std;
const int N = 1e3 + 10;
char s[N];
int res;
struct node{
int id,cnt;
}num[N];
bool cmp(node a1,node a2) {
return a1.cnt < a2.cnt;
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
for(int i=0;i<m;i++) s[i] = '0';
for(int i=0;i<m;i++) {
s[i] = '1';
printf("? %s\n",s);
fflush(stdout);
s[i] = '0';
int k;
scanf("%d",&k);
num[i] = {i,k};
}
sort(num,num+m,cmp);
for(int i=0;i<m;i++) {
s[num[i].id] = '1';
printf("? %s\n",s);
fflush(stdout);
int k;
scanf("%d",&k);
if(k == res + num[i].cnt) res += num[i].cnt;
else s[num[i].id] = '0';
}
printf("! %d\n",res);
fflush(stdout);
return 0;
}