【模板】
//名称:单调队列(双端队列)
//用途:用于计算整个数组中固定区间的最值
//复杂度:O(n)
//方法:把数组元素填充到num[]中。调用get(),结果返回在dp[]中
const int maxn = 1000000;
struct node
{
int v;
int index;
}q[maxn+5];
int num[maxn+5];
int dp[maxn+5];
//num[]为原数组,n为数组大小,k为窗口大小,dp[]为结果数组
//min为true表示取区间最小值,否则取最大值
void get(int *num, int n, int k, int *dp, bool min)
{
int i;
int s = 0;
int t = -1;
for (i=1; i<=n; i++)
{
while(s<=t && (min?(q[t].v>=num[i]):(q[t].v<=num[i]))) t--;
t++;
q[t].v = num[i];
q[t].index = i;
while(s<=t && q[s].index<=i-k) s++;
dp[i] = q[s].v;
}
}
【题解】
HDU3706:http://acm.hdu.edu.cn/showproblem.php?pid=3706
POJ2823:http://poj.org/problem?id=2823
这两道题都是单调队列的简单应用。
HDU1003:http://acm.hdu.edu.cn/showproblem.php?pid=1003
最大和子段,可以用动态规划解决。
同时也可以用单调队列解决。
求出s[ ]数组表示前n项的和。
然后遍历s[ ],记录遇到的最小值min,那么以当前结点结束的最大和子段就是s[i]-min。
HDU3415:http://acm.hdu.edu.cn/showproblem.php?pid=3415上一题的扩展,要求子段长度不超过k。
使用上一题同样的方法,不同的是用单调队列维护当前区间大小为k。
注意数组是循环的。
#include <iostream>
using namespace std;
const int maxn = 100000;
struct node
{
int v;
int index;
}q[maxn*2+5];
int a[maxn*2+5];
int main()
{
int n, k;
int i;
int T;
int s, t;
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &n, &k);
a[0] = 0;
for (i=1; i<=n; i++) scanf("%d", &a[i]);
for (i=1; i<=n; i++) a[i] += a[i-1];
for (i=n+1; i<=n+k; i++) a[i] = a[n] + a[i-n];
s = 0;
t = 0;
q[0].v = 0;
q[0].index = 0;
int ms=INT_MAX, mt=INT_MAX, max = INT_MIN;
for (i=1; i<=n+k; i++)
{
if (a[i]-q[s].v>max)
{
max = a[i]-q[s].v;
ms = q[s].index+1;
mt = i;
}
while(s<=t && a[i]<q[t].v) t--;
t++;
q[t].v = a[i];
q[t].index = i;
while(s<=t && q[s].index<=i-k) s++;
}
printf("%d %d %d\n", max, (ms-1)%n+1, (mt-1)%n+1);
}
return 0;
}
POJ3017:http://poj.org/problem?id=3017
单调队列+BST。
b[ ]表示当前结点向前所能包括的最大窗口的起始位置。
单调队列就用于维护当前的窗口值。
而BST用于维护当前窗口中各元素的dp[ ]值。
当把元素从队列中删除时需要把BST中相应的值也删除。
#include <iostream>
#include <set>
using namespace std;
#define min(a,b) ((a)<=(b)?(a):(b))
const int maxn = 100000;
int b[maxn+5];
__int64 a[maxn+5];
__int64 s[maxn+5], dp[maxn+5];
struct node
{
__int64 v;
int index;
}q[maxn+5];
set<__int64>se;
int main()
{
int n;
__int64 m;
int i, j;
int begin, end;
scanf("%d %I64d", &n, &m);
s[0] = a[0] = 0;
for (i=1; i<=n; i++)
{
scanf("%I64d", &a[i]);
s[i] = s[i-1] + a[i];
}
for (i=1; i<=n; i++)
{
if (a[i]>m) break;
}
if (i<=n)
{
printf("-1\n");
}
else
{
b[1] = 1;
for (i=2,j=1; i<=n; i++)
{
while(s[i]-s[j-1]>m) j++;
b[i] = j;
}
dp[1] = s[1];
q[0].v = a[1];
q[0].index = 1;
begin = end = 0;
se.clear();
for (i=2; i<=n; i++)
{
while(begin<=end && q[end].v<=a[i])
{
if (begin<end) se.erase(dp[q[end-1].index]+q[end].v);
end--;
}
end++;
q[end].v = a[i];
q[end].index = i;
while(begin<=end && q[begin].index<b[i])
{
if (begin<end) se.erase(dp[q[begin].index]+q[begin+1].v);
begin++;
}
if (begin<end) se.insert(dp[q[end-1].index]+q[end].v);
dp[i] = dp[b[i]-1] + q[begin].v;
if (!se.empty()) dp[i] = min(dp[i], *se.begin());
}
printf("%I64d\n", dp[n]);
}
return 0;
}
POJ2373:http://poj.org/problem?id=2373
DP+单调队列。
其实这道题可以不用单调队列做的。可以使用查询最值的方法去做。
单调队列的重要特点就是函数单调。
可以用于优化dp。
这道题的转移方程为:dp[i] = min(dp[j])+1 (2*a<=i-j<=2*b且 j 和 i 均可以作为sprinkler的开始和结束点)
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1000;
const int maxl = 1000000;
const int inf = 10000000;
int q[maxl+5];
int dp[maxl+5];
bool can[maxl+5];
struct node
{
int s;
int e;
}seg[maxn+5];
bool cmp(const node &a, const node &b)
{
if(a.s!=b.s) return a.s<b.s;
else return a.e<b.e;
}
int main()
{
int n, l;
int a, b;
int i, j;
int s, t;
bool flag;
scanf("%d %d", &n, &l);
scanf("%d %d", &a, &b);
flag = true;
for (i=0; i<n; i++)
{
scanf("%d %d", &seg[i].s, &seg[i].e);
if (seg[i].e-seg[i].s>2*b) flag = false;
}
if (!flag) printf("-1\n");
else
{
sort(seg, seg+n, cmp);
for (i=0; i<=l; i++) can[i] = 1;
for (i=0; i<n; i++)
{
for (j=seg[i].s+1; j<seg[i].e; j++)
can[j] = 0;
}
dp[0] = 0;
s = 0;
t = -1;
for (i=1; i<=l; i++)
{
dp[i] = inf;
j = i-2*a;
if (j<0) continue;
while(s<=t && dp[j]<=dp[q[t]]) t--;
if (can[j])
{
t++;
q[t] = j;
}
if (!can[i] || (i&1)) continue;
while(s<=t && q[s]<i-2*b) s++;
if (s<=t) dp[i] = dp[q[s]]+1;
}
if (dp[l]>=inf) printf("-1\n");
else printf("%d\n", dp[l]);
}
return 0;
}