SCNUSE 9月训练专题

3 篇文章 0 订阅

问题 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”。

样例输入

3
([])
(([()])))
([()])()

样例输出

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值