把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

20180716 [AtCoder]CF697 EASY+SoundHound HARD【值得琢磨实现细节】

A - Romaji CodeForces - 1008A
B - Turn the Rectangles CodeForces - 1008B
C - Reorder the Array CodeForces - 1008C
D - Saving Snuuk AtCoder - 4190
E - + Graph AtCoder - 4191 (留坑,至今未果,寻病终


A

题目简述
把一个数组分成连续的k段,每一段的价值为这一段最大元素的值,求每一段价值之和的最大值。并输出每一段的长度。
分析
贪心地思考一下,这个数组无论如何都可以分成k段(n>=k)则将这个数组前k大的数字分在k个部分中 问题就在于怎么输出 细节考虑比较多 考试的时候实现得不是很好 也卡了很久

//考试的实现
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 2005
int N,K,ans,cn;
struct node{
    int v,p;
}a[MAXN],c[MAXN];
int b[MAXN];
bool cmp(node x,node y)
{
    return x.v>y.v;
}
bool cmp2(node x,node y)
{
    return x.p<y.p;
}
int main()
{
    scanf("%d %d",&N,&K);
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&a[i].v);
        a[i].p=i;
        b[i]=a[i].v;
    }
    sort(a+1,a+N+1,cmp);
    for(int i=1;i<=K;i++)
    {
        ans+=a[i].v;
        c[++cn].v=a[i].v;
        c[cn].p=a[i].p;
    }
    sort(c+1,c+cn+1,cmp2);
    int n=1;
    printf("%d\n",ans);
    int last=0;
    for(int i=1;i<=N;i++)
        if(b[i]==c[n].v)
        {
            if(n==cn)
            {
                printf("%d",N-last);
                return 0;
            }
            printf("%d ",i-last);
            last=i;
            n++;
        }
}

就是保存了前k大的数在数组中的位置,输出的时候以此分节,但却是实现不够优秀(包括最后的for循环其实多余了 都已经存有了位置 for 1~k 就OK)。

//更优秀的实现
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 2000;
struct node{
    int val, id;
}a[MAXN + 5];
bool operator < (node a, node b) {
    return a.val < b.val;
}
int b[MAXN + 5], bcnt;
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i=1;i<=n;i++) {
        scanf("%d", &a[i].val);
        a[i].id = i;
    }`
    sort(a+1, a+n+1);
    int ans = 0;
    for(int i=n;i>=n-k+1;i--) {
        ans += a[i].val;
        b[++bcnt] = a[i].id;
    }
    b[++bcnt] = n+1;
    printf("%d\n", ans);
    sort(b+1, b+bcnt+1);
    printf("%d", b[2]-1);
    for(int i=2;i<bcnt;i++)
        printf(" %d", b[i+1]-b[i]);
}

B

题目简述
把数组分成连续的3段 使第一段的元素之和等于第三段的元素之和 每一段都可以为空 求第一段元素之和的最大值
分析
Tip:正解&简洁的代码请往下翻
我一拿到就想到了前缀和&后缀和
a[i]表示1~i的和 b[j]表示j~N的和 若i,j满足i < j并且a[i]==b[j] 更新ans
然后 我就这么写了 然后 T了QAQ

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 200006
int N,ans=-10;
int a[MAXN],b[MAXN],c[MAXN];
int main()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&a[i]);
        b[i]=b[i-1]+a[i];
    }
    for(int i=N;i>=1;i--)
        c[i]=c[i+1]+a[i];
    for(int i=1;i<N;i++)
        for(int j=i+1;j<=N;j++)
            if(b[i]==c[j])
                ans=max(ans,b[i]);
    if(ans==-10) ans=0;
    printf("%d\n",ans);
}

然后后来。。。搞了一个挺麻烦的操作。。。把前缀和后缀数组搞成了一个 标记了一下属性
然后就是 记得一定要开long long 10^9加起来就爆了 QAQ
如果是黑箱测试 我这场考试就凉凉了

//考场实现
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 200005
int N,ans;
int a[MAXN];
struct node{
    long long v;
    int p,t;
}b[2*MAXN];
bool cmp(node x,node y)
{
    return x.v>y.v;
}
int main()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&a[i]);
        b[i].v=b[i-1].v+a[i];
        b[i].p=i;
        b[i].t=1;
    }
    for(int i=N;i>=1;i--)
        b[i+N].v=b[i+N+1].v+a[i],b[i+N].p=i,b[i+N].t=2;
    sort(b+1,b+2*N+2,cmp);
    for(int i=1;i<=2*N;i++)
        if(b[i].v==b[i+1].v&&((b[i].p<b[i+1].p&&b[i].t==1&&b[i+1].t==2)||(b[i].p>b[i+1].p&&b[i].t==2&&b[i+1].t==1)))
        {
            printf("%lld\n",b[i].v);
            return 0;
        }
    printf("0\n");
    return 0;
}
//long long不开毁前程

然后这个差不多的思想 有一个特别简单的实现 (赛后shared)

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,t;
long long a[200001],ans;
int main() {
    scanf("%d",&n);
    for(int i=1; i<=n; i++)scanf("%I64d",&a[i]),a[i]+=a[i-1];
    for(int i=1; i<=n; i++) {
        t=lower_bound(a+1,a+1+n,a[n]-a[i])-a;
        if(a[t]==a[n]-a[i]&&i<=t)ans=a[i];
    }
    printf("%lld",ans);
    return 0;
}

赛后看了这个实现 我。。。QAQ
最关键的就是在第二个for循环里面
根本不需要另外用一个后缀数组 a[n]-a[i]==b[i] 然后low_bound()求出第一个大于等于这个数的位置(对于这道题而言,upper _bound也可以)
如果在前缀里面 能够找到与之相等的数 并且后缀的位置大于前缀 则合法 更新ans

这道题还有一个思路就是用滑动窗口的思想 设置左右边界 由于sum具有单调性(所有的元素都是正数),则可以通过调整边界来调整sum的关系

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define MAXN 200000
int n,d[MAXN+5],l,r;
long long sum1,sum2,ans;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&d[i]);
    int l=0,r=n+1;
    while(l<r)
    {
        if(sum1==sum2)ans=max(ans,sum1),l++,r--,sum1+=d[l],sum2+=d[r];
        if(sum1<sum2)l++,sum1+=d[l];
        if(sum1>sum2)r--,sum2+=d[r];
    }
    printf("%lld",ans);
}

C

题目简述
2个长度为n的字符串 可以进行如下交换
1.a[i]与a[n-i+1]
2.a[i]与b[i]
3.b[i]与b[n-i+1]
可以将两个字符串 改变其中的字符 问至少要改变多少个字符才能使这2个字符串完全相等

分析
有点模拟的意味 但不是纯暴力 进行分类讨论即可
注意a[i]与b[j]和b[i]与a[j]直接交换是非法的 需进行2次交换 交换后顺序与直接交换不同

然后 坑点来了
敲黑板
题目上说只能修改a的字符
如果 if(a[i]==a[j]) swap(a[i],b[i]),swap(a[i],a[j]);
这个操作就会把a[j]和b[i]换了
当后面访问到a[j]时如果你发现无法与b[j]匹配,后面的程序就会修改a[j]的值
但实际上题目要求是先修改值,再交换位置
所以修改a[j]就相当于修改b[j],就违反了规则

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 100005
char a[MAXN],b[MAXN];
int N,ans;
int main()
{
    scanf("%d",&N);
    scanf("%s\n%s",a+1,b+1);
    for(int i=1;i<=N;i++)
        if(a[i]!=b[i])
        {
            int j=N-i+1;
            if(j>i)
            {
                if(a[i]==b[j]) swap(b[i],b[j]);
                else if(b[i]==a[j]) swap(a[i],a[j]);
                //else if(a[i]==a[j]) swap(a[i],b[i]),swap(a[i],a[j]);
                else if(a[j]==b[j]) swap(a[i],a[j]),swap(b[i],b[j]);
                else if(b[i]==b[j]) swap(a[i],b[i]),swap(b[i],b[j]);
                else ans++;
            }
            else ans++;
        }
    printf("%d\n",ans);
}

还有一种做法,就是满足这样的一种性质:可以互相交换的4个地方的字符一定4个一样或者有2种字符,每种2个。
其实也比较好做,但是我没做

D

题意简述

Kenkoooo去旅行,有n个城市,m条火车线路。每条线路有两种权值:日元和Snuke,任选一种即可。开始时,他有10 ^15日元,在任意一个城市,他都可以将日元换成Snuke,但必须全部换完,且只能换一次,并且城市i会在i年后不能兑换货币。他每年都会去旅行,对于每一年求最大能够省下的Snuke。

思路分析

很朴素的一个想法当然就是枚举在哪个车站换钱,但是肯定会超时。
怎么办呢?我们折中一下,因为不管在哪个车站换,在那个车站以前,用的是日元,在那个车站以后,用的是Snuke。
所以我们分别以起点和终点为起点,这话怎么这么绕,分别以日元和Snuke为边权,找最短路,然后枚举断点。

但是这道题的有(恶)趣(心)之处呢,就在于它有很多很多年,每一年的可供换钱的车站还有变化。年数要是太大,时间也不好受。

于是我们可以这样进行一个优化。
每一年会关掉i号窗口,如果我们把时间轴倒着看,就是每一年会多开第i号窗口。而对于第i年来说,由于变化的只是第i号窗口有没有开,所以对于上一年(实际是下一年,这里把时间轴倒着看的)求出的那个ans值,表示的是在i+1~n号窗口换的最优值,那我们只需将上一年的ans值,与这一年在第i号窗口换钱的值进行比较,取最小即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 100005
#define LL long long
#define INF 1e18
int N,M,S,T,u,v;
LL a,b,f=1e15,ans[MAXN];
LL Min(LL x,LL y)
{
    if(x>y) return y;
        return x;
}
struct graph{
    int cnt,head[MAXN],vis[MAXN];
    LL dis[MAXN];
    struct Edge
    {
        int next,to;LL dis;
    }edge[2*MAXN];
    queue<int> q;

    void addedge(int from,int to,LL dis)
    {
        edge[++cnt].next=head[from];
        edge[cnt].to=to;
        edge[cnt].dis=dis;
        head[from]=cnt;
    }

    void spfa(int s)
    {
        for(int i=1;i<=N;i++)
            dis[i]=INF;
        memset(vis,0,sizeof(vis));
        q.push(s); vis[s]=1; dis[s]=0;
        while(!q.empty())
        {
            int x=q.front(); q.pop();
            vis[x]=0;
            for(int i=head[x];i;i=edge[i].next)
            {
                int to=edge[i].to;
                if(dis[to]>dis[x]+edge[i].dis)
                {
                    dis[to]=dis[x]+edge[i].dis;
                    if(!vis[to]) q.push(to),vis[to]=1;
                }
            }
        }
    }
}g1,g2;
int main()
{
    scanf("%d %d %d %d",&N,&M,&S,&T);
    for(int i=1;i<=M;i++)
    {
        scanf("%d %d %lld %lld",&u,&v,&a,&b);
        g1.addedge(u,v,a); g1.addedge(v,u,a);
        g2.addedge(v,u,b); g2.addedge(u,v,b);
    }
    g1.spfa(S); g2.spfa(T);
    ans[N+1]=INF;
    for(int i=N;i>=1;i--) ans[i]=Min(ans[i+1],g1.dis[i]+g2.dis[i]);
    for(int i=1;i<=N;i++)
        printf("%lld\n",f-ans[i]);
    return 0;
}

P.S:终于知道了咋在结构体里面搞函数,跟在外面其实是一样的写法。

posted @ 2018-08-22 09:10  Starlight_Glimmer  阅读(9)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end