AtCoder Regular Contest 088

43 篇文章 1 订阅
27 篇文章 0 订阅

C:

给出一个X,Y
求出一个序列 A ,满足Ai1|AiAi1AiAi[X,Y],求这个序列的最长长度。


模拟,序列第一位为X,每一位为之前一位的两倍, O(logY)

D:

给出一个01串,可以将01串上任意连续的长度不小于k的一个子序列翻转,求k的最大值。


很容易发现一点,假设我们将翻转的区间的一端固定在整个序列的端点,那么我们可以通过翻转前k个点,再翻转前k+1个点来满足单点翻转(即k+1号点的翻转),这样一来,我们可以确定
这里写图片描述
除了中间2K-N个点以外,所有的点都可以不影响其他点的情况下进行翻转。
相对应的,再考虑中间这2K-N个点,发现每次翻转必定会包含这个连续的区间,也就是说这个区间内的点,被翻转情况是一致的。现在答案就很显然了,从中间开始,寻找尽量多的连续的相同的区间,这个区间的长度越长,k就越大。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 100010
using namespace std;
char a[MAXN];
int main(){
    SF("%s",a);
    int len=strlen(a);
    if(len%2==0){
        int mid=len/2-1;
        int y=0;
        if(a[mid]==a[mid+1]){
            y=1;
            while(mid-y>=0&&a[mid-y]==a[mid]&&a[mid-y]==a[mid+1+y])
                y++;
        }
        y--;
        PF("%d",mid+y+2);
    }
    else{
        int mid=len/2;
        int y=1;
        while(mid-y>=0&&a[mid-y]==a[mid]&&a[mid+y]==a[mid])
            y++;
        y--;
        PF("%d",mid+y+1);
    }
} 

E:

给出一个仅由小写字母组成的字符串,可以交换相邻两个字符,求最终能将整个字符串转化为回文串的最小操作次数,如不能做到,输出-1


经典的贪心题
设这个字符串为 c1c2....cn1cn
很容易想到,将与 c1 相同的字符移动到最右端是最优的,
证明很容易:
无论如何,必须有一个点在1号位置与n号位置的点相同,那么如果是除了 c1 以外的其他点,那么必然最终会将这两个点移动到两端,这个操作显然是多余的,
这里写图片描述
如上图红色箭头所指的位置必然比黑色箭头的位置要优一些。
如果是单个节点(即没有与之相同的另一个未匹配字符),这个节点必然会移动到中间。这里为了统计操作次数,需要用到树状数组来存储前缀和。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 200010
using namespace std;
char a[MAXN];
vector<int> topx[50];
bool del[MAXN];
int las[50],n,sum;
long long ans,tree[MAXN];
void change(int x,int adx){
    while(x<=n){
        tree[x]+=adx;
        x+=x&(x^(x-1));
    }
}
long long query(int x){
    long long res=0;
    while(x){
        res+=tree[x];
        x-=x&(x^(x-1));
    }
    return res;
} 
int main(){
    SF("%s",a);
    n=strlen(a);
    for(int i=0;i<n;i++){
        topx[a[i]-'a'].push_back(i);
    }
    for(int i=0;i<=30;i++)
        las[i]=topx[i].size()-1;
    for(int i=0;i<=30;i++)
        if(topx[i].size()%2==1)
            sum++;
    if(sum>1){
        PF("-1");
        return 0;   
    }
    for(int i=1;i<=n;i++)
        change(i,1);
    for(int i=0;i<n;i++)
        if(del[i]==0){
            int x=a[i]-'a';
            int y=topx[x][las[x]];
            int u=i+1;
            int v=y+1;
            del[i]=1;
            del[y]=1;
            if(u==v){
                ans+=(query(n)-query(u))/2;
            }
            else{
                ans+=query(n)-query(v);
            }
            change(u,-1);
            change(v,-1);
            las[x]--;
        }
    PF("%lld",ans);
} 

F:

PS:这道题让我深切体会到了JP人民的智(wu)慧(liao)与善(e)良(xin),从浅显易懂(disgusting)的题目描述到实现细节,都非常的优(wei)美(suo)。
题意:
给出一颗树,已知这棵树是按照一种特定的方式生成的:
首先有A条长度最长为B的链,
每次选中两个不连通的点,
将这两个点合并成一个点,直到形成一颗树(也就是给出的树)
求在A最小的情况下,A的值与B的最小值。


我们当然不能从构造的角度来看这个问题,
不难发现,每条最初的链都会形成树上的一条树链,
也就是说我们要将这棵树分成尽量少的树链,并在此基础上使得最长的链最短。

对于A的最小值应该是很容易想到的,其实就是奇度点的个数的一半。
很容易解释,为了使得树链尽量少,我们不可能将同一个点作为2条及以上的树链的端点(那样就可以合并这两条树链,进而减少树链的数量)。
因此,偶度点是不可能作为树链的端点的,也很容易想到,奇度点必然会成为一条树链的端点,因此,总的树链的个数就是奇度点的个数的一半。

再来解决第二问:
很容易想到树DP,设dp[i]表示到达i点时最短的路径。。。因为这只能判断最长长度小于等于某个特定值。所以只要加一个二分答案就可以了。
然后是一堆实现细节。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<set>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 100010
using namespace std;
int dp[MAXN];
int l,r,n,sum,ans;
multiset<int> s;
vector<int> a[MAXN];
bool dfs(int x,int fa,int maxl){
    for(int i=0;i<a[x].size();i++)
        if(a[x][i]!=fa)
            if(dfs(a[x][i],x,maxl)==0)
                return 0;
    s.clear();
    for(int i=0;i<a[x].size();i++)
        if(a[x][i]!=fa){
            if(dp[a[x][i]]+1>maxl){
                return 0;
            }
            s.insert(dp[a[x][i]]+1);
        }
    int flag=0;
    if(s.size()%2==1)
        flag=1;
    if(x==1)
        flag--;
    while(s.size()>1){
        int t=*s.rbegin();
        s.erase(s.find(t));
        multiset<int>::iterator it=s.lower_bound(maxl-t+1);
        if(it==s.begin()){
            if(flag==-1){
                return 0;
            }
            if(flag==1){
                dp[x]=t;
                flag=-1;
            }
            else{
                if(x!=1)
                    flag=1;
                else
                    flag=-1;
            }
        }
        else{
            it--;
            s.erase(it);
        }
    }
    if(s.size()==1){
        dp[x]=*s.begin();
        if(dp[x]>maxl){
            return 0;
        }
        s.erase(dp[x]);
    }
    return 1;
}
bool check(int maxl){
    memset(dp,0,sizeof dp);
    s.clear();
    if(dfs(1,0,maxl))
        return 1;
    return 0;
}
int u,v;
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    SF("%d",&n);
    for(int i=1;i<n;i++){
        SF("%d%d",&u,&v);
        a[u].push_back(v);
        a[v].push_back(u);
    }
    for(int i=1;i<=n;i++)
        if(a[i].size()%2==1)
            sum++;
    sum/=2;
    PF("%d ",sum);
    //PF("[%d]",check(6));
    l=1,r=n;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            r=mid-1;
        }
        else{
            l=mid+1;
        }
    }
    PF("%d",ans);
}
比赛总结:

嗯。。与上一次做Atcoder相比,这次做得还不错,过了前三题(第四题题意太长不想看),但简单的代码实现了很久!第三题的树状数组居然写了我半个小时!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值