1. 程式人生 > >單調佇列及其deque寫法 HDU 3415+Poj 4002 (日期處理) + 合併果子

單調佇列及其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

已經總結過一次了,這裡補一個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;
}