一.闲话

最近准备省选,好久没写题解了qwq,今天的比赛挺有意思的,就来写几道题吧,qwq

二.题解

G.Mathematical Modelling Class

这道题只要读懂题其实挺简单的。但是貌似没几个人读然后被我这个菜鸡拿了一血(大雾)

题目大意:

有n个人,将每个人划分进A,B两组中的一组,第i个人划分进A组后,对A组的贡献为ai;划分进B组后,对B组的贡献为bi(ai,bi={0,1}),求是否存在一种方案,使得A组划分进了个人,B组划分进剩下的人,且A,B组的总贡献一样。若存在输出任意一种划分方式,若不存在输出-1。

读题后,我们发现,其实,一共只有四种人,即:

1.ai=0,bi=0

2.ai=0,bi=1

3.ai=1,bi=0

4.ai=1,bi=1

那么,我们只需要分别统计下这四种人分别有几个,再来看怎么划分,题目就会简单许多。

设第i种人有s[i]个,m=

那么,我们考虑枚举最终两组的总贡献,设为S。

那么,我们就有,对于A组的人来说,一定有:

A组中,第一种人的个数+第二种人的个数=m-S,第三种人的个数+第四种人的个数=S

这样,我们只需要找到一种方案数,使得A组满足上述条件,并且剩下的人对B组的贡献也等于S即可

那么,我们只要分别枚举下第一/二种人和第三/四种人的个数,就可以知道A组中所有类型的人的数量了,然后我们再判断下是否成立,就可以了。

但是,如果我们同时枚举第一/二种人和第三/四种人的个数的话,复杂度就会上升至,再加上一开始的枚举最终总贡献,复杂度就变成了,显然不可行

那么,该怎么办呢?

一个简单的办法,我们参考折半搜索。先枚举第一/二种人的数量,然后记录下当前数量下第一/二种人对B组的总贡献,然后,我们再枚举第三/四种人的数量,算出当前数量下对B的总贡献,判断是否存在两个贡献和为S的情况即可。

复杂度

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5001;
bool dp[N];
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n,T=n,m;
        scanf("%d",&n);
        m=n/2+(n&1);
        string x,y;
        cin>>x>>y;
        int one=0,two=0,thr=0,fou=0;
        for(int i=0;i<n;++i){
            if(x[i]=='0'&&y[i]=='0')++one;
            if(x[i]=='0'&&y[i]=='1')++two;
            if(x[i]=='1'&&y[i]=='0')++thr;
            if(x[i]=='1'&&y[i]=='1')++fou;
        }
        int a=-1,b=-1,c=-1,d=-1;bool flag=0;
        for(int i=0;i<=m;++i){//上午班学生1的个数
            int l=m-i,r=i;//one和two一共选l个,thr和fou一共选r个
            //两边分别枚举
            memset(dp,0,sizeof(dp));
            for(int j=min(l,one);~j;--j){//枚举选j个one
                if(l-j>two)break;
                dp[two-l+j]=1;
            }
            for(int j=min(r,thr);~j;--j){//枚举选j个thr
                if(r-j>fou)break;
                int T=i-(fou-r+j);
                if(T>=0&&dp[T]){
                    flag=1;
                    c=j,d=r-j;b=two-T,a=l-b;
                    break;
                }
            }
            if(flag)break;
        }
        if(!flag){
            puts("-1");
            continue;
        }
        for(int i=0;i<n;++i){
            if(x[i]=='0'&&y[i]=='0'&&a){
                --a;
                printf("%d ",i+1);
            }
            if(x[i]=='0'&&y[i]=='1'&&b){
                --b;
                printf("%d ",i+1);
            }
            if(x[i]=='1'&&y[i]=='0'&&c){
                --c;
                printf("%d ",i+1);
            }
            if(x[i]=='1'&&y[i]=='1'&&d){
                --d;
                printf("%d ",i+1);
            }
        }
        puts("");
    }
    return 0;
}

L.Yet Another Bracket Sequence

注意到,一个括号匹配序列是否合法的充要条件是:

对于任意一个前缀,'('的个数大于等于')',且对于整体,'('的个数等于')'

那么,我们将题目变为维护每个前缀中'('的个数-')'的个数的值。

如果我们维护出来了,只有所有前缀中的值最小的那个>=0,且最后的前缀的值为0,那么这就是一个合法的括号匹配了。

如果我们把第x个括号改变了的话,那么,相当于把x-n的前缀的值+2/-2,那么,用线段树维护区间修改+区间最值即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
char ch[N];int W[N<<2],laz[N<<2];
int s[N];
inline void down(int now){
    W[now<<1]+=laz[now],W[now<<1|1]+=laz[now];
    laz[now<<1]+=laz[now],laz[now<<1|1]+=laz[now];
    laz[now]=0;
}
inline void update(int now){
    W[now]=min(W[now<<1],W[now<<1|1]);
}
inline void build(int now,int l,int r){
    if(l==r){
        W[now]=s[l];
        return;
    }
    int mid=(l+r)>>1;
    build(now<<1,l,mid),build(now<<1|1,mid+1,r);
    update(now);
}
inline void insert(int now,int l,int r,int lc,int rc,int x){
    if(lc<=l&&r<=rc){
        W[now]+=x,laz[now]+=x;
        return;
    }
    down(now);
    int mid=(l+r)>>1;
    if(lc<=mid)insert(now<<1,l,mid,lc,rc,x);
    if(rc>mid)insert(now<<1|1,mid+1,r,lc,rc,x);
    update(now);
}
inline int find(int now,int l,int r,int x){
    if(l==r){
        return W[now];
    }
    down(now);
    int mid=(l+r)>>1;
    if(x<=mid)return find(now<<1,l,mid,x);
    return find(now<<1|1,mid+1,r,x);
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    scanf("%s",ch+1);
    for(int i=1;i<=n;++i){
        int val;
        if(ch[i]=='(')val=1;
        else val=-1;
        s[i]=s[i-1]+val;
    }
    build(1,1,n);
    while(m--){
        int x;
        scanf("%d",&x);
        if(ch[x]=='('){
            ch[x]=')';
            insert(1,1,n,x,n,-2);
        }else{
            ch[x]='(';
            insert(1,1,n,x,n,2);
        }
        if(W[1]==0&&find(1,1,n,n)==0){
            puts("Yes");
        }else{
            puts("No");
        }
    }
    return 0;
}

M.Yet Another Stones Game

这题用SG函数推的。。。

n=2大家都会做就不多说了。

当n=4时,
我们假设有两个数,l=r,

那么,我们明显有如果我们拿这个点对(l,r)

当l=0时
sg(l,r)=0 [无法拿,必败]

当l=1时
sg(l,r)=1(算mex即可)

当l=2时
sg(l,r)=2
...
于是,我们有sg(l,r)=l

更一般的,对于操作点对(a1,a2...ak)我们不难得出

sg(a1,a2...ak)=min(a1,a2...ak)

那么,根据nim游戏的性质,因为每次我们取n/2个点,那么如果我们确定了取的n/2个点,整个游戏的sg值即为

有nim游戏的结论可知,若整个局面的sg值为0,则先手必败,否则先手必胜,那么我们只要判断下是否存在一种情况使得整个局面sg值不为0即可。

又因为两个部分的sg值是各个部分的最小值,所以,最佳的划分方法是,我们将最小的n/2的数划为一组,剩下的划为另一组,然后,我们判断他们的sg是否相等即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[51];
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
        }
        sort(a+1,a+n+1);
        if(a[n/2+1]!=a[1]){
            puts("Happy Little Gyro");
        }else{
            puts("Sad Little Gyro");
        }
    }
    return 0;
}