zoukankan      html  css  js  c++  java
  • ABC223题解

    链接

    A

    水题,特判 (0) 并看 (X) 是否能被 (100) 整除。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 2010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int main(){
        int x;read(x);
        if(x%100||!x) printf("No");
        else printf("Yes");
    }
    

    B

    注意到字符串长度为 (1000),所以直接暴力做出循环串然后排序即可。

    也可以用最小表示法,最大的的求解大概和最小差不多,但是比较麻烦。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 3020
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    string s;
    string t[N];
    int tail,len;
    
    inline string Left(string s){
        string now;now.clear();
        for(int i=1;i<len;i++) now+=s[i];
        now+=s[0];return now;
    }
    
    int main(){
        cin>>s;t[++tail]=s;
        len=s.length();
        for(int i=1;i<=len-1;i++){
            s=Left(s);t[++tail]=s;
        }
        sort(t+1,t+tail+1);
        cout<<t[1]<<'
    ';
        cout<<t[tail];
        return 0;
    }
    

    C

    这个题有一点诈骗,一开始想两边同时走,但这样太难于实现,不难发现每个人走的时间相同,所以我们只要处理处每个人走的时间然后直接走就可以。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 100010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int n;
    struct Node{
        dd a,b;
    }c[N];
    dd t,dis;
    
    int main(){
        read(n);
        for(int i=1;i<=n;i++){
            read(c[i].a);read(c[i].b);
            t+=c[i].a/c[i].b;
        }
        t/=2;
        for(int i=1;i<=n;i++){
            if(t*c[i].b>=c[i].a){
                t-=c[i].a/c[i].b;
                dis+=c[i].a;
            }
            else{
                dis+=c[i].b*t;
                break;
            }
        }
        printf("%0.10lf
    ",dis);
        return 0;
    }
    

    D

    就是求拓扑排序,至于字典序最小,我们可以用优先队列来做。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 200010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    struct edge{
        int to,next;
        inline void Init(int to_,int ne_){
            to=to_;next=ne_;
        }
    }li[N];
    int head[N],tail;
    
    inline void Add(int from,int to){
        li[++tail].Init(to,head[from]);
        head[from]=tail;
    }
    
    int n,m,rd[N],xulie[N],tt;
    
    struct cmp{
        inline bool operator () (int a,int b){return a>b;}
    };
    priority_queue<int,vector<int>,cmp> q;
    
    inline void bfs(){
        while(q.size()){
            int top=q.top();q.pop();
            xulie[++tt]=top;
            for(int x=head[top];x;x=li[x].next){
                int to=li[x].to;rd[to]--;
                if(!rd[to]) q.push(to);
            }
        }
    }
    
    int main(){
        read(n);read(m);
        for(int i=1;i<=m;i++){
            int from,to;read(from);read(to);
            Add(from,to);rd[to]++;
        }
        for(int i=1;i<=n;i++) if(!rd[i]) q.push(i);
        bfs();
        if(tt!=n) return puts("-1"),0;
        for(int i=1;i<=n;i++) printf("%d ",xulie[i]);
        return 0;
    }
    

    E

    这个题比赛场上没有做出来,实际上这个题还是有一点技巧的,这个题启示我从简单情况开始考虑,然后一步步想出正确的贪心策略。

    首先我们考虑放两个矩形的情形,不难发现,如果有合法方案的话,一定存在一条线,然后这两个矩形分别在线的两边,并且这个线满足平行于 (x) 轴或 (y) 轴。

    上面这个性质不难证明,我们考虑如何放置。

    我们考虑第一个矩形如何放,我们先假设这条线平行于 (x) 轴,设这个矩形的大小为 (S),那么我们就令 (x=leftlceil frac{S}{X} ight ceil),其中 (X) 为题目给定的放置的地方大小。

    然后我们在上面放第二个矩形,只需要检查其面积即可。

    然后我们考虑三个矩形的情形,有一个性质是我们仍然可以画一条平行于 (x) 轴或平行于 (y) 轴的直线,满足一边有一个矩形,另一边有两个矩形。

    以上性质通过反证法不难证明。

    所以我们仍然可以先看这条线,贪心的放一个矩形,然后问题转化成了上面那个问题。

    不得不说 std 实现的是真的好,放哪个矩形,平行于哪个轴,都可以通过交换来实现,最终本题可以 (O(1)) 解决。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define int long long
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N number
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int x,y,a,b,c;
    
    inline bool Check(int x,int y,int b,int c){
        for(int i=1;i<=2;i++){
            int len=(b+x-1)/x;
            if(len<y&&(y-len)*x>=c) return 1;
            swap(x,y);
        }
        return 0;
    }
    
    inline bool Solve(int x,int y,int a,int b,int c){
        for(int i=1;i<=2;i++){
            for(int j=1;j<=3;j++){
                int len=(a+x-1)/x;
                if(len<y&&Check(x,y-len,b,c)) return 1;
                swap(a,b);swap(b,c);
            }
            swap(x,y);
        }
        return 0;
    }
    
    signed main(){
        read(x);read(y);read(a);read(b);read(c);
        puts(Solve(x,y,a,b,c)?"Yes":"No");
        return 0;
    }
    

    F

    这个题在思维难度上比 E 题要简单许多,一看就是一个线段树小清新题。

    题目是维护某个区间的括号序列是否合法,带修改。

    考虑到如果一个区间内的括号序列不合法,那么让合法的全部消掉之后一定变成了这样:

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

    我们考虑维护能消的都消去之后左括号和右括号的数量,然后合并的时候注意消去就可以。

    如果一段区间内的括号序列合法,那么合并的结果括号数量一定为 (0)

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 2000100
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
        
    }
    
    int n,m;
    char s[N];
    
    struct Node{
        int lt,rt;
        inline Node(){lt=0;rt=0;}
        inline Node operator + (const Node &b)const{
            Node a;a.lt=a.rt=0;
            a.rt=rt;a.lt=b.lt;
            if(lt<b.rt) a.rt+=b.rt-lt;
            else a.lt+=lt-b.rt;
            return a;
        }
    }p[N<<2];
    
    #define ls k<<1
    #define rs k<<1|1
    struct SegmentTree{
        inline void PushUp(int k){
            // if(p[ls].lt==p[rs].rt){p[k].rt=p[ls].rt;p[k].lt=p[rs].lt;}
            // else if(p[ls].lt<p[rs].rt){p[k].rt=p[ls].rt+p[rs].rt-p[ls].lt;p[k].lt=p[rs].lt;}
            // else{p[k].rt=p[ls].rt;p[k].lt=p[ls].lt-p[rs].rt+p[rs].lt;}
            p[k]=p[ls]+p[rs];
        }
        inline void Build(int k,int l,int r){
            if(l==r){
                if(s[l]=='(') p[k].lt++;
                else p[k].rt++;return;
            }
            int mid=(l+r)>>1;
            Build(ls,l,mid);Build(rs,mid+1,r);
            PushUp(k);
        }
        inline void Change(int k,int l,int r,int w){
            if(l==r){
                p[k].lt^=1;p[k].rt^=1;return;
            }
            int mid=(l+r)>>1;
            if(w<=mid) Change(ls,l,mid,w);
            else Change(rs,mid+1,r,w);
            PushUp(k);
        }
        inline Node Ask(int k,int l,int r,int z,int y){
            if(l==z&&r==y){return p[k];}
            int mid=(l+r)>>1;
            if(y<=mid) return Ask(ls,l,mid,z,y);
            else if(z>mid) return Ask(rs,mid+1,r,z,y);
            else return Ask(ls,l,mid,z,mid)+Ask(rs,mid+1,r,mid+1,y);
        }
    }st;
    
    int main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        read(n);read(m);
        scanf("%s",s+1);
        st.Build(1,1,n);
        for(int i=1;i<=m;i++){
            int op,l,r;read(op);read(l);read(r);
            if(op==1){
                if(s[l]==s[r]) continue;
                swap(s[l],s[r]);
                st.Change(1,1,n,l);st.Change(1,1,n,r);
            }
            else{
                Node ans=st.Ask(1,1,n,l,r);
                if(ans.lt==0&&ans.rt==0){
                    puts("Yes");
                }
                else puts("No");
            }
        }
        return 0;
    }
    

    G

    G 题是一个比较复杂的换根 dp,换根 dp 的题我也不常做,这个题算是帮我熟悉一下。

    因为要对每个节点求,所以不难想到是一个换根 dp,然后我们考虑先求出整棵树初始的答案。

    (f_{k,0/1}) 表示这个节点在与其儿子匹配或不匹配的条件下,(k) 这颗子树的答案是多少,思考之后不难得出转移:

    [f_{k,0}=sumlimits_{sin son_k}max(f_{s,0},f_{s,1})\ f_{k,1}=maxlimits_{toin son_k}{ sumlimits_{sin son_k,s eq to}max(f_{s,0},f_{s,1})+f_{to,0}+1 } ]

    注意到我们可以先做第一个转移,然后第二个转移枚举一遍就可以了,具体转移细节看代码。这个 dp 的复杂度为 (O(n))

    然后我们考虑如何计算去掉每一个节点及其连边后的匹配。首先我们默认以 (1) 为根。

    注意到如果我们把 (k) 去掉,这个答案由这样几部分组成:

    1. (k) 的儿子的子树,这个答案就是 (sum_{sin son_k}max(f_{s,0},f_{s,1}))

    2. 除去 (k) 的子树的部分,这个部分我们设为 (g_k)

    然后我们考虑如何处理出 (g_{k}),首先我们还是要考虑 (k) 与其父亲的连边,设 (g_{k,0/1}) 表示考虑整棵树去掉 (k) 的子树的剩下部分的贡献,(0/1) 表示 (k) 和其父亲的连边选还是不选。设 (k) 的父亲为 (fa)

    考虑转移,不难得到:

    [g_{k,1}=sumlimits_{sin son_{fa},s eq k}max(f_{s,0},f_{s,1})+g_{fa,0}+1\ g_{k,0}=egin{cases} maxlimits_{uin son_{fa},u eq k}{ sumlimits_{sin son_{fa},s eq u,s eq k} max(f_{s,0},f_{s,1})+f_{u,0}+g_{fa,0}+1 }\ sumlimits_{sin son_{fa},s eq k}max(f_{s,0},f_{s,1})+max(g_{fa,0},g_{fa,1}) end{cases} ]

    解释一下 (g_{k,0}) 的转移,第一个式子考虑的是 (fa) 与其中一个儿子相连,第二个式子考虑的是 (fa) 与其父亲相连或没有相连。

    第一个转移不难做,第二个转移的第二个式子不难做,我们考虑如果做第二个转移的第一个式子。

    不难发现一个式子,对于大部分的 (k)(u) 的选择都是一定的。这个 (u) 可以贪心的求出,对于 (k eq u),我们直接转移就可以,这个不难做,对与 (k=u) 我们再去枚举。

    整个 dp 的复杂度仍然可以做到线性。

    然后处理出去掉每个节点的答案,统计即可。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 200010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    template<typename T> inline T Max(T a,T b){return a<b?b:a;}
    
    struct edge{
        int to,next;
        inline void Init(int to_,int ne_){
            to=to_;next=ne_;
        }
    }li[N<<1];
    int head[N],tail;
    
    inline void Add(int from,int to){
        li[++tail].Init(to,head[from]);
        head[from]=tail;
    }
    
    int f[N][2],g[N][2],ans[N],Ans,n;
    
    inline void Dfs(int k,int fa){
        bool op=0;
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa) continue;
            Dfs(to,k);f[k][0]+=Max(f[to][1],f[to][0]);
        }
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa) continue;
            f[k][1]=Max(f[k][1],f[k][0]-Max(f[to][1],f[to][0])+f[to][0]+1);
            op=1;
        }
        if(!op) f[k][1]=-INF;
        // printf("f[%d][0]=%d
    f[%d][1]=%d
    ",k,f[k][0],k,f[k][1]);
    }
    
    inline void Init(){
        read(n);
        for(int i=1;i<=n-1;i++){
            int from,to;read(from);read(to);
            Add(from,to);Add(to,from);
        }
    }
    
    inline void Dp(int k,int fa){
        int sum=0,X=-1;
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa) continue;
            sum+=Max(f[to][0],f[to][1]);
            if(f[to][0]>=f[to][1]) X=to;
        }
        if(X==-1){
            int cha=INF;
            for(int x=head[k];x;x=li[x].next){
                int to=li[x].to;
                if(to==fa) continue;
                if(f[to][1]-f[to][0]<cha){
                    cha=f[to][1]-f[to][0];
                    X=to;
                }
            }
        }
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa) continue;
            g[to][1]=sum-Max(f[to][0],f[to][1])+g[k][0]+1;
            g[to][0]=sum-Max(f[to][0],f[to][1])+Max(g[k][0],g[k][1]);
        }
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa||to==X) continue;
            g[to][0]=Max(g[to][0],sum-Max(f[to][0],f[to][1])-Max(f[X][0],f[X][1])+f[X][0]+1+g[k][0]);
            g[X][0]=Max(g[X][0],sum-Max(f[X][0],f[X][1])-Max(f[to][0],f[to][1])+f[to][0]+1+g[k][0]);
        }
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa) continue;
            Dp(to,k);
        }
    }
    
    inline void Calc(int k,int fa){
        ans[k]=g[k][0];
        // printf("g[%d][0]=%d
    g[%d][1]=%d
    ",k,g[k][0],k,g[k][1]);
        for(int x=head[k];x;x=li[x].next){
            int to=li[x].to;
            if(to==fa) continue;
            ans[k]+=Max(f[to][0],f[to][1]);
            Calc(to,k);
        }
    }
    
    inline void Print(){
        int tot=0;
        Ans=Max(f[1][0],f[1][1]);
        // printf("Ans=%d
    ",Ans);
        for(int i=1;i<=n;i++){
            if(Ans==ans[i]) tot++;
            // printf("ans[%d]=%d
    ",i,ans[i]);
        }
        printf("%d
    ",tot);
    }
    
    int main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        Init();Dfs(1,0);Dp(1,0);Calc(1,0);Print();
    }
    

    H

    第一次做线性基的题目。

    我们考虑到集合异或,一定是线性基,不难判断一个数是否通过某个集合异或出来,我们考虑如何做那个区间限制。

    首先可以线段树线性基暴力合并,然后查找区间,这样复杂度大概可以过?(不知道,没写过)

    我们考虑转化题目条件,(x) 可以被 ([l,r]) 中的数异或出来相当于我们在 ([1,r]) 这个前缀中异或,并且用到的数都是大于等于 (l) 的,如果要维护这个信息,我们必须要考虑如果让我们构造出来的线性基编号尽可能的大。

    构造过程具体看代码,代码后我们来证明一下构造的正确性。

    构造出来后,直接查询即可。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define int long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 66
    #define M 400010
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    struct LinearBasis{
        int b[N],ID[N];
        inline void Insert(int val,int id){
            for(int i=60;i>=0;i--){
                if(((val>>i)&1)==0) continue;
                if(!b[i]){b[i]=val;ID[i]=id;return;}
                else{
                    if(ID[i]<id){swap(ID[i],id);swap(val,b[i]);}
                    val^=b[i];
                }
            }
        }
        inline bool Ask(int val,int l){
            for(int i=60;i>=0;i--){
                if(((val>>i)&1)==0) continue;
                if(!b[i]||ID[i]<l) return 0;
                val^=b[i];
            }
            return 1;
        }
    }LB[M];
    
    int n,q;
    
    signed main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        read(n);read(q);
        for(int i=1;i<=n;i++){
            int x;read(x);LB[i]=LB[i-1];LB[i].Insert(x,i);
        }
        for(int i=1;i<=q;i++){
            int l,r,x;read(l);read(r);read(x);
            puts(LB[r].Ask(x,l)?"Yes":"No");
        }
        return 0;
    }
    

    考虑证明正确性:

    • 首先,这个线性基仍然是线性无关的,这个非常显然。

    • 然后我们考虑为什么交换之后这个线性基依旧是正确的,我们考虑是 (u)(v) 换掉,随后 (v) 变成 (v xor u)

      我们发现这和朴素的线性基构造只有一个区别,就是这个位置上的数变了,往下走的数并没有变化。

      我们考虑这个东西是否满足线性基性质,不难发现,我们不用考虑某些位置因为之前插入的时候异或的是 (v) 而无法构造,事实上,我们往下插的时候,相当于插入了 (v),所以这个线性基一定是可以异或出 (v) 的,遍历到的位置变成了那个数异或 (u),没有被遍历到的不参与构成 (v)

  • 相关阅读:
    Java线程的学习(一)——以售票系统为例
    web笔记
    ssm2之applicationContext.xml文件配置
    ssm笔记1
    在ViewHolder中添加item点击事件接口(自定义
    Java反射机制
    新手导航页(小圆点
    jsoup
    TextView设置随机大小和颜色
    常用IO流
  • 原文地址:https://www.cnblogs.com/TianMeng-hyl/p/15466435.html
Copyright © 2011-2022 走看看