传送门:https://www.luogu.org/problemnew/show/P1330
很神奇的做法,之前没有仔细思考,贪心+优先队列去找尽可能连接多条边的点,但其实是有问题的,这样贪心的结果很可能是没有解的,而选取其他的没那么多条边的点去操作,反而是可能有解的,所以贪心不可取。
看了大佬们的思路才理解到这个题可以这样设计(思路借鉴的,已注明转载),这个题可以分为若干个联通图,那么我们发现,对于某一条边来说,如果他连接的一个节点被选取了,那么他的另一个节点一定是没有被选取的,那么利用这个思路,我们发现,对于一个联通图来说,我们随意一个节点之后,剩下的节点的状态一定是全部确定的,因为图联通,随意一个点经过一条路径一定可以到达联通图上的任意一个点,那么这个图就只有两个状态了,第一个是我们选取的那个节点被使用,和我们选取的那个节点不被使用,那么两种图我们取其中最小的使用个数,然后对个联通图的结果求和就是我们所需要的答案。
因为关系是一层一层推进的,所以bfs写法特别的方便,而且如果我们的下一层节点的使用情况已经标记,那么需要判断和当前我们的节点标记是否一致,如果一致说明这个方法会有一条边两个节点都选取了的情况,那么输出impossible即可。
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int gcd(int a,int b){if (b == 0) return a; return gcd(b , a%b);}
int lcm(int a, int b){ return a/gcd(a,b)*b;}
inline int read(){
int f = 1, x = 0;char ch = getchar();
while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int maxn = 1e5 + 10;
int vis[maxn];
vector<int> v[maxn];
queue<int> q,q1;
int main(){
int n = read(),m = read(),sum = 0;
for (int i = 0; i < m; ++i) {
int a = read(), b = read();
v[a].push_back(b);
v[b].push_back(a);
}
for (int i = 1; i <= n; ++i) {
if (vis[i] == 0){
q.push(i);
vis[i] = 1;
int cnt1 = 0,cnt2 = 0;// 1 代表染色,-1代表不染色
while(!q.empty()) {
int now = q.front(), tag = 1;
q.pop();
if (vis[now] == 1) cnt1++;
else cnt2++;
for (int i = 0; i < v[now].size(); ++i) {
if (vis[v[now][i]] == vis[now]) {
cout << "Impossible" << endl;
return 0;
}
if (vis[v[now][i]] == 0) {
q.push(v[now][i]);
vis[v[now][i]] = -vis[now];
}
}
}
while (!q.empty()) q.pop();
sum += min(cnt1,cnt2);
}
}
cout << sum << endl;
return 0;
}