智算之道初赛第二场 - 高校组——情报战
情报站
(题目链接:http://oj.csen.org.cn/contest/7/22#)
现在我方已经查明,敌人通信所使用的加密方式依赖于一个长度为 nn 的数列,只要得知了这个数列中每个数的值,我方便可破解敌方的通信。
通过深入敌人内部的内线人员的艰苦奋斗,我方逐渐获得了一些有用的情报,通过这些情报,整个数列正在被不断地破解。
先后有 mm 条情报被得知,每条情报是以下两种情况之一:
-
情况 11 :知道了数列中第 xx 个数的值
-
情况 22 :知道了数列中第 xx 个数和第 yy 个数的和
每得知一条情报,我方都试图破解数列中元素的值
作为情报部门核心技术人员的你,请编程实现如下功能:每次得知一条新情报,你都要计算当前已经能够确定出数列中的多少个数了
你比较笨,对于情况 2 这种情报,只能在已知其中一个数的情况下推出另一个数,不能通过若干情况 2 的情报列方程求解
输入格式
第一行,两个正整数 n,mn,m
接下来 mm 行,每行的第一个数是 typetype
- 如果 type=1type=1,则接下来跟着一个整数 xx,表示得知了数列中第 xx 个数的值;
- 如果 type=2type=2,则接下来跟着两个空格隔开的整数 x,yx,y,表示得知了第 xx 个数和第 yy 个数的和
输出格式
输出 mm 行,每行包含一个非负整数,第 ii 行的非负整数表示在得知了前 ii 条情报之后数列中已经能够确定的数的数量
数据规模与约定
对于 20\%20% 的数据,1 \le n,m \le 101≤n,m≤10,且只有第一种情报
对于 50\%50% 的数据,1 \le n,m \le 50001≤n,m≤5000
对于 100\%100% 的数据,1 \le n,m \le 3\times 10^51≤n,m≤3×105
可能会有重复的情报,也可能出现 x=yx=y 的情况
样例输入
5 4
1 1
1 2
2 2 3
2 1 3
样例输出
1
2
3
3
我当初写这题的时候,没有考虑到并查集,以为只是简单的模拟题,一提交就爆0了,认真一看似乎和并查集有关,但是由于注意的细节太多,直到比赛结束代码还没有调试完。
这题的并查集最重要的地方是维护现在有集合,和未知集合,未知集合又分为两种:
第一种是:未知和未知是一个集合,且其中一个数可以是已知也可以是未知的,这也要是分开讨论的;
第二种是:单个未知,该未知也可以是已知也可以是未知,这也是需要讨论的;
***注:以下代码是在智算之道上提交的并且是正确,但是并没有在其他平台上测试。因此并不能保证是正确的代码,且该代码是本人自己想,自己的写的,也不能保证该代码是最优的。
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e6+100; int fa[2*N],size[N],num[N]; int n,m,len; set<int> s; inline int read() { int x=0,f=1; char c=getchar(); while(c>'9'||c<'0') { if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') { x=x*10+c-'0'; c=getchar(); } return x*f; } int find(int x)//查找父亲 { return x==fa[x]?x:fa[x]=find(fa[x]); } void merge(int x,int y)//合并且累加个数 { int a=find(x),b=find(y);//分别是x,y的父亲。 if(a!=b) { fa[b]=a;//父亲合并 size[a]+=size[b];//两个所带的个数合并 } return ; } int main() { n=read(),m=read(); s.insert(0); for(int i=1;i<=n;i++) { if(i<=n) size[i]=1; fa[i]=i; } for(int i=0;i<m;i++) { int k,x,y; k=read(); if(k==1) { x=read(); s.insert(x);//记录当前所用的数 num[++len]=x; if(num[len-1]>num[len]) swap(num[len-1],num[len]); int a=find(num[len-1]);//上个一个数 int b=find(num[len]);//当前的数 目的是为了使拥有的数成为一个集合,因此每一次都要把操作一的数与上次的数合并。 if(a==b)//如果两个父亲相同,因此所有拥有的个数也相同 { printf("%d\n",size[a]); } else//如果不同,合并两个数的个数。 { merge(num[len-1],num[len]);//小合并大的,统一合并方向 printf("%d\n",size[a]); } } else { x=read(),y=read(); if(x>y) swap(x,y);//小数合并大数,统一方向。 int a=find(x),b=find(y);//查找两个的父亲 if(a==b&&s.count(a))//如果相同且是当前所有的数,因它们所拥有的个数相同 { printf("%d\n",size[a]); } else if(a==b)//如果相同但又不存在于当前所拥有的数当中,那么当前的个数依旧不变,仍然是当前集合的个数 { int c=find(num[len]);//既然当前所拥有的数都是在一个集合里,那么任意一个数它所带着的个数都是相同的,因此就随便找一个已知的数,去查找它所带有的个数就好了。 printf("%d\n",size[c]); } else//如果不同,有两种情况 { if(s.count(a)||s.count(b))//第一种:这两个数中的某一个数是当前已经拥有的,但是另一个数不是,因此只要合并它们,在记录下当前没有拥有的数。 { merge(x,y); s.insert(x),s.insert(y); printf("%d\n",size[a]); } else//第二种:这两个数都不是当前所拥有的,但也要把他们合并,万一在下一次的操作一中会出现该两数的其中一个,那是的个数不是多一,而是多二。 { merge(x,y); int c=find(num[len]); printf("%d\n",size[c]); //输出的是当前已有的数的个数,目的与上相同 } } } } return 0; }
给出两个我自己想的测试点,因为我是靠这两个测试点过的这题,答案被我清除了,原因是这样的好复制(嘿嘿嘿~),所以答案需要自己想的。
测试点一:
12 16 1 1 1 2 2 1 2 1 3 2 1 3 2 2 3 2 2 2 2 3 3 2 1 1 2 4 5 2 5 6 1 5 2 7 8 2 9 9 2 8 9 1 8
测试点二:
123 18 2 1 2 2 2 3 2 3 4 2 4 5 2 5 6 2 6 7 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 2 8 9 2 9 10 2 11 11 2 12 12
望大佬们海涵。

浙公网安备 33010602011771号