zoukankan      html  css  js  c++  java
  • CSP-S 2019 题解

    D1T1-格雷码

    题中给出了构造格雷码的方法。

    $solve(n,k)$表示求出$2^n$意义下排名为$k$的格雷码,

    只要比较一下考虑最高位的0/1取值就好了。

    部分分提示了要开$unsigned long long$,注意一下就可以了。

    D1T2-括号树

    子序列问题是便于处理的,只要处理以每个点结尾的合法序列,作树上前缀和就好了。

    然而合法括号序列也并不简单。

    判断一个括号序列是否合法的方法是,

    视左右括号分别为+1,-1。

    判断是否满足前缀和不小于0,并且总和为0。

    套用这个思路,考场上想到的一个做法是。

    用一个multiset维护前缀和。

    当加入一个左括号时插入元素,同时给set中所有元素作加一处理。

    当加入一个右括号时不断删除set中最小的元素,即前缀小于0的不合法判断。

    同时维护一个桶,记录set中元素的树上前缀和,这样可以做到O(1)直接查询合法的当前点为结尾的括号序列。

    然而因为树上的特殊性质,set需要还原,在最坏的情况下(可以为扫把图),这个算法可以被卡到O(n^2logn)。

    因为官方数据很水,它AC了。因为洛谷数据很水,它AC了。

    因为牛客数据和清北学堂数据都并不水,它TLE80分。

    正解实际上利用了一个点为结尾的合法括号序列的另一种生成方式,这在题目背景中也有提示。

    找到最近的一个满足左括号数等于右括号数的点,在满足这一段合法的意义下,只要加上它前面一个点为结尾生成的合法括号序列数就好了。

    一个简单的做法是用栈维护,左括号即压栈,右括号即弹出栈顶元素,同时可以确定第一个合法的位置。

    D1T3-树上的数

    大概是一个贪心。

    一个点想要换到想要换到的点,限制了路径上每个点的一个交换顺序。

    然后部分分看起来就很可打,链上只要维护左右的先后顺序,菊花只要搞一个类似并查集的插入的结构来确定先后顺序。

    然后感觉分类讨论非常复杂,两个都没有打出来,只有10分暴力。

    还没有改这道题。

    D2T1-Emiya 家今天的饭

    开始做这道题并没有思路,感觉十分不可做。

    猜测复杂度,估计是$O(n^2m)$,所以要枚举菜?

    然后突然想,这个玩意只要简单容斥就好了。

    枚举最终不合法的菜是哪一道。

    一个简单的做法是直接记录选了多少道菜和选了多少道非法的菜,这样直接$dp$的复杂度是$O(n^3m)$。

    然而状态数太多了,所以可以想到记录差值,即非法的菜数 - 选的菜数/2,同时维护选的菜数的奇偶性,

    然后复杂度就对了,然后就因为没打取模优化成功被卡常了。

    D2T2-划分

    看到数据范围整懵了,没有任何思路。

    然后发现暴力$dp$,$O(n^2)$就有64分了,所以直接打。

    $dp(l,r)$表示最后一次选择的区间,显然转移点是一个后缀,

    只要用单调指针预处理出这个后缀的位置,直接取后缀最小值就好了。

    正解有一个贪心,只要满足最后一次选的区间最小,答案就是最小的。

    并不会证明,然而直观理解一下是正确的。

    设$f_x$表示$x$结尾的最小的最后一次选择的区间,有简单的转移

    $f_i=min_j(pre_i-pre_j)(pre_i-pre_j>=f_j)$,这个玩意的形式还挺简单的,

    顺便记录一下答案就做到$O(n^2)$了,弱智剪枝是找到第一个答案直接跳出,在一些比较水的数据下可以得到84分。

    然后发现这个转移的条件是$pre_i-pre_j>=f_j$,即$pre_i>=f_j+pre_j$,因为$pre$数组是单调的,所以显然转移点也是单调的。

    然后用一个单调队列维护一下就好了。

    因为数据范围过大,需要写高精度。

    为了防止$MLE$,可以在转移的同时不记录答案而是维护转移点。

    最后统计一下答案就好了。

    D2T3-树的重心

    看题发现部分分有75,而且蛮好打的。

    暴力和链可以直接打出来。

    对于满二叉树,打个表就发现答案只出现在根节点和它的两个儿子的系数是2的一个次幂,其它节点的系数都是1。

    考试最后几分钟大概想到了答案一定只出现在重链上。

    所以维护重链,同时二分答案就可以了,当然比较简单的做法是用倍增来实现这个二分的过程。

    然而并不会维护父亲方向的重链,实际上是一个简单的换根$dp$,难度并不是很大。

    在计算的过程中多打几个特判就好了。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=3e5+7;
    int n,tot=1;
    int head[N],nxt[N<<1],to[N<<1];
    int sz[N],son[N],son2[N];
    int f[N][20];
    long long ans;
    void dfs1(int x,int fa){
        sz[x]=1;
        for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa){
            dfs1(to[i],x);
            sz[x]+=sz[to[i]];
            if(sz[to[i]]>sz[son[x]]) son2[x]=son[x],son[x]=to[i];
            else if(sz[to[i]]>sz[son2[x]]) son2[x]=to[i];
        }
        f[x][0]=son[x];
        for(int i=1;i<=18;++i) f[x][i]=f[f[x][i-1]][i-1];
    }
    int solve(int x){
        if(!son[x]) return x;
        int now=x,ans=0;
        for(int i=18;~i;--i) if(sz[x]-sz[f[now][i]]<sz[x]/2) now=f[now][i];
        if(sz[son[now]]<=sz[x]/2&&sz[x]-sz[now]<=sz[x]/2) ans+=now;
        now=son[now];
        if(sz[son[now]]<=sz[x]/2&&sz[x]-sz[now]<=sz[x]/2) ans+=now;
        return ans;
    }
    void dfs2(int x,int fa){
        for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa){
            int tmp=son[x],s=sz[x];
            sz[x]=n-sz[to[i]];
            if(to[i]==son[x]) son[x]=son2[x];
            if(fa&&sz[fa]>sz[son[x]]) son[x]=fa;
            f[x][0]=son[x];
            for(int j=1;j<=18;++j) f[x][j]=f[f[x][j-1]][j-1];
            ans+=solve(to[i]);
            ans+=solve(x);
            dfs2(to[i],x);
            son[x]=tmp; sz[x]=s; f[x][0]=son[x];
            for(int j=1;j<=18;++j) f[x][j]=f[f[x][j-1]][j-1];
        }
    }
    inline void add(int a,int b){
        nxt[++tot]=head[a]; to[head[a]=tot]=b;
        nxt[++tot]=head[b]; to[head[b]=tot]=a;
    }
    inline int read(register int x=0,register char ch=getchar(),register int f=0){
        for(;!isdigit(ch);ch=getchar()) f=ch=='-';
        for(; isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
        return f?-x:x;
    }
    int main(){
        freopen("centroid.in","r",stdin);
        freopen("centroid.out","w",stdout);
        int T=read();
        while(T--){
            tot=1; ans=0;
            memset(head,0,sizeof(head));
            memset(son,0,sizeof(son));
            memset(son2,0,sizeof(son2));
            n=read();
            for(int i=1;i<n;++i) add(read(),read());
            dfs1(1,0); dfs2(1,0);
            printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    内存使用过高点检checklist
    Ubuntu linux系统下 su:出现: authentication failure的解决办法
    static完全解析
    C语言开发规范
    单片机、ARM、PC程序执行介质区别
    2021来了,一份小菜鸡的2020总结!
    Linux命令进阶篇-文件查看与查找
    LINUX常用命令(二)
    Linux常用命令(一)
    百钱百鸡
  • 原文地址:https://www.cnblogs.com/skyh/p/11953978.html
Copyright © 2011-2022 走看看