[NOIP2017模拟]刮刮卡

2017.10.20 T1 1997

样例数据
输入

5
4 6 2 8 4
1 5 7 9 2

输出

4

分析:之前做过重温世界杯,这道题我就用的类似的dp思想。其实加上了“A总和等于B总和”这个条件,题目是变简单了的:只需要算出所有卡收入的前缀和,最小前缀和的卡为答案。
为什么呢?因为前缀和最小的卡之后的卡的收入总和是等于这个最小值的相反数的(因为收入总和为B-A=0),所以从它的后一个开始取就相当于给所有前缀和加上了一个这个最小值的相反数,而且由于这是最小值,所以前面的那些负数都绝对被加成了正数,故可以取完所有刮刮卡。(其实就是最后拿那个如果从头拿会亏本最厉害的,这样相当于取到它之前都是赚,取完它正好不赚不亏)
举个例子:

B:   1  3  8  2  4  6  5
A:   2  3  3  8  1  5  7
收入:-1  0  5 -6  3  1 -2

从开头开始算前缀和:

 -1  -1  4  -2  1  2  0

我们发现地四张卡前缀和-2最小,所以就从它的后一个开始,这样算出的前缀和是这样的(从第五张卡开始):

 3  4  2  1  1  6  0

相当于给所有的前缀和加上了2,因为最小负数是-2,所以前面1—4张卡前缀和就没有负数了,所有卡都能被取到。

代码
我的dp思想(基本和重温世界杯一模一样)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int maxn=1000010;
struct node{
    int c,w;
}dp[maxn*2];
int n,sum,ans,res,bh;
int a[maxn],b[maxn];

int main()
{
    freopen("rock.in","r",stdin);
    freopen("rock.out","w",stdout);

    n=getint();
    for(int i=1;i<=n;++i)
    {
        b[i]=getint();
        sum+=b[i];
    }

    for(int i=1;i<=n;++i)
    {
        a[i]=getint();
        dp[i].c=b[i]-a[i];
        dp[i+n].c=dp[i].c;
        dp[i].w=b[i];
        dp[i+n].w=dp[i].w;
    }

    bh=0;
    for(int i=2;i<=2*n;++i)
    {
        if(dp[i].c+dp[i-1].c>=0&&dp[i-1].c>=0)
        {
            dp[i].c=dp[i].c+dp[i-1].c;
            dp[i].w=dp[i].w+dp[i-1].w;
            if(dp[i].w==sum)
            {
                ans=sum;
                res=bh;
                break;
            }
        }
        else if(dp[i].c+dp[i-1].c<0&&dp[i-1].c>=0)
        {
            if(ans<dp[i].w+dp[i-1].w)
            {
                res=bh;
                ans=dp[i].w+dp[i-1].w;
            }

            bh=i;
        }
        else if(dp[i-1].c<0)
        {
            if(ans<dp[i-1].w)
            {
                res=bh;
                ans=dp[i-1].w;
            }

            bh=i-1;
        }
    }

    cout<<res<<'\n';
    return 0;
}

简单的正解

#include <bits/stdc++.h>
using namespace std;

const int Max=1001000;
int n,ans,sum;
int a[Max],b[Max],c[Max],dis[Max];

int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(c<'0'||c>'9')&&(c!='-');c=getchar());
   if(c=='-') {c=getchar();f=-1;}
   for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

int main()
{
   freopen("rock.in","r",stdin);
   freopen("rock.out","w",stdout);

   n=get_int();
   for(int i=1;i<=n;i++) b[i]=get_int();
   for(int i=1;i<=n;i++) a[i]=get_int();
   for(int i=1;i<=n;i++) c[i]=b[i]-a[i];
   dis[1]=c[1];
   for(int i=2;i<=n;i++) dis[i]=dis[i-1]+c[i];
   int minn=1e+8,location;
   for(int i=1;i<=n;i++) if(minn>dis[i]) minn=dis[i];
   for(int i=1;i<=n;i++) if(minn==dis[i]) {location=i;break;}
   cout<<location<<endl;
   return 0;
}

本题结。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值