现在我方已经查明,敌人通信所使用的加密方式依赖于一个长度为 nn 的数列,只要得知了这个数列中每个数的值,我方便可破解敌方的通信。通过深入敌人内部的内线人员的艰苦奋斗,我方逐渐获得了一些有用的情报,通过这些情报,整个数列正在被不断地破解。
先后有 m m m条情报被得知,每条情报是以下两种情况之一:
情况 1 1 1:知道了数列中第 x x x个数的值
情况 2 2 2:知道了数列中第 x x x个数和第 y y y个数的和
每得知一条情报,我方都试图破解数列中元素的值。作为情报部门核心技术人员的你,请编程实现如下功能:每次得知一条新情报,你都要计算当前已经能够确定出数列中的多少个数了。你比较笨,对于情况 2 这种情报,只能在已知其中一个数的情况下推出另一个数,不能通过若干情况 2 的情报列方程求解
输入格式
第一行,两个正整数 n , m n,m n,m
接下来 m m m行,每行的第一个数是 t y p e type type
如果 t y p e = 1 type=1 type=1,则接下来跟着一个整数 x x x,表示得知了数列中第 x x x个数的值;
如果 t y p e = 2 type=2 type=2,则接下来跟着两个空格隔开的整数 x , y x,y x,y,表示得知了第 x x x个数和 y y y个数的和
输出格式
输出 m m m行,每行包含一个非负整数,第 i i i行的非负整数表示在得知了前 i i i条情报之后数列中已经能够确定的数的数量
数据规模与约定
对于 100 % 100\% 100%的数据, 1 ≤ n , m ≤ 3 × 1 0 5 1 \le n,m \le 3\times 10^5 1≤n,m≤3×105
可能会有重复的情报,也可能出现 x = y x=y x=y 的情况
样例输入
5 4
1 1
1 2
2 2 3
2 1 3
样例输出
1
2
3
3
首先不难想到暴力的方法,也就是如果输入1每次 d f s dfs dfs找一个点和它相连的点,如果输入2就建立一条边。但是最后wa了,原因是,如果图中存在环,那么这一系列的点都会知道,但是 d f s dfs dfs是无法处理环的
然后就想到了并查集,那么我们用vector保存每个顶点为祖先相连的集合,如果输入操作1,那么该点祖先节点的集合全部可以得知,如果输入为2,先看这两个点是否均已知,如果有一个已知另外一个祖先的集合也可以全部得知;如果均未知且两个节点的祖先不同,那么合并为一个集合。最后注意因为操作二的两个数可以相等,那么相当于直接得知了这个数,特判一下即可
当然集合的合并要使用启发式合并
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
vector<int> q[maxn];
bool vis[maxn];
int ans=0,f[maxn];
int Find(int x){
return f[x]==x?x:f[x]=Find(f[x]);
}
void Union(vector<int> &x,vector<int> &y){
if(x.size()<y.size()) swap(x,y);
for(int i=0;i<y.size();i++)
x.push_back(y[i]);
}
int main(){
int n,m,x,y,op;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
f[i]=i;
q[i].push_back(i);
}
while(m--){
scanf("%d",&op);
if(op==1){
scanf("%d",&x);
int fx=Find(x);
for(int i=0;i<q[fx].size();i++) if(!vis[q[fx][i]]){
vis[q[fx][i]]=1;
ans++;
}
q[fx].clear();
}else{
scanf("%d%d",&x,&y);
int fx=Find(x);
int fy=Find(y);
if(vis[fx] && vis[fy]){
printf("%d\n",ans);
continue;
}
if(x==y){
for(int i=0;i<q[fx].size();i++) if(!vis[q[fx][i]]){
vis[q[fx][i]]=1;
ans++;
}
q[fx].clear();
printf("%d\n",ans);
continue;
}
if(vis[fx] && !vis[fy]){
for(int i=0;i<q[fy].size();i++) if(!vis[q[fy][i]]){
vis[q[fy][i]]=1;
ans++;
}
q[fy].clear();
}else if(!vis[fx] && vis[fy]){
for(int i=0;i<q[fx].size();i++) if(!vis[q[fx][i]]){
vis[q[fx][i]]=1;
ans++;
}
q[fx].clear();
}else{
if(fx!=fy){
f[fy]=fx;
Union(q[fx],q[fy]);
q[fy].clear();
}
}
printf("%d\n",ans);
}
return 0;
}
后来从朋友那里明白,其实这个问题并不需要具体的集合元素,只需要知道集合中每个元素的个数,那么合并时只需合并数目,具体见代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
int f[maxn];
bool vis[maxn];
int a[maxn];
int Find(int x){
return f[x]==x?x:f[x]=Find(f[x]);
}
int main(){
int n,m,op,x,y,ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
f[i]=i,a[i]=1;
while(m--){
scanf("%d",&op);
if(op==1){
scanf("%d",&x);
int fx=Find(x);
if(!vis[fx]){
ans+=a[fx];
vis[fx]=1;
}
}else{
scanf("%d%d",&x,&y);
int fx=Find(x),fy=Find(y);
if(x==y || vis[fx] || vis[fy]){
if(!vis[fx]){
ans+=a[fx];
vis[fx]=1;
}
if(!vis[fy]){
ans+=a[fy];
vis[fy]=1;
}
}
if(fx!=fy){
f[fy]=fx;
a[fx]+=a[fy];
}
}
printf("%d\n",ans);
}
}