问题 A: Beiju Text
题目描述
LXY 同学的键盘出现了奇妙的故障,所有键都会正常的工作,但是键盘上的 Home 以及 End 键有时候会莫名其妙的自己按下。但是盲打很熟练的他一般习惯关闭显示器打字,因为这样很酷。
现在他正在打一段文本,假设你已经知道这段文本以及 Home 和 End 键会什么时候出现故障自行按下。请你编写一个程序,求出他最后打出的文本。
输入
输入数据有多组。
每组数据在一行内包含了至多 100000 个字母、下划线和两个特别的标点 ‘[’ 以及 ‘]’ ,其中 ‘[’ 代表输入到此时 “Home” 键会被按下。而 ‘]’ 则代表输入到此时 “End” 键会被按下。
输入数据以 EOF 作为结束。
输出
输入数据有多组。
每组数据在一行内包含了至多 100000 个字母、下划线和两个特别的标点 ‘[’ 以及 ‘]’ ,其中 ‘[’ 代表输入到此时 “Home” 键会被按下。而 ‘]’ 则代表输入到此时 “End” 键会被按下。
输入数据以 EOF 作为结束。
样例输入
This_is_a_[Sample]_text
[[]][]]Nihao_I_am_a_Sample_Input
This_pr[oblem_has_a_100]0[m]s_time_limit
Maybe_theres_no_bracket
样例输出
SampleThis_is_a__text
Nihao_I_am_a_Sample_Input
moblem_has_a_100This_pr0s_time_limit
Maybe_theres_no_bracket
提示
①本题不宜采用在数组中挪动字母的方式,你可以认为一定会超时。
②提示:可以尝试使用链表或者双向队列来解决这个问题。
解析
这道题没什么好说的,自从学了deque后看到这道题很自来熟,但是链表的话,真是打扰了。
代码如下
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
char a[100110];
deque<char> q;
int main(){
while((scanf("%s",a))!=EOF){
int flag=0;
q.clear();
for(int i=0;i<strlen(a);){
if(a[i]=='['){
i++;
int k=0;
while(a[i]!=']'&&a[i]!='['&&i<strlen(a)){
q.insert(q.begin()+k,a[i]);
k++;
i++;
}
//i++;
}
else{
if(a[i]!='['&&a[i]!=']'&&i<strlen(a)){
q.push_back(a[i]);
i++;
}
else
i++;
}
}
for(int i=0;i<q.size();i++)
cout<<q[i];
cout<<endl;
}
return 0;
}
问题 B: Boxes in a Line
题目描述
一行中有 n 个框 1…n 从左到右。你的任务是模拟 4 种命令:
• 1 X Y:将框 X 左移到 Y(如果 X 已经是 Y 的左边,则忽略)。
• 2 X Y:将框 X 右移到 Y(如果 X 已经是 Y 的右边,则忽略)。
• 3 X Y:交换框 X 和 Y。
• 4:反转整行。
命令保证有效,即 X 不等于 Y。
例如,如果 n=6,执行 1 1 4 后,该行变为 2 3 1 4 5 6。然后执行 2 3 5,该行变为 2 1 4 5 3 6。然后在执行 3 1 6 后,该行变为 2 6 4 5 3 1。然后在执行 4 之后,行变为 1 3 5 4 6 2。
输入
最多只有 10 个测试用例。每个测试用例都以包含 2 个整数 n,m(1 ≤ n,m ≤ 100,000)的行开始。以下每一行都包含一条命令。
输出
对于每个测试用例,打印奇数索引位置的数字总和。从左到右编号为 1 到 n。
样例输入
6 4
1 1 4
2 3 5
3 1 6
4
6 3
1 1 4
2 3 5
3 1 6
100000 1
4
样例输出
Case 1: 12
Case 2: 9
Case 3: 2500050000
解析
这道题一看就知道是链表,然后我就想到了尾插法,插入等等一系列操作,但是让我直接写链表,那是不可能的,今天算是学了一个好办法,能够用数组进行双向链表的步骤~
if(one==3) 在这个地方刚开始我是没有分类的,以为不需要,结果自己模拟了一下x和y相邻的操作,发现如果不分类直接做会有错。
代码
#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>
#include <map>
using namespace std;
int n,m;
int one,x,y,counta=0;
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
int right[100005],left[100005];
for(int i=0;i<=n;i++)
right[i]=i+1;
for(int i=n;i>0;i--)
left[i]=i-1;
int flag=0;
for(int i=0;i<m;i++){
cin>>one;
if(one==4) flag=(!flag);
else{
cin>>x>>y;
if(flag&&one!=3)
one=3-one;
if(one==1&&left[y]==x) continue;
if(one==2&&right[y]==x) continue;
int lx=left[x],rx=right[x],ly=left[y],ry=right[y];
if(one==1){
right[lx]=rx,left[rx]=lx;
right[ly]=x,left[x]=ly;
right[x]=y,left[y]=x;
}
if(one==2){
right[lx]=rx,left[rx]=lx;
right[x]=ry,left[ry]=x;
right[y]=x,left[x]=y;
}
if(one==3){//这里请自行举例原因
if(right[x]==y){
right[x]=ry,left[ry]=x;
right[lx]=y,left[y]=lx;
right[y]=x,left[x]=y;
}
else if(right[y]==x){
right[ly]=x,left[x]=ly;
right[y]=rx,left[rx]=y;
right[x]=y,left[y]=x;
}
else{
right[lx]=y,left[y]=lx;
right[y]=rx,left[rx]=y;
right[x]=ry,left[ry]=x;
right[ly]=x,left[x]=ly;
}
}
}
}
int step=0;
long long int sum=0;
for(int i=1;i<=n;i++){
step=right[step];
if(i%2)
sum+=step;
}
if(flag&&(n%2==0))
sum=(long long int)n*(n+1)/2-sum;
printf("Case %d: %lld\n",++counta,sum);
}
return 0;
}
问题 C: Matrix Chain Multiplication
#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>
#include <map>
using namespace std;
int mem[30][2],a[30][2];
char c;
char s[200000];
int main(){
int time,x,y;
cin>>time;
while(time--){
getchar();
cin>>c>>x>>y;
mem[c-'A'][0]=x;
mem[c-'A'][1]=y;
}
while(cin>>s){
int k=0,sum=0,flag=1;
if(strlen(s)<=1){
cout<<"0"<<endl;
continue;
}
for(int i=0;i<strlen(s);i++){
if(s[i]=='(')
continue;
else if(s[i]==')'){
if(a[k-2][1]!=a[k-1][0]){
cout<<"error"<<endl;
flag=0;
break;
}
else{
sum+=a[k-2][0]*a[k-2][1]*a[k-1][1];
a[k-2][1]=a[k-1][1];
}
k--;
}else{
a[k][0]=mem[s[i]-'A'][0];
a[k][1]=mem[s[i]-'A'][1];
k++;
}
}
if(flag)
cout<<sum<<endl;
}
return 0;
}
问题 D: Throwing cards away
问题描述
给定一个有序的一组卡,编号为 1 到 n,卡 1 在顶部,卡 n 在底部。只要卡组中至少有两张卡片,就会执行以下操作:
扔掉顶牌并将现在在顶部的牌移动到底部。
你的任务是找到废弃卡片的序列和剩余的最后一张卡片。
输入
每行输入(除最后一行)包含一个数字 n ≤ 50。最后一行包含 ‘0’,不应处理该行。
输出
对于来自输入的每个数字产生两行输出。第一行显示丢弃卡片的顺序,第二行显示剩余的最后一张卡片。没有行会有前导或尾随空格。请参阅示例以了解格式。
样例输入
7
19
10
6
0
样例输出
Discarded cards: 1, 3, 5, 7, 4, 2
Remaining card: 6
Discarded cards: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 4, 8, 12, 16, 2, 10, 18, 14
Remaining card: 6
Discarded cards: 1, 3, 5, 7, 9, 2, 6, 10, 8
Remaining card: 4
Discarded cards: 1, 3, 5, 2, 6
Remaining card: 4
解析
这道题我真的很服我自己,为什么不直接把Remaining card: 复制下来,以为都是cards ,这里有一点需要注意一下,就是如果是1的话,“Discarded cards:”冒号后面是没有空格的!
代码
#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>
#include <map>
#include <deque>
using namespace std;
int main(){
int n,k=0,a[60]={0};
while(cin>>n){
if(n==0)
break;
if(n==1){
cout<<"Discarded cards:"<<endl;
cout<<"Remaining card: 1"<<endl;
continue;
}
k=0;
deque<int> q1;
for(int i=1;i<=n;i++)
q1.push_back(i);
while(q1.size()>1){
a[k++]=q1.front();//储存丢弃的卡
q1.pop_front();
q1.push_back(q1.front());
q1.pop_front();
}
cout<<"Discarded cards: ";
for(int i=0;i<k;i++){
cout<<a[i];
if(i!=k-1)
cout<<", ";
}
cout<<endl;
cout<<"Remaining card: ";
cout<<q1.front()<<endl;
}
return 0;
}
问题 E: Printer Queue
题目描述
软件协会中唯一的打印机工作负载非常繁重。(等等,我们哪来的打印机)(哦一定是 ZYJ 的)有时打印机队列中有一百个工作,可能需要等待几个小时才能得到一页输出。
由于一些工作比其他工作更重要,ZYJ 已经为打印作业队列发明并实施了一个简单的优先系统。现在,每个作业被分配 1 到 9 之间的优先级(9 是最高优先级,1 是最低优先级),打印机的操作如下。
• 队列中的第一个工作 J 取自队列。
• 如果队列中某个工作的优先级高于工作 J,则将 J 移至队列的末尾而不打印。
• 否则,打印工作 J(并且不要将其放回队列中)。
通过这种方式,ZYJ 将打印的所有重要的工作(当然包括奖状)都可以很快打印出来。当然,其它人(如 HYR)印刷的那些讨厌的毕业论文可能需要等待一段时间才能印刷,但这就是生活。
确定自己的印刷工作何时实际完成的问题变得非常棘手。你决定写一个程序来解决这个问题。该程序将获得当前队列(作为优先级列表)以及工作在队列中的位置,并且必须计算打印工作需要多长时间,假定不会添加额外的工作到队列。为了简化问题,我们假设打印工作总是需要一分钟,并且从队列中添加和删除工作是即时的。
输入
第一行包含正整数:测试用例的数量(最多 100 个)。然后对于每个测试用例:
• 第一行有两个整数 n 和 m,其中 n 是队列中的工作数(1 ≤ n ≤ 100),m 是关注的工作的位置(0 ≤ m ≤ n - 1)。队列中的第一个位置是数字 0,第二个数字是 1,依此类推。
• 第二行有 n 个整数,范围为 1 到 9,给出队列中工作的优先级。第一个整数给出第一个工作的优先级,第二个整数给出第二个工作的优先级,等等。
输出
对于每个测试用例,用一个整数打印一行:假定没有其他打印工作将到达,完成关注的打印工作的分钟数。
样例输入
3
1 0
5
4 2
1 2 3 4
6 0
1 1 9 1 1 1
样例输出
1
2
5
解析
终终终于有一道题是直接AC的了
队列题目前面已经刷了几道了,但是这道比之前的直接模拟要稍微复杂一丢丢,要记录它关注的打印工作,不过还是不难的啦~
代码
#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <map>
#include <deque>
using namespace std;
int main(){
int t,a[110];
cin>>t;
while(t--){
int ans=0;
deque<int> q;
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>a[i];
q.push_back(a[i]);
}
sort(a,a+n);
int time=n-1;
while(true){
if(q.front()==a[time]){
ans++;
if(m==0)
break;
time--;
}
else
q.push_back(q.front());
q.pop_front();
m--;
if(m<0)
m=n-1-ans;
}
cout<<ans<<endl;
}
return 0;
}
问题 F: Parentheses Balance
问题描述
您将获得一个由括号 () 和 [] 组成的字符串。这种类型的字符串被认为是正确的:
(a)如果是空字符串
(b)如果 A 和 B 是正确的,AB 是正确的,
(c)如果 A 是正确的,(A) 和 [A] 是正确的。
编写一个程序,它接受这种类型的字符串序列并检查它们的正确性。您的程序可以假设最大字符串长度为 128。
输入
包含一个正整数 n 和 n 个括号字符串,一个字符串为一行。
输出
输出 “Yes” 或 “No”。
样例输入
样例输出
Yes
No
Yes
解析
这里我把deque写在了while的外边,导致t次循环的时候未清楚q的内容,一直WA~
代码
#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <map>
#include <deque>
using namespace std;
int main(){
int t;
char s[130]={0};
cin>>t;
getchar();
while(t--){
deque<int>q;
int flag=1;
gets(s);
for(int i=0;i<strlen(s);i++){
if(s[i]=='('||s[i]=='[')
q.push_back(s[i]);
if(s[i]==']'||s[i]==')'){
if(q.empty()){
flag=0;
break;
}
if((s[i]==']'&&q.back()=='[')||(s[i]==')'&&q.back()=='('))
q.pop_back();
else{
flag=0;
break;
}
}
}
if(flag==0||(!q.empty()))
cout<<"No"<<endl;
else
cout<<"Yes"<<endl;
}
return 0;
}
问题 G: Tree Recovery
题目描述
小学刚开始学 OI 的时候,小 CGY 非常喜欢玩二叉树。他最喜欢的游戏是用大写字母构造随机的二叉树。
为了记录他的树,他为每棵树写了两个字符串:前序遍历(根,左子树,右子树)和中序遍历(左子树,根,右子树)。
这是他的一个创作的例子:前序遍历是 DBACEGF,并且中序遍历是 ABCDEFG。
他认为这样一对字符串会提供足够的信息来重建树(但他从未尝试过)。
多年以后,再次看到,他意识到重建二叉树确实是可能的,但这只是因为他从未在同一棵树上使用过两次相同的字母。
然而,手工重建很快就变得单调乏味。
所以现在他要求你写一个为他工作的程序!
输入
输入将包含一个或多个测试用例。
每个测试用例由一行包含两个字符串组成,表示二叉树的前序遍历和顺序遍历。两个字符串都由唯一的大写字母组成。(因此它们不超过 26 个字符。)
输入由文件结束终止。
输出
对于每个测试用例,恢复小 CGY 的二叉树并打印一行包含树的后序遍历(左子树,右子树,根)。
样例输入
DBACEGF ABCDEFG
BCAD CBAD
样例输出
ACBFGED
CDAB
解析
emmm这道题之前看过师兄的简书学过,只不过就是把数字变成了字符,具体内容请参考这里
代码
#include <iostream>
//#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define mst(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(int i=(a);i<(b);i++) //虽然看见很多代码都是这么写,感觉会减少一些时间
const int maxN = 35;
int N,M;
struct node{char v;node *lc,*rc;}; //分别为左子树和右子树
char mid[maxN],pre[maxN];
node *build(char *p, char *m, int len){ //每次递归的时候p,m指针都会改变吗?嗯,最终返回根节点
if(len==0)
return NULL; //这里用Dev只允许NULL,nullptr的话会报错
node* prt = new node(); //这一步的作用?
prt->v = p[0];
int idx;
for(idx=0;idx<len;idx++)
if(m[idx]==p[0])
break;
int lsz = idx, rsz = len-idx-1;
prt->lc = build(p+1,m,lsz);
prt->rc = build(p+1+lsz,m+idx+1,rsz);
return prt;
}
//输出这里不是很明白
void print_path(node *prt){
if(prt==NULL)
return;
print_path(prt->lc); //每一个prt相当于一个实例,每个节点对应一个实例
print_path(prt->rc);
printf("%c",prt->v); //因为是后序,所以后面输出
}
int main()
{
// freopen("data.in.txt","r",stdin);
while(~scanf("%s%s",&pre,&mid)){
node *prt = build(pre,mid,strlen(mid));
print_path(prt);
cout<<endl;
}
return 0;
}
问题 L: 01迷宫
题目描述
有一个仅由数字 0 与 1 组成的 n × n 格迷宫。若你位于一格 0 上,那么你可以移动到相邻 4 格中的某一格 1 上,同样若你位于一格 1 上,那么你可以移动到相邻 4 格中的某一格 0 上。
你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。
输入
输入的第 1 行为两个正整数 n,m (n ≤ 1000,m ≤ 100000)
下面 n 行,每行 n 个字符,字符只可能是 0 或者 1,字符之间没有空格。
接下来 m 行,每行 2 个用空格分隔的正整数 i,j,对应了迷宫中第 i 行第 j 列的一个格子,询问从这一格开始能移动到多少格。
输出
输出包括 m 行,对于每个询问输出相应答案。
样例输入
2 2
01
10
1 1
2 2
样例输出
4
4
解析
当时看到这题是因为觉得熟悉,然后就用了bfs的模板,哪知不管用,然后借看了一下大佬的代码,才知道这是需要记忆化的,我就将大佬的代码拿去测了一下,结果提示重复率过高//赶紧逃。今天重新做了一遍,整个优化了一下,先到洛谷试试水,谁知还有一个数据MLE,最后终于发现了问题,数组不小心开大了~
代码
#include <iostream>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
int xx[4]={0,0,-1,1};
int yy[4]={1,-1,0,0};
//原来坑我的地方在这里?!
int visit[1005][1010],coun[1000005];
int n,m,st,en;
char a[1005][1010];
map<int,int>mapp;
struct point{
int x,y;
};
//如果这些点都是第一次,说明他们能走的步数是一样的
int bfs(int pn){
point p1,p2;
queue<point> que;
p1.x=st;
p1.y=en;
int ans=1;
que.push(p1);
while(!que.empty()){
p1=que.front();
que.pop();
for(int i=0;i<4;i++){
p2.x=p1.x+xx[i];
p2.y=p1.y+yy[i];
if(p2.x>0&&p2.y>0&&p2.x<=n&&p2.y<=n&&a[p1.x][p1.y]!=a[p2.x][p2.y]&&visit[p2.x][p2.y]==0){
ans++;
visit[p2.x][p2.y]=pn;
que.push(p2);
}
}
}
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
for(int k=1;k<=m;k++){
cin>>st>>en;
if(!visit[st][en]){
visit[st][en]=k;
coun[k]=bfs(k);
cout<<coun[k]<<endl;
}
else{
coun[k]=coun[visit[st][en]];
cout<<coun[visit[st][en]]<<endl;
}
}
return 0;
}
问题 J: 火柴棒等式
题目描述
给你n根火柴棍,你可以拼出多少个形如 “A + B = C” 的等式?等式中的 A、B、C 是用火柴棍拼出的整数(若该数非零,则最高位不能是 0)。用火柴棍拼数字 0 - 9 的拼法如图所示:
注意:
1.加号与等号各自需要两根火柴棍
2.如果 A ≠ B,则 A + B = C 与 B + A = C 视为不同的等式(A、B、C >= 0)
3.n 根火柴棍必须全部用上
输入
只有一行,一个整数 n(n <= 24)
输出
只有一行,一个整数,表示能拼成的不同等式的数目
样例输入
18
样例输出
9
提示
对于 18 0 + 4 = 4 0 + 11 = 11 1 + 10 = 11 2 + 2 = 4 2 + 7 = 9 4 + 0 = 4 7 + 2 = 9 10 + 1 = 11 11 + 0 = 11
解析
第一眼看到这道题是很熟悉,但是可能因为被坑多了,首先想到的是用搜索,其实只需要将1000个遍历一遍就可以了,时间是够的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int num[10]={6,2,5,5,4,5,6,3,7,6};
int a[2001]={6};//先要初始化
int main(){
int n,j,sum=0;
scanf("%d",&n);
for(int i=1;i<=2000;i++){
j=i;
while(j>0){
a[i]+=num[j%10];
j/=10;
}
}
for(int i=0;i<=1000;i++)
for(int j=0;j<=1000;j++){
if(a[i]+a[j]+a[i+j]+4==n)
sum++;
}
printf("%d\n",sum);
return 0;
}
问题 I: Dictionary
题目描述
您的任务是编写一个简单字典的程序,该字典实现以下指令:
insert str:将字符串 str 插入到字典中
find str:如果字典包含 str,则打印 yes,否则打印 no
输入
在第一行 n 中,给出了指令的数量。在以下 n 行中,以上述格式给出 n 条指令。
字符串由 A,C,G 或 T 组成
1 ≤ 字符串长度 ≤ 12
n ≤ 100000
输出
为一行中的每个查找指令打印 yes 或 no。
样例输入
6
insert AAA
insert AAC
find AAA
find CCC
insert CCC
find CCC
样例输出
yes
no
yes
解析
这道题是真的不会/哭了/,到现在也只是知道是哈希表(散列表),我决定去好好看看数据结构
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
#define row 1001333 //取一个比h2(k)大的质数
#define col 20
char H[row][col];
ll h1(ll key){
return key%row;
}
ll h2(ll key){
return 1+(key%(row-1));
}
ll chtonum(char s){ //character to number
if(s=='A') return 1;
if(s=='C') return 2;
if(s=='G') return 3;
if(s=='T') return 4;
return 0;
}
ll get_key(char s[]){ //生成key值
ll sum=0,p=1;
int len=strlen(s);
for(ll i=0;i<len;i++){
sum+=p*chtonum(s[i]);
p*=5;
}
return sum;
}
void insert(char s[]){
ll key,h;
key=get_key(s);
ll i =0;
while(1){
h=(h1(key)+i*h2(key))%row; //根据key值生成对应的位置
if(strcmp(H[h],s)==0) return ;//这个元素字典里已经有了
else if (strlen(H[h])==0){
strcpy(H[h], s);//如果该位置没有别的值,就把这个元素插进去
return ;
}
i++;//如果这个i对应的h的位置已经有元素了,那么继续寻找下一个i对应的位置
}
}
bool find(char s[]){
ll key,h;
key=get_key(s);
ll i=0;
while(1){
h=(h1(key)+i*h2(key))%row;
if(strcmp(H[h],s)==0) return true;//如果找到了该元素
else if (strlen(H[h])==0) return false;
i++;
}
return false;
}
int main (){
int n;
char c[20],s[20];
for(int i = 0;i<row;i++) H[i][0]='\0'; //把数组初始化 方便比较
scanf("%d",&n);
for(int i =0;i<n;i++){
cin>>c>>s;
if(c[0]=='i') insert(s);
else {
if(find(s)) cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
}
return 0;
}
问题N: 信息传递
题目描述
有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入
共2行。
第1行包含1个正整数 n (1<=n<=200000),表示 n 个人。
第2行包含 n 个用空格隔开的正整数 T1,T2,⋯⋯,Tn ,其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti的同学,Ti<=n且Ti≠i。
输出
1个整数,表示游戏一共可以进行多少轮。
样例输入
5
2 4 2 3 1
样例输出
3
提示
游戏的流程如图所示。当进行完第 33 轮游戏后, 4 4号玩家会听到 22 号玩家告诉他自己的生日,所以答案为 33。当然,第 33 轮游戏后, 2 2号玩家、 33 号玩家都能从自己的消息来源得知自己的生日,同样符合游戏结束的条件。
对于 30%30%的数据, n ≤ 200n≤200;
对于 60%60%的数据, n ≤ 2500n≤2500;
对于 100%100%的数据, n ≤ 200000n≤200000。
解析
说实话,一开始我是没有看懂这道题和他的说明,凝视了很久才发现第二轮和第一轮有关,第三轮和第二轮有关,由于本蒟蒻只是接触过并查集并未深入了解,无法像其它大佬一样只是看了一眼就知道是并查集求最小环问题,果然还是我tcl//orz~ 具体的题解已写在代码里啦~
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int par[200010],last[200020],mini,d[200020],sum;
int find(int x){
if(par[x]!=x){ //查找时沿途更新祖先节点和路径长
int last=par[x]; //记录父节点
par[x]=find(par[x]);//更新祖先节点
d[x]+=d[last]; //更新路径长
}
return par[x];
}
void unionn(int x,int y){
int fx=find(x); //查找祖先节点
int fy=find(y);
if(fx!=fy){ //若不相连,则连接两点,更新父节点和路径
par[fx]=fy;
d[x]=d[y]+1;
}
else//之所以加一的原因是,如果有两个祖先节点相同,那么就可以构成一个环,长度为两个点到祖先节点长度之和+1
mini=min(mini,d[x]+d[y]+1);//若已连接,则更新最小环的长度
return ;
}
int main(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++)
par[i]=i;
mini=0x7777777;
for(int i=1;i<=n;i++){
cin>>m;
unionn(i,m);
}
cout<<mini;
return 0;
}