并查集:可以把并查集的过程看做是一个拉帮结派的过程
江湖上散落着各式各样的大侠 他们整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,不打自己的朋友。而且信奉“朋友的朋友就是我的朋友” 这样一来,就形成了一个一个的帮派,通过两两之间的朋友关系串联起来。而无法通过朋友关系连起来的,就可以放心往死了打
单调栈:元素进栈过程:
对于一个单调递增栈来说,若当前进栈的元素为a,如果a>栈顶元素,则直接将a进栈。
如果a<=栈顶元素,则不断将栈顶元素出栈,直到满足a>栈顶元素
所以单调栈并没有把所有元素存进去,他只是一个动态的过程。
单调队列(可以求出区间的最大值最小值,后面的C题):单调队列与单调栈及其相似,把单调栈先进后出的性质改为先进先出既可。
元素进队列的过程对于单调递增队列。
对于一个元素a,如果a>队尾元素,那么直接将a扔进队列,如果a<=队尾元素,则将队尾元素出队列,直到满足 a>队尾元素即可。
对于不太理解单调队列的可以上网查视频,里面有详细的介绍。
问题 A: 道路统计
题目描述
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。
省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。即 1-2 2-3 也可以认为1与3 是联通的
问最少还需要建设多少条道路?
输入
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( <= 10000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
输出
对每个测试用例,在1行里输出最少还需要建设的道路数目。
样例输入
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
样例输出
1
0
2
998
这个题是用并查集的方法做
int find(int x){
if(x==pre[x])
return pre[x];
else return pre[x]=find(pre[x]); //路径压缩
}
void Union(int x,int y){ //归并(结盟)
x=find(x);
y=find(y);
if(x!=y){
guanxi[x]=y;
}
}
int main(){
int n,m,p;
while(cin>>n>>m){
if(n==0) break;
int ans=0;
memset(guanxi,0,sizeof(guanxi));
for(int i=1;i<=n;i++)
guanxi[i]=i;
for(int i=1;i<=m;i++){
int mi,mj;
cin>>mi>>mj;
Union(mi,mj);
}
int root=find(1); //把1的盟主作为一个参照
for(int i=2;i<=n;i++){
if(find(i)!=root){ //当前结点和我的参照节点不是一个盟派的
Union(i,root); //那我就把他归到一起
ans++; //两个联盟连在一起ans++,相当于连在一起后我这两个联盟的成员可以互通了
}
}
cout<<ans<<endl;
}
return 0;
}
问题 B: 排队的边边
题目描述
边边去排队买饭,因为身高问题只能看见前面没被比他高的人挡住的比他矮的人的头顶
他突然很好奇每个人能看见的别人头顶个数的总和,快来帮帮他吧
输入
排队人数n和每个人的身高
输出
每个人能看到的人数的总和
样例输入
6
176 175 178 190 160 150
样例输出
4
这个用单调栈做,是个单调递减栈(栈底元素是最大的)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000;
stack<int> s;
int num[maxn];
int main(){
int n,ans=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>num[i];
}
for(int i=1;i<=n;i++){
while(s.size()&&num[s.top()]<=num[i]) {
s.pop();
}
ans+=s.size();//每次加栈的大小
s.push(i);
}
cout<<ans<<endl;
}
问题 C: 滑动窗口
题目描述
给出一个n≤1000000的数组。有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。你只能在窗口看到k个数字。每次滑动窗口向右移动一个位置。比如:
The array is [1 3 -1 -3 5 3 6 7] and k is 3.
要求输出在k 范围内的最大和最小值
输入
输入由两行组成。
第一行包含两个整数n和k,它们是数组和滑动窗口的长度。第二行有n个整数。
输出
输出中有两行。
第一行分别从左到右给出窗口中每个位置的最小值。
第二行给出最大值。
样例输入
8 3
1 3 -1 -3 5 3 6 7
样例输出
-1 -3 -3 -3 3 3
3 3 5 5 6 7
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000+5;
int num[maxn];
struct node{
int num,i; //num大小,i位置
};
node dq[2*1001170];
void get_min(int n,int m){ //n是数字多少,m是区间
for(int i=0;i<=n;i++){
dq[i].num=dq[i].i=0;
}
dq[0].num=-0x3f3f3f3f; //队列0为最小
int head,tail=1;
for(int i=1;i<=m;i++){ //开始区间
while(head<=tail&&dq[tail].num>=num[i]) tail--;//删元素
tail++;
dq[tail].num=num[i];
dq[tail].i=i;
} //此时队首存的是1~3区间的最小值
for(int i=m;i<=n;i++){ //然后把区间向后挪动
while(head<=tail&&dq[tail].num>=num[i]) tail--;
tail++;
dq[tail].num=num[i];
dq[tail].i=i;
while(dq[head].i<=i-m) head++; //队头不在我这个区间,删队头
cout<<dq[head].num<<' '; //输出我这个区间的最小值
}
cout<<endl;
}
void get_max(int n,int m){ //和最小值类似
for(int i=0;i<=n;i++){
dq[i].num=dq[i].i=0;
}
dq[0].num=0x3f3f3f3f;
int head=1,tail=1;
for(int i=1;i<=m;i++){
// cout<<dq[head].num<<endl;
while(tail>=head&&dq[tail].num<=num[i]) tail--;
tail++;
dq[tail].num=num[i];
dq[tail].i=i;
}
for(int i=m;i<=n;i++){
while(tail>=head&&dq[tail].num<=num[i]) tail--;
tail++;
dq[tail].num=num[i];
dq[tail].i=i;
while(dq[head].i<=i-m) head++;
cout<<dq[head].num<<' ';
}
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>num[i];
}
get_min(n,m);
get_max(n,m);
}
问题 D: 可可的迷宫
题目描述
可可与人友好,经常喜欢设置一些有意思的游戏取人开心。今天她设置了一个迷宫让红红来走,她设计的迷宫是双向连通的,也就是说如果一个迷宫可以从A走到B,那么也一定可以从B走到A。为了提高难度,可可希望每两个顶点之间只有一条路可以通向对方(除非走了回头路)。可可现在把她的设计图给你,让你帮她判断她的设计是否符合她的设计思路。比如下面两个样例中,前两个是符合的,而最后一个由于从5->8的路径有两条不重复的路,因此不满足条件。
输入
输入包含多组数据,每组数据是一个以0 0结尾的整数对列表,每一对数表示了一条通道连接的两个位置的编号。位置的编号至少为1,且不超过100000。每两组数据之间有一个空行。
整个文件以两个-1结尾。
输出
对于每组测试样例,输出仅包括一行。如果该迷宫符合可可的思路,那么输出"Yes",否则输出"No"。
样例输入
6 8 5 3 5 2 6 4
5 6 0 0
8 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 0
3 8 6 8 6 4
5 3 5 6 5 2 0 0
-1 -1
样例输出
Yes
Yes
No
这道题的做法是用并查集判环
举个例子,比如当有结点0,1,2三个结点,当输入0,1(0和1之间连一条线)时把0和1归为一个集合,当再输如1,2(1,2之间连一条线)时就把1和2归为一个集合而1和0是一个集合,那么此时0,1,2就在一个集合里,而当你输入0,2时(0,2之间连一条线)那么此时就会形成环,因为他0,2是存在一个集合里的,所以判断条件就是当两个点是否存在于一个集合里,你
#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+5;
int x,y,head[maxn]; //Rank[maxn]
bool ans,flag,vis[maxn];
int find(int x){
if(x==head[x]) return x ;
else return head[x]=find(head[x]);
}
void Union(int x,int y){
int fx=find(x),fy=find(y);
vis[x]=vis[y]=true;
if(fx==fy){ //一旦两个点的盟主是一个人,说明之前两个点就在一个集合里,那这再加上去就会形成环
ans=false; //判环
return ;
}
head[fx]=fy;
}
int main(){
while(cin>>x>>y){
if(x==-1&&y==-1) break;
ans=true; //初始化ans(标记是否形成环),因为第一次结盟一定是不存在环的
if(x==0&&y==0){
printf("Yes\n");
continue;
}
for(int i=1;i<=maxn;i++) head[i]=i;
memset(vis,false,sizeof(vis)); //初始化
Union(x,y);
//判断每次第一个输入
while(cin>>x>>y){
// if(x!=0&&y!=0) flag=false;
if(x==0&&y==0) break;
Union(x,y);
}
int root=0;
for(int i=0;i<maxn;i++)
if(vis[i]&&head[i]==i) //maxn太多不知道哪个结点用过,所以用vis数组做标记,表示这个结点被访问过
root++;
if(root>1) ans=false; //存在没有形成一个联盟里的情况 也为false ;
if(ans) printf("Yes\n");
else printf("No\n");
}
return 0;
}
问题 E: Maximum Element In A Stack
题目描述
As an ACM-ICPC newbie,Aishah is learning data structures in computer science. She has already known that a stack, as a data structure,can serve as a collection of elements with two operations:
• push,which inserts an element to the collection, and
• pop, which deletes the most recently inserted element that has not yet deleted.
Now, Aishah hopes a more intelligent stack which can display the maximum element in the stack dynamically. Please write a program to help her accomplish this goal and go through a test with several operations. Aishah assumes that the stack is empty at first. Your program will output the maximum element in the stack after each operation. If at some point the stack is empty, the output should be zero.
输入
The input contains several test cases and the first line is a positive integer T indicating the number of test cases which is up to 50.
To avoid unconcerned time consuming in reading data,each test case is described by seven integers n (1 ≤ n ≤ 5 × 106 ) ,p, q, m (1 ≤ p q m ≤ 109 ), SA, SB and SC (104 ≤ SA SB SC ≤ 106 ). The integer n is the number of operations,and your program should generate all operations using the following code in C++.
int n,p,q,m;
unsigned int SA,SB,SC;
unsigned int rng61(){
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC;
}
void gen(){
scanf("%d%d%d%d%u%u%u" &n,&p,&q,&m,&SA,&SB,&SC);
for(int i = 1; i <= n; i++){
if(rng61() % (p + q) < p)
PUSH(rng61() % m + 1);
else
POP();
}
}
The procedure PUSH(v) used in the code inserts a new element with value v into the stack and the procedure POP() pops the topmost element in the stack or does nothing if the stack is empty.
输出
For each test case,output a line containing Case #x: y where x is the test case number starting from 1,and y is equal to where ai is the answer after the i-th operation and ⊕ means bitwise xor.
样例输入
2
4 1 1 4 23333 66666 233333
4 2 1 4 23333 66666 233333
样例输出
Case #1: 19
Case #2: 1
提示
The first test case in the sample input has 4 operations:
• POP();
• POP();
• PUSH(1);
• PUSH(4).
The second test case also has 4 operations:
• PUSH(2);
• POP();
• PUSH(1);
• POP().
中文翻译:
现在有一个栈,初始为空。接下来有若干次操作,每次可能向栈顶 push 一个正整数,也可能 pop 掉
栈顶元素。
你需要在每次操作之后计算出栈内所有元素的最大值。如果栈为空则认为此时最大值是 0。
为了避免输入文件过大,所有操作会使用 rng61 算法生成。同时为了避免输出文件过大,你只需要
输出一个数:表示每次操作之后的答案与下标乘积的异或和。
输入格式
第一行包含一个整数 T,表示测试数据的组数。
接下来依次描述 T 组测试数据,为了减少读入量,采用如下的方式来给出数据。
对于每组测试数据:一行给出了 7 个整数依次为 n; p; q; m; SA; SB; SC。其中 n 表示总的操作次数,
你的程序可以使用如下所示的 C++ 代码生成所有的操作。
(代码见原题面)
在这份代码中, PUSH(v) 表示向栈中插入一个新的元素,它的值为 v。 POP() 表示弹出栈顶元素;如
果当前栈为空,则什么也不操作(即:没有东西可以弹出)。
输出格式
对于每组测试数据,输出一行信息 “Case #x: y”(不含引号),其中 x 表示这是第 x 组测试数据,
y 等于 i=1到n的(i · ai)的异或和,其中 ai 表示第 i 次操作后的答案
题目给出了个rng61算法,不需要懂,直接在题目的代码上补全就行,
要自己实现
PUSH(rng61() % m + 1);
POP();
#include<bits/stdc++.h>
using namespace std;
stack<long long> s;
int n,p,q,m;
unsigned int SA,SB,SC;
long long ans;//异或和 定义long long型,定义Int会出错
int t;
unsigned int rng61(){
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC;
}
void PUSH(long long x){ //补全push操作
if(s.empty()) s.push(x);
else s.push(max(x,s.top())); //取最大的push
}
void POP(){
if(!s.empty()) s.pop();
}
int main(){
int t;
cin>>t;
int Case=0;
while(t--){
while(!s.empty()) s.pop();
ans=0;
scanf("%d%d%d%d%u%u%u",&n,&p,&q,&m,&SA,&SB,&SC);
for(int i = 1; i <= n; i++){
if(rng61() % (p + q) < p)
PUSH(rng61() % m + 1);
else
POP();
if(!s.empty()) ans^=(i*s.top()); //每次操作都异或和
}
printf("Case #%d: %lld\n",++Case,ans);
}
return 0;
}