2018ICPC北京网络赛 80 Days 单调队列(动态区间求最小值)

今天的比赛彻底自闭。。这道题跟原来做过的一道题很像,当时不会做,今天果然还是不会做,改了一下午都没改出来哇呜呜呜...果然我还是太菜了啊55555... 但是后来听说O(n^2)过了??exm???自闭了。。

那道类似的题是HDU4193,参考博客:https://blog.csdn.net/wiking__acm/article/details/7771134

HDU4193:循环序列,要往后补充n个数。下标从1开始,对于位置i(i>=n)的数,如果以i-n+1为起点,i为终点,长度为n的这一段区间内的前缀和均非负,其实就是该前缀和区间内的最小值大于0。但一开始存的是从1开始的前缀和,为了求从i-n+1开始的前缀和,每次减掉sum[i-n]就可以。

本题思路:把点和邻接的边看作一个整体,权值为a[i]-b[i];再用一个前缀和sum,放到单调队列里。这道题与上题最大的不同就是多了一个初值c,使长度为n的这一段区间内前缀和均要满足+c>=0。

附上AC代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
typedef pair<ll,ll>pp;
#define mkp make_pair
#define pb push_back
const double eps=1e-9;
const int INF=0x3f3f3f3f;

const int MAX=1e6+10;
ll n,c;
ll a[MAX];
ll b[MAX];
ll t[MAX*2];
ll sum[MAX*2];
deque<ll>q;//存下标

void ins(ll i)
{
    while(!q.empty()&&sum[q.back()]>=sum[i])//找最小值的下标
        q.pop_back();
    q.pb(i);
    while(!q.empty()&&i-q.front()>=n)//区间长度为n,注意有"="!
        q.pop_front();
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld",&n,&c);
        for(ll i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        for(ll i=1;i<=n;i++)
            scanf("%lld",&b[i]);
        while(!q.empty())//注意清空!
            q.pop_back();
        memset(sum,0,sizeof(sum));
        memset(t,0,sizeof(t));
        for(ll i=1;i<=n;i++)
        {
            t[i]=a[i]-b[i];
            t[i+n]=t[i];
        }
        for(ll i=1;i<=2*n;i++)
            sum[i]=sum[i-1]+t[i];

        ll i;
        for(i=1;i<n;i++)//不可少!
            ins(i);
        for(i=n;i<2*n;i++)
        {
            ins(i);
           /* q.front()存区间内前缀和最小值的下标;
            -sum[i-n]变成从i-n+1开始的区间内的前缀和;
            +c使满足题目条件 */
            if(c+(sum[q.front()]-sum[i-n])>=0)
                break;
        }
        if(i==2*n)
            printf("-1\n");
        else
            printf("%d\n",i-n+1);
    }
	return 0;
}

论补题的重要性。。。

猜你喜欢

转载自blog.csdn.net/Cc_Sonia/article/details/82817114