题目来源
题意:
给定一个 n 个点,m 条边无向连通图,每条边有权值
c
i
c_i
ci,各不相同。所以,其最小生成树是唯一的。
有
q
q
q 次询问,每次给出一条边:
x
i
,
y
i
,
w
i
x_i, y_i, w_i
xi,yi,wi,表示两端点为
x
i
x_i
xi 和
y
i
y_i
yi,权值为
w
i
w_i
wi。
问,加入这条边之后,该图的最小生成树会不会发生变化?
或者说,加入的这条边是否会在新的最小生成树中?
2
≤
N
≤
2
×
1
0
5
2≤N≤2×10^5
2≤N≤2×105
N
−
1
≤
M
≤
2
×
1
0
5
N−1≤M≤2×10^5
N−1≤M≤2×105
1
≤
Q
≤
2
×
1
0
5
1≤Q≤2×10^5
1≤Q≤2×105
分析:
如果每次询问都重新跑一遍最小生成树的话,复杂度太高。所以需要考虑离线操作。
将本地的边和询问的边放到一起排序,跑最小生成树。
按照权值从小到大遍历所有边:
- 如果当前边所连接的两个端点已经连通,那么这条边没有贡献,跳过;
- 如果两个端点没有连通:
1、如果这条边是本地的边,那么将两个端点所在连通块合并;
2、否则就是询问的边,则说明这条边是有贡献的,会出现在最小生成树中,将其标记下来。但是注意,不将其真正的放在生成树中,也就是不将其两端点所在连通块合并。
这样,便可以跑一遍生成树就可以判断所有询问边是否能在最小生成树中。
细节问题:
思路明确了,接下来就说说踩的坑。。
要标记出来询问的边,一开始想的做法是开一个结构体node,存下来这三个元素,然后用 map 标记。但是发现map中不能放结构体。
然后又想到可以像字符串哈希一样,把这三个数字映射成一个,然后用map标记。但是,131 和 13331 都映射不为唯一的。。
最后,在队友的提醒下,用vector把这三个数字存起来,用map把vector标记。心想,这总该唯一了吧。
但是,还是不对。
原因在于一个大细节问题!我把询问的边用map标记为1,如果发现这条边能够在最小生成树中就标记为2,然后这样写的:
vector<int> v;v.pb(x);v.pb(y);v.pb(z);
if(mp[v] == 1) mp[v] = 2;
else pre[find(x)] = find(y);
如果是询问的边,就把其标记为2,否则就是本地边,合并连通块。有什么问题么?
确实有问题。
因为题目中没说询问的点是不是互不相同的,也就是说,mp[v] 之前赋值为 1 之后,下一个又是同一个v,那么此时 mp[v] 已经不是 1 了,而是 2,那么就会执行 else 指令,将连通块合并!!于是就出现错误了。
解决方法是,把询问边的判断方式,改成 if(mp[v]) ...
。
啊,大细节!
下面是 AC 不易的代码:
方法1:vector映射
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
/**/
const int N = 500010, mod = 1e9+7;
int T, n, m;
map<vector<int>, int> mp;
struct node{
int x, y, z;
}a[N], b[N];
int pre[N], cnt;
int ans[N];
bool cmp(node a, node b){
return a.z < b.z;
}
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
void kruskal()
{
sort(a+1, a+cnt+1, cmp);
for(int i=1;i<=cnt;i++)
{
int x = a[i].x, y = a[i].y, z = a[i].z;
if(find(x) == find(y)) continue;
vector<int> v;v.pb(x);v.pb(y);v.pb(z);
if(mp[v]) mp[v] = 2;
else pre[find(x)] = find(y);
}
}
signed main(){
Ios;
int q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++) pre[i] = i;
while(m--)
{
int x, y, z;
cin >> x >> y >> z;
a[++cnt] = {x, y, z};
}
for(int i=1;i<=q;i++)
{
int x, y, z;
cin >> x >> y >> z;
a[++cnt] = {x, y, z};
b[i] = {x, y, z};
vector<int> v;v.pb(x);v.pb(y);v.pb(z);
mp[v] = 1;
}
kruskal();
for(int i=1;i<=q;i++)
{
int x = b[i].x, y = b[i].y, z = b[i].z;
vector<int> v;v.pb(x);v.pb(y);v.pb(z);
if(mp[v] == 2) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
方法2:
可以在存边的结构体中再开一个变量 id,记录当前边是不是询问边,是第几个询问边。然后如果有贡献的话将其对应的答案数组直接赋值。
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;
/**/
const int N = 500010, mod = 1e9+7;
int T, n, m;
struct node{
int x, y, w, id;
}a[N];
int ans[N];
int pre[N], cnt;
bool cmp(node a, node b){
return a.w < b.w;
}
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
void kruskal()
{
sort(a+1, a+cnt+1, cmp);
for(int i=1;i<=cnt;i++)
{
int x = a[i].x, y = a[i].y;
if(find(x) == find(y)) continue;
if(a[i].id) ans[a[i].id] = 1;
else pre[find(x)] = find(y);
}
}
signed main(){
Ios;
int q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++) pre[i] = i;
for(int i=1;i<=m;i++){
cnt ++;
cin >> a[cnt].x >> a[cnt].y >> a[cnt].w;
}
for(int i=1;i<=q;i++){
cnt ++;
cin >> a[cnt].x >> a[cnt].y >> a[cnt].w;
a[cnt].id = i;
}
kruskal();
for(int i=1;i<=q;i++){
if(ans[i]) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
方法3:哈希
其实,多试几个哈希值确实能水过去,这里用的是 13371。
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
/**/
const int N = 500010, mod = 1e9+7;
int T, n, m;
int e[N], h[N], ne[N], w[N], idx;
map<int, int> mp;
struct node{
int x, y, z;
}a[N], b[N];
int pre[N], cnt;
bool cmp(node a, node b){
return a.z < b.z;
}
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
int get(int x, int y, int z){
unsigned long long tx = x, ty = y, tz = z;
return tx * 13371*13371 + ty * 13371 + tz;
}
void kruskal()
{
sort(a+1, a+cnt+1, cmp);
for(int i=1;i<=cnt;i++)
{
int x = a[i].x, y = a[i].y, z = a[i].z;
if(find(x) == find(y)) continue;
if(mp[get(x, y, z)]) mp[get(x, y, z)] = 2;
else pre[find(x)] = find(y);
}
}
signed main(){
Ios;
int q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++) pre[i] = i;
while(m--)
{
int x, y, z;
cin >> x >> y >> z;
a[++cnt] = {x, y, z};
}
for(int i=1;i<=q;i++)
{
int x, y, z;
cin >> x >> y >> z;
a[++cnt] = {x, y, z};
b[i] = {x, y, z};
mp[get(x, y, z)] = 1;
}
kruskal();
for(int i=1;i<=q;i++)
{
int x = b[i].x, y = b[i].y, z = b[i].z;
if(mp[get(x, y, z)] == 2) cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
思路想出来了实现挂了。。