单调队列及其deque写法 HDU 3415+Poj 4002 (日期处理) + 合并果子

尝试用deque写一下单调队列,发现速度还是可以接受的,STL依赖症越来越严重了。。。。

HDU 3415 Max Sum of Max-K-sub-sequence

题意:给出一个有N个数字(-1000..1000,N<=10^5)的环状序列,让你求一个和最大的连续子序列。这个连续子序列的长度小于等于K。

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define upmax(a,b) ((a)=(a)>(b)?(a):(b))

const int N=100005;
int data[N];
int sum[2*N],n,k;
deque <int> Q;

void In (int i)
{//以i为开头的一个区间需要比较和保存i-1
    while (!Q.empty() && sum[Q.back()]>=sum[i-1])   //>也可
        Q.pop_back();
    Q.push_back(i-1);  //记录该元素的前一个下标
}

void Out (int i)
{
    while (!Q.empty() && i-Q.front()>k )
        Q.pop_front();
}

int main ()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&n,&k);
        int i;
        if (Q.empty()==false) Q.clear();
        memset(sum,0,sizeof(sum));
        for (i=1;i<=n;i++)
        {
            scanf("%d",&data[i]);
            sum[i]=sum[i-1]+data[i];
        }
        for (;i<=2*n;i++)
            sum[i]=sum[i-1]+data[i-n];

        int s=0,e=0,ans=-9999999;
        for (i=1;i<=n+k;i++)
        {
            In(i);
            Out(i);
            if (ans<sum[i]-sum[Q.front()])
            {
                upmax(ans,sum[i]-sum[Q.front()]);
                s=Q.front()+1;
                e=i;
            }
        }
        if (s>n)
            s=s-n;
        if (e>n)
            e=e-n;
        printf("%d %d %d\n",ans,s,e);
    }
    return 0;
}

/*
6
6 6
0 1 2 3 4 5
6 6
1 2 3 -4 5 6
6 6
2 0 -4 5 6 7
8 8
0 1 2 2 1 0 0 0
51 34
-537 -622 -109 302 420 -955 987 570 138 -7 866 -291 -919 -746 -501 -814 -182 121 174 -919 181 -759 -879 514 222 -392 -452 -581 -150 -278 880 911 202 -401 667 123 109 -296 -961 -761 450 716 355 604 -449 -50 -572 644 -364 339 -475
5 5
-1 -1 -1 -1 0

Out
15 2 6
17 5 3
20 4 1
6 2 5
2724 31 11
0 5 5

*/

#include <cstdio>
#include <cstring>
#define upmax(a,b) ((a)=(a)>(b)?(a):(b))

const int N=100005;
int data[N],Q[2*N];
int sum[2*N],n,k,head,tail;

void In (int i)
{//以i为开头的一个区间需要比较和保存i-1
    while (head<=tail && sum[Q[tail]]>=sum[i-1])  //>也可
        tail--;
    Q[++tail]=i-1;  //记录该元素的前一个下标
}

void Out (int i)
{
    while (head<=tail && i-Q[head]>k)
        head++;
}

int main ()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&n,&k);
        int i;
        memset(sum,0,sizeof(sum));
        for (i=1;i<=n;i++)
        {
            scanf("%d",&data[i]);
            sum[i]=sum[i-1]+data[i];
        }
        for (;i<=2*n;i++)
            sum[i]=sum[i-1]+data[i-n];

        head=0;tail=-1;
        int s=0,e=0,ans=-9999999;
        for (i=1;i<=n+k;i++)
        {
            In(i);
            Out(i);
            if (ans<sum[i]-sum[Q[head]])
            {
                upmax(ans,sum[i]-sum[Q[head]]);
                s=Q[head]+1;
                e=i;
            }
        }
        if (s>n)
            s=s-n;
        if (e>n)
            e=e-n;
        printf("%d %d %d\n",ans,s,e);
    }
    return 0;
}

Poj 4002 & Hdu 4122 Alice's mooncake shop

题意:一个生产月饼的工厂,给出一个数m,该工厂只在前m小时(也就是[1,m])生产月饼。给出一系列订单,订单给出在第i小时买家要拿走R数量的月饼(1<=i<=m)。生产一个月饼的单价每天不同。工厂有一个冰箱,可以将提前生产的月饼放在冰箱里(工厂也可以在订单到来的那个时刻生产,生产月饼可以瞬间完成),但是放在冰箱里的时间不能超过T。每个月饼在冰箱里保存一天需要额外的费用S。制定工厂的生产计划使得总花费最少?冰箱无限大,每小时生产月饼数量没有限制。

思路:维护一个单调队列,把时间都转化成从2000年第一个月第一天的0时开始

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;


const int N=100005;

int n,m,S,T,p[N]; //p数组表示该时刻的成本
int hour[2505];   //每项请求所在的时间,以小时表示
int R[2505];

char map[12][4]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

int M[2][13]=
{
	{0,31,28,31,30,31,30,31,31,30,31,30,31},
	{0,31,29,31,30,31,30,31,31,30,31,30,31}
};

int Y[]={365,366};

int getMonth (char s[])
{
	int i;
	for (i=0;i<12;i++)
		if (strcmp(map[i],s) == 0)
			break;
	return i+1;
}

int Is_Year (int x)
{//闰年返回1,否则返回0
	return x%400==0 || x%100 && x%4==0;
}

void Input ()
{
	char str[15];
	int day,year,H,i,j;
	for (i=1;i<=n;i++)
	{
		scanf("%s %d %d %d %d",str,&day,&year,&H,&R[i]);
		int month=getMonth(str);
		int ans=0;
		for (j=2000;j<year;j++)
			ans+=Y[Is_Year(j)];
		int t=Is_Year(year);
		for (int j=1;j<month;j++)
			ans+=M[t][j];
		ans+=day-1;
		hour[i]=ans*24+H+1;
	}
	scanf("%d%d",&T,&S);
	for (i=1;i<=m;i++)
		scanf("%d",&p[i]);
}

struct Node
{
	int x,y;//x制作时的单价,y制作时的时刻
}tmp;
deque<Node> Q;

int main ()
{
	while (scanf("%d%d",&n,&m),n||m)
	{
		Input ();
		Q.clear();
		__int64 ans=0;
		int head=0,tail=-1;
		int id=1;
		for (int i=1;i<=m;i++)
		{//维护单增队列,队首元素一定单价最小
		    while (!Q.empty() && Q.back().x+(i-Q.back().y)*S >= p[i])
                Q.pop_back();
            tmp.x=p[i];
            tmp.y=i;
            Q.push_back(tmp);
			while (id<=n && hour[id]==i)
			{
			    while (!Q.empty() && Q.front().y+T<i) //超出保存期限,删除队头元素
                    Q.pop_front();
				ans+=((__int64)(Q.front().x+(i-Q.front().y)*S))*R[id];
				id++;
			}
		}
		printf("%I64d\n",ans);
	}
	return 0;
}
/*
2 12
Jan 1 2000 9 10
Jan 1 2000 10 10
5 2
20
20
20
10
10
8
7
9
5
10
15
13
0 0

OUT
160
*/

#include <cstdio>
#include <cstring>
#include <cstdlib>

const int N=100005;

int n,m,S,T,p[N]; //p数组表示该时刻的成本
int hour[2505];   //每项请求所在的时间,以小时表示
int R[2505];

char map[12][4]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

int M[2][13]=
{
	{0,31,28,31,30,31,30,31,31,30,31,30,31},
	{0,31,29,31,30,31,30,31,31,30,31,30,31}
};

int Y[]={365,366};

int getMonth (char s[])
{
	int i;
	for (i=0;i<12;i++)
		if (strcmp(map[i],s) == 0)
			break;
	return i+1;
}

int Is_Year (int x)
{//闰年返回1,否则返回0
	return x%400==0 || x%100 && x%4==0;
}

void Input ()
{
	char str[15];
	int day,year,H,i,j;
	for (i=1;i<=n;i++)
	{
		scanf("%s %d %d %d %d",str,&day,&year,&H,&R[i]);
		int month=getMonth(str);
		int ans=0;
		for (j=2000;j<year;j++)
			ans+=Y[Is_Year(j)];
		int t=Is_Year(year);
		for (int j=1;j<month;j++)
			ans+=M[t][j];
		ans+=day-1;
		hour[i]=ans*24+H+1;
	}
	scanf("%d%d",&T,&S);
	for (i=1;i<=m;i++)
		scanf("%d",&p[i]);
}

struct Node
{
	int x,y;//x制作时的单价,y制作时的时刻
}Q[N];

int main ()
{
	while (scanf("%d%d",&n,&m),n||m)
	{
		Input ();
		__int64 ans=0;
		int head=0,tail=-1;
		int id=1;
		for (int i=1;i<=m;i++)
		{//维护单增队列,队首元素一定单价最小
			while (head<=tail && Q[tail].x+(i-Q[tail].y)*S >= p[i])
				tail--;
			tail++;
			Q[tail].x=p[i];
			Q[tail].y=i;
			while (id<=n && hour[id]==i)
			{
				while (head<tail && Q[head].y+T<i)  //超出保存期限,删除队头元素
					head++;
				ans+=((__int64)(Q[head].x+(i-Q[head].y)*S))*R[id];
				id++;
			}
		}
		printf("%I64d\n",ans);
	}
	return 0;
}


Hdu 4193 Non-negative Partial Sums

在这里Hdu4193 Non-negative Partial Sums (单调队列) - whyorwhnt的专栏 - 博客频道 - CSDN.NET

已经总结过一次了,这里补一个deque写法

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int N=1000005;

int data[N],sum[2*N];
int n,ans;
deque<int> Q;

void In (int i)
{//单调递增队列,保证队首最小
    while (!Q.empty() && sum[Q.back()]>=sum[i])
        Q.pop_back();
    Q.push_back(i);
}

void Out (int i)
{
    if (Q.front()<=i-n) Q.pop_front();
    if (sum[Q.front()]-sum[i-n]>=0) //区间内最小的元素>=区间第一个元素
        ans++;
}

int main ()
{
	while (scanf("%d",&n),n)
	{
	    Q.clear();
		ans=0;
		memset(sum,0,sizeof(sum));
		int i;
		for (i=1;i<=n;i++)
		{
			scanf("%d",&data[i]);
			sum[i]+=sum[i-1]+data[i];
		}
		for (;i<2*n;i++)
			sum[i]+=sum[i-1]+data[i-n];
		for (i=1;i<n;i++)
			In (i);
		for (i=n;i<2*n;i++)
		{
			In(i);
			Out(i);
		}
		printf("%d\n",ans);
	}
	return 0;
}

合并果子

题目链接:https://vijos.org/p/1097

以下分析摘自:http://www.cnblogs.com/neverforget/archive/2011/10/13/ll.html

这个题目非常的经典,方法也很多,可以采用快排或者堆,其思想都是选取当前最小的两个堆进行合并。复杂度均为O(nlogn),如果用有序队列维护,时间复杂度为O(n)。

每次选取进行合并的两堆,不是最先给定的堆,就是合并最初堆若干次后得到的新堆,所以需要维护两个单调递增队列,一个队列存最初给定的堆的值(1),一个存合并后得到的新值(2)。

每次选择时有三种状态:

1.选取队一的队首两个

2.选取队2的的队首两个

3.选取二者队首各一个

只需对每个队列的指针做相应的更改。

特别注意初始化。

这道题很好的运用了题目中决策的单调性,对初始对经行排序,保证了其单调性。而对于新产生的堆来说,一旦有新元素加入其中,则新元素一定大于原有元素。(很显然,由于队列1的单调性)。

也就是说,队列的单调性是自然而然的。是不需要维护的。要善于观察分析,才能发现。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=10005;

int data[N],Q[N];

int main ()
{
    int n;
    while (~scanf("%d",&n))
    {
        int i,front,head,tail;
        memset(Q,0x3f3f3f3f,sizeof(Q));
        memset(data,0x3f3f3f3f,sizeof(data));
        for (i=0;i<n;i++)
            scanf("%d",&data[i]);
        if (n==1)
        {
            printf("%d\n",data[0]);
            continue;
        }
        sort(data,data+n);
        head=tail=front=0;
        int sum=0;
        for (i=1;i<=n-1;i++)
        {
            int tmp=0;
            if (data[front]<Q[head])
            {
                tmp+=data[front];
                front++;
            }
            else
            {
                tmp+=Q[head];
                head++;
            }
            if (data[front]<Q[head])
            {
                tmp+=data[front];
                front++;
            }
            else
            {
                tmp+=Q[head];
                head++;
            }
            sum+=tmp;
            Q[tail]=tmp;
            tail++;
        }
        printf("%d\n",sum);
    }
    return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值