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