一、整体流程

晚了一小时参赛,被第一道题卡了许久,下一道题卡常卡了许久后火速做完了另外两道暴力

比赛链接


二、具体题目

1.问题 H:括号匹配

(1)读题

①场上:场上考虑到用栈,放进去左括号后遇到右括号出栈,如果不是需要的括号则返回NO。一开始没有考虑到同等大小的左右括号必须相互匹配,循环判断了每种括号,但是每一种括号都可行不代表总体可行。

②改进:字符串读入尽量用scanf("%s"),不要一个一个读。读题时首先读限制条件,往往限制条件中保存着特殊情况和从小归大的方法。

(2)做题

#include<cstdio>
#include<iostream>
#include<stack>
using namespace std;
char c[2000000];
int n;

int main(){
    scanf("%d",&n);
    if(n==0){
        printf("YES\n");
        return 0;
    }
    scanf("%s",c+1);
    stack<char> s;
    for(int i=1;i<=n;i++){//处理每个字母 
        char nowChar=c[i];
        if(s.size()==0){
            if(nowChar>=97){
                printf("NO\n");
                return 0;
            }
            s.push(nowChar);
        }else{
            if(nowChar<97){//大写字母直接入栈 
                s.push(nowChar);
            }else{//判断上一个大写字母是否与自己匹配 
                char lastChar=s.top();
                if(lastChar+32==nowChar){//匹配 
                    s.pop();
                }else{
                    printf("NO\n");
                    return 0;
                }
            }
        }
    }
    if(s.size()!=0){
        printf("NO\n");
        return 0;
    }
    printf("YES\n");
    return 0;
}
View Code

 

2.问题 C: 又是氧基生物

(1)读题

一开始考虑DP,发现不可行,果断放弃。注意到n、m的范围只有1e5,如果二分xi的话,O(nlogxi),能过。

(2)做题

写了二分之后硬是卡不过去,开了各种优化后减少了半秒,最后强行卡过去了。

#pragma once//只编译一次 
#pragma GCC diagnostic error "-std=c++11"
#pragma GCC target("avx")
#pragma GCC optimize(3)
#pragma GCC optimize(3,"Ofast","inline")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[200000];
int n,m;
//1 2 4 8 9
inline bool isOk(int val){//当前和平度能否达到 
    int okCnt=0;
    for(int i=1;i<=n;i++){
        //每次贪心到达能满足和平度的点
        for(int j=i+1;j<=n;j++){//下一个 
            if(a[j]-a[i]<val){////如果不满足和平度 
                //printf("不满足:a[%d]-a[%d]=%d\n",j,i,a[j]-a[i]); 
                continue;
            }
            //printf("满足:a[%d]-a[%d]=%d\n",j,i,a[j]-a[i]); 
            i=j-1;//如果满足和平度就转移 
            okCnt++;
            break; 
        } 
    }
    if(okCnt+1<m)return false;
    return true;
}

int main(){
    scanf("%d%d",&n,&m);
    int maxx=-1;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    sort(a+1,a+n+1);//从小到大排序
    for(int i=2;i<=n;i++){
        maxx=max(maxx,a[i]-a[i-1]);
    }
    int l=1,r=maxx;
    while(l!=r){
        if(l==r-1){
            if(isOk(r))l=r;
            else r=l;
        }
        int mid=(l+r)/2;
        if(isOk(mid)){
            l=mid;
            //printf("isOk(%d):true\n",mid);
        }else{
            //printf("isOk(%d):false\n",mid);
            r=mid;
        }
        //printf("l:%d,r:%d\n",l,r);
    }
    printf("%d\n",l);
    //printf("l:%d,r:%d\n",l,r);
    return 0;
}
View Code

 

3.问题 B: 拔苗助长

(1)读题

一开始从线段操作联想到线段树了,但不可做。从n的范围来看应当是dp。从ai的数据范围看,可能跟O(na)有关,即从目标高度开始考虑,但是不论怎么做最终复杂度都是超限的。

正确的做法与ai的大小无关,为O(n)算法。考虑到对于每个大于1的序列,贪心地减去最小值,序列就会分成几个被0分割的子序列,可以用递归操作这些子序列,直到求出答案。

那么转化一下,首先思考一个子问题:给定一个序列,其中的值都为正整数且单调递增,怎么减才能最终都减到0?答案很简单,贪心的从最小值开始减,每次影响的范围尽量大。

对于这个题,我们要做的就是将大问题转化为子问题。观察数列性质,发现对于一个单调递增的子数列,如果强制拿出这段子序列,那么很容易通过右边值与左边值的差意识到

减少的总数量(因为每次只能减少1,这一点很关键)。那么对于数列1 2 3,需要的次数就是3次(1-0也算一次),那么如果另一个序列与这个序列无关,但也单调递增,那么另一个序列也满足这个性质。

接下来,总问题就转化成了如何将整个序列的答案转化到若干个子序列中。

换句话说,在扫的过程中,如何计算序列之间分界点的贡献?

枚举子情况。如果是从大到小,比如(3,0),显然两序列完全无关,不需要转化贡献。在另一种情况下,如果是(3,1),表面看1与之前的1存在序列重复性,不能进行转化。

但其实这种情况下,1已经被贪心地 从前面强行减过了。换句话讲,每次减少的实际上是尽量大的范围,即这种情况已被提前计入答案。

(2)做题

#include<cstdio>
#include<iostream>
using namespace std;
int a[200000];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(a[i]>a[i-1]){
            ans+=a[i]-a[i-1];
        }
    }
    printf("%d\n",ans); 
    return 0;
}
View Code

 

4.问题 D: 发红包

(1)读题

最不正常的地方是只给1个红包。从这里入手,我们考虑,是否要建图,将某个人与收到红包的人连上单向边?

从这个角度看的话,是过不了n=2e5的数据的,换句话讲,需要O(n)的做法。

其次不正常的地方是收到自己初始准备的红包,这里必然是解题的关键区域。

结合两个不正常的地方,能否做一种算法,在图上进行操作,然后判断树的深度?

经过画图发现,真正对答案有贡献的只有那些点双连通分量。此时,问题转化为求图中最小的点连通分量的siz。

但是这里有个问题:虽然点双连通具有必要性,但是枚举最小的分量大小是否具有作为答案的充分性?

这个问题可以由单向边的性质回答。由于一个点的出度只有1,那么在某个双连通分量中,其必然是无穷多的"单恋",那么最终只能形成一个简单环,问题也就证明完毕了。

(2)做题

#include<cstdio>
#include<iostream>
using namespace std;
int f[200000+5],d[200000+5],minn,n;
int fa(int x){
    if(f[x]!=x){
        int last=f[x];
        f[x]=fa(f[x]);
        d[x]=d[x]+d[last]; 
    } 
    return f[x]; 
}
void check(int a,int b){
    int x=fa(a);int y=fa(b);
    if(x!=y){
        f[x]=y;
        d[a]=d[b]+1;
    }else{
        minn=min(minn,d[a]+d[b]+1);
    }
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        f[i]=i;
    } 
    minn=2000000000;
    int temp;
    for(int i=1;i<=n;i++){
        scanf("%d",&temp);
        check(i,temp);
    }
    printf("%d\n",minn);
    return 0;
}