Loading

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]<<'\n';
    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\n",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=\left\lceil \frac{S}{X} \right\rceil\),其中 \(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}=\sum\limits_{s\in son_k}\max(f_{s,0},f_{s,1})\\ f_{k,1}=\max\limits_{to\in son_k}\{ \sum\limits_{s\in son_k,s\neq to}\max(f_{s,0},f_{s,1})+f_{to,0}+1 \} \]

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

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

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

  1. \(k\) 的儿子的子树,这个答案就是 \(\sum_{s\in 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}=\sum\limits_{s\in son_{fa},s\neq k}\max(f_{s,0},f_{s,1})+g_{fa,0}+1\\ g_{k,0}=\begin{cases} \max\limits_{u\in son_{fa},u\neq k}\{ \sum\limits_{s\in son_{fa},s\neq u,s\neq k} \max(f_{s,0},f_{s,1})+f_{u,0}+g_{fa,0}+1 \}\\ \sum\limits_{s\in son_{fa},s\neq 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\neq 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\nf[%d][1]=%d\n",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\ng[%d][1]=%d\n",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\n",Ans);
    for(int i=1;i<=n;i++){
        if(Ans==ans[i]) tot++;
        // printf("ans[%d]=%d\n",i,ans[i]);
    }
    printf("%d\n",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\)

posted @ 2021-10-26 16:31  hyl天梦  阅读(98)  评论(3编辑  收藏  举报