AtCoder Grand Contest 026题解

前四个题都很基础啊。

E - Synchronized Subsequence

这种思路不是很好想啊。

我们发现这个题直接逐位确定非常的难啊,根本没法做。

正解是将原序列分成了若干个段,使得$a$的个数等于$b$的个数。

并且要保证是最小的划分。

之后有一个结论就是在同一段里$a$与对应$b$的前后关系是一样的。

这个可以用不存在一种更小的划分来证,即中间不存在前缀$cnt[a]==cnt[b]$。

对于$a$在$b$i前面的段,显然分成$ababab......$最优。

对于$a$在$b$后面的段,是一个完整的后缀最优。

证明的话就是我们删掉一个前缀并选择剩下的一些点后,剩下没有选的位置都是$b$,所以一定是选上最优。

第二种我用了一个$cnt$代表开头$b$的个数还有一个串$S$来表示这个字符串。

这样就可以用后缀自动机$O(1)Lcp$来优化到$O(n)$了。

#include <bits/stdc++.h>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;
inline int read() {
    int f=1,sum=0;
    char x=getchar();
    for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1;
    for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0';
    return f*sum;
}

#define N 10000
#define M 1000005
int n;
char s[M];
int be[M],f[M],buc[M];
int id[M],Max[M],pos[M],nxt[M],sum[M],L[M],R[M];

inline bool check(int x,int xr,int y,int yr) {
    int len=min(xr-x,yr-y);
    for1(0,len,i)
        if(s[x+i]!=s[y+i]) return s[x+i]<s[y+i];
    return xr-x<=yr-y;
}

inline bool check_Max(int x,int y) {
    if(f[x]<N&&f[y]>N) return 0;
    if(f[x]>N&&f[y]<N) return 1;
    if(f[x]<N) return f[x]>=f[y];
    if(f[x]/N>f[y]/N||(f[x]/N==f[y]/N&&check(f[y]%N,R[y],f[x]%N,R[x]))) return 1;
    return 0;
}

int main() {
    //freopen("a.in","r",stdin);
    //freopen("mine.out","w",stdout);
    n=read()*2;
    scanf("%s",s+1);
    int shu=0;
    for1(1,n,i) {
        if(!shu) be[i]=++be[0];
        else     be[i]=be[i-1];
        shu+=('a'==s[i])-('b'==s[i]);
    }
    for1(1,n,i) R[be[i]]=i;
    FOR2(n,1,i) L[be[i]]=i;
    
    for(int l=1,r;l<=n;l=r+1) {
        r=l;
        while (be[r+1]==be[l]) ++r;
        if(s[l]=='a') {
            //怎么都没想到这里错啊,太马虎了
            int h=1,t=0;
            for1(l,r,i) 
                if(s[i]=='a') buc[++t]=i;
                else pos[buc[h++]]=i;
            int now=l;
            while (1) {
                ++f[be[l]];
                now=pos[now]+1;
                while (now<=r&&s[now]!='a') ++now;
                if(now>r) break;
            }
        }
        else {
            sum[l-1]=0;
            for1(l,r,i) sum[i]=sum[i-1]+(s[i]=='b');
            int h=1,t=0;
            for1(l,r,i) 
                if(s[i]=='b') buc[++t]=i;
                else pos[buc[h++]]=i;
            FOR2(r,l,i)
                if(s[i]=='b') nxt[i]=nxt[i+1];
                else nxt[i]=i;
            int pre=sum[pos[l]],id=pos[l];
            for1(l,r-1,i) if(s[i]=='b') {
                int x[2]={sum[pos[i]]-sum[i],pos[i]+1};
                x[0]+=nxt[x[1]]-x[1];
                x[1]=nxt[x[1]];
                if(x[0]>pre||(x[0]==pre&&check(id,r,x[1],r))) pre=x[0],id=x[1];
            }
            f[be[l]]=pre*N+id;
        }
    }
    
    //abab是累加,不能比较字典序。。
    FOR2(be[0],1,i) {
        Max[i]=Max[i+1];
        if(f[i]>N&&check_Max(i,Max[i])) Max[i]=i;
    }
    //for1(1,be[0],i) cout<<f[be[i]]<<" "; cout<<endl;
    for1(1,be[0],i) if(!Max[i]) Max[i]=i;
    int pos=0;
    while (1) {
        pos=Max[pos+1];
        if(f[pos]>N) {
            FOR2(f[pos]/N,1,i) putchar('b');
            for1(f[pos]%N,R[pos],i) putchar(s[i]);
        }
        else {
            for1(1,f[pos],i) putchar('a'),putchar('b');
        }
        if(pos==be[0]) break;
    }
    puts("");
}

F - Manju Game

$O(n^3)$是显然的,然后就想不出来了。

正解分析性质就很多了。。

首先黑白染色,$10101010......$。

若$n$是偶数,有两个结论:

1>先手答案大于等于$max(B,W)$

2>后手答案大于等于$min(B,W)$

第一个好证啊。。直接选一个边界就好了,第二个直接归纳法证一下就好了。

这样$n$为偶数就可以直接输出了。

当$n$为奇数时,第一步若我们选了黑色,依然好说。

但是选白色就比较复杂了。

我们考虑因为如果我们选白色,之后一定一直都是我们先手,这样我们就得到了一个白色点的集合。

显然我们可以在某个区间内让我们选黑色,后手选白色,其他区间我们要白色,后手选黑色。

如果二分答案之后存在一个白色点的集合使得任意一个区间都满足$B-W>=mid$即可。

这个显然是正确的。因为我们选的那个区间一定是$B-W$最小的区间,如果后手不让我们选这个区间是更亏的。

#include <bits/stdc++.h>
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;

#define M 1000005
int n;
int a[M];

inline bool check(int x) {
    int sum=0;
    for(int i=1;i<=n;i+=2) {
        if(sum>=x) sum=max(sum+a[i]-a[i-1],a[i]);
        else sum+=a[i]-a[i-1];
    }
    return sum>=x;
}

int main () {
    //freopen("a.in","r",stdin);
    scanf("%d",&n);
    for1(1,n,i) scanf("%d",a+i);
    int sum[2]={0};
    for1(1,n,i) sum[i&1]+=a[i];
    if(!(n&1)) printf("%d %d\n",max(sum[0],sum[1]),min(sum[0],sum[1]));
    else {
        int l=0,r=sum[1],mid,ans;
        while (l<=r) {
            mid=l+r>>1;
            if(check(mid)) ans=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d %d\n",sum[0]+ans,sum[1]-ans);
    }
}

转载于:https://www.cnblogs.com/asd123www/p/9712789.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值