毒瘤CSP-S复赛2019赛后总结

前言

提高组比赛后我差点抱头痛哭——

亲眼看着民间数据别人300+,AC了三题,我200-,爆零了三题。。

尤其是看了测评上的分数,一道打了2h+的血汗题爆零了。。

(污污污)。。。。。

Day1

注意,本博文为比赛总结,不一定有题解

格雷码

这道题比较简单,按题意倒过去推就可以,难点在于它的数据范围

由题可知,k<2^64,用long long都不行,如果还是想直接存储,只能用unsigned long long,还必须提心吊胆地用。

括号树

由题意可知,合理括号串有两种(设大写字母为一个合理括号串):

  1. A=(B)
  2. A=B_{1}B_{2}...B_{n}

两种串意味着两种计算方法,第一种,numA=numB+1,第二种,numA=\sum_{i=1}^{n}numB_{i} + n*(n-1)/2

很容易想到,用一个结构体表示一个括号串(总和sum,项数n),然后用栈存到根节点的路径上的所有分隔开的括号串(以一个不含合理串的不合理串分隔,如“(”),分隔开的若干个串只用单独计算,互不关联,

当一个节点的括号进入到判断栈(起码还要有个栈来判断合理吧)中,与上一个括号匹配,还需要分类讨论:

  1. 两个括号相邻"()",直接存入大栈(sum=1,n=1);
  2. 不相邻“(......)”,中间消掉了一些合理串,显然,这些串一定是连在一起的,并且是大栈中被分隔开的栈顶的串,把它拿出来,改一下(重新计算sum,n变为1)再放进去。

在入栈之前,还要特判一下:因为两个括号匹配后,可能导致两个原本被分隔开的合理串变为相邻(如"()(()"加上一个")",经上述操作后变为"()(())",红绿两串相邻,应合为第二类括号串),此时要把栈顶踢出与将进入的串合并,再入栈。

以此计算每个节点值的时候,还要考虑一下回溯。

因为一条路径搜完过后还要回来搜另一条,所以必须把两个栈还原。

还原可以打一个大的结构体直接赋值吗?

不行!因为我就是这样才扣了50

由于判断栈每一次要么出一个括号,要么进一个括号,所以只用记录操作类型和改变的值;

大栈里在每次入栈一个新串之前,要么不改动,要么踢了一个串,要么踢了两个串,记录踢出的串(最多两个)然后还原。

时间复杂度约为O(n)

#include<cstdio>//整篇博客光秃秃,拼个代码丰富一下内容
#include<cstring>//大佬的代码60行,我的行数为两倍
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<stack>
#define M 500005
#define ll long long
using namespace std;
int n,fa[M];
ll k[M];
vector<int>G[M];
bool c[M],spcl;
char s;
stack<int>st1;
inline int Read(){
    int x=0;char s=getchar();
    while(s<'0'||s>'9'){s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+s-'0';s=getchar();}
    return x;
}
struct itn{
    int l,r;
    ll NUM,SUM;
    itn(){l=r=NUM=SUM=0;}
    itn(int L,int R){
        l=L,r=R;
    }
};
struct bol{
    stack<itn>S;
    ll ans;
}st2;
void dfs(int x){
    itn yst1,yst2;
    if(!st2.S.empty())yst1=st2.S.top();
    if(st2.S.size()>1){
        st2.S.pop();yst2=st2.S.top();
        st2.S.push(yst1);
    }
    ll rt=st2.S.size(),yan=st2.ans;
    int out=-1,jin=-1;
    if(st1.empty()||c[st1.top()]==c[x]){
        st1.push(x);
        jin=1;
        k[x]=k[fa[x]];
    }
    else{
        if(c[st1.top()]==0){
            int l=st1.top();
            itn in=itn(l,x);
            st1.pop();
            out=l;
            if(l!=fa[x]){
                itn A=st2.S.top();
                st2.S.pop(),st2.ans-=A.SUM+1ll*(A.NUM*(A.NUM-1ll)>>1);
                in.NUM=1ll,in.SUM=1ll+(A.SUM+1ll*(A.NUM*(A.NUM-1ll)>>1));
            }
            else{
                in.NUM=1ll,in.SUM=1ll;
            }
            if(st2.S.empty()||st2.S.top().r!=fa[l]){
                st2.S.push(in);
                st2.ans+=in.SUM+1ll*(in.NUM*(in.NUM-1ll)>>1);
            }
            else{
                itn A=st2.S.top();
                st2.S.pop(),st2.ans-=A.SUM+1ll*(A.NUM*(A.NUM-1ll)>>1);
                A.r=x,A.NUM+=in.NUM,A.SUM+=in.SUM;
                st2.S.push(A);
                st2.ans+=A.SUM+1ll*(A.NUM*(A.NUM-1ll)>>1);
            }
            k[x]=st2.ans;
        }
        else{
            st1.push(x);
            jin=1;
            k[x]=k[fa[x]];
        }
    }
    for(int i=0;i<G[x].size();i++){
        dfs(G[x][i]);
    }
    if(spcl){
        if(jin>0)st1.pop();
        if(out>0)st1.push(out);
        while(st2.S.size()>rt)st2.S.pop();
        if(yst2.l>0){
            while(st2.S.size()>rt-2)st2.S.pop();
            st2.S.push(yst2),st2.S.push(yst1);
        }
        else{
            if(yst1.l>0){
                while(st2.S.size()>rt-1)st2.S.pop();
                st2.S.push(yst1);
            }
        }
        st2.ans=yan;
    }
}
int main()
{
    //freopen("brackets.in","r",stdin);
    //freopen("brackets.out","w",stdout);
    n=Read();
    for(int i=1;i<=n;i++){
        s=getchar();
        while(s!='('&&s!=')')s=getchar();
        c[i]=(s==')');
    }
    for(int i=2;i<=n;i++){
        fa[i]=Read();
        if(fa[i]!=i-1)spcl=1;
        G[fa[i]].push_back(i);
    }
    dfs(1);
    ll ans=k[1];
    for(ll i=2;i<=1ll*n;i++)ans^=(i*k[i]);
    printf("%lld\n",ans);
    return 0;
}

树上的数

我还没搞懂,只截了张PPT

Day2

Emiya 家今天的饭

这就是我的爆零的血汗题

&%&…@%#@%¥%#(*#*&@T@(#*#^$*#(FUCK)#(*&$^&#^%*&&@#...

看到这题,我想到了容斥,还想到了dp式...仿佛看到了骗84分的曙光...

但式子打错了,100多行代码,编译器又不能调,最后检查不出来......

我对这道题太执着,策略严重错误

唉。。。

划分、树的重心

这两道题虽然难,但有脑的人应该可以骗到100+的分(AK大佬除外)

只要拿到部分分数,总分就会比那些死敲正解还爆零的人(比如我)要高得多

总结

经过惨痛教训的思考后我深刻意识到,除AK虐场的大佬们外,暴力骗分是我们小ju拿省一的关键一招&关键抉择&决定性因素&唯一途径!

不要以为有了想法就能打AC(大佬除外)

要把暴力骗分卡常和优化(还有手速)的基本功练到极致!

猜你喜欢

转载自blog.csdn.net/weixin_43960287/article/details/103140857