单调队列入门之烽火传递

有N个数(N<=100000) ,在连续M(M<=N)个数里至少要有一个数被选择. 求选出来数的最小总和。

输入
第一行两个整数 N,M 接下来N行 Wi(Wi<=100) 表示第i个数

输出
一个整数,最小总和

样例
输入
5 3
1
2
5
6
2
输出
4
提示
2+2=4

Sol:
定义f[i]为一定选择第i个数字,且保证前i个数字都满足题意的最小代价
则可以写出这样的方程f[i]:=min{f[j]}+a[i](i-m<=j<=i-1)
(因为要保证从i向左数的m个数字均要被覆盖)
于是对于样例
输入数字 1 2 5 6 3 4 1
f数组 1 2 5 7 5 9 6
则对于6来说, f[4]=min(1,2,5)+6=7
则对于3来说, f[5]=min(2,5,7)+3=5
则对于4来说, f[6]=min(5,7,5)+4=9
则对于1来说, f[4]=min(7,5,9)+1=6
由于覆盖第n个数字,我们可以选择f[n-m+1]...f[n]中的最小值
于是扫一遍就好了。
暴力程序如下:

 

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m, q[N], h = 1, t, num[N], a[N << 1], ans, f[N << 1];
int main() 
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) 
	     scanf("%d", &a[i]);
    memset(f, 0x3f, sizeof(f));
    for (int i = 1; i <= m; ++i) 
	     f[i] = a[i];
    for (int i = m + 1; i <= n; i++)
    {
    
        for (int j = i - m; j < i; j++) 
		//要从[i-m,i-1]这段中选个最小的f[j]+a[i]出来
		    f[i] = min(a[i] + f[j], f[i]);
	}
	ans = 0x3f3f3f3f;
    for (int i = n - m + 1; i <= n; i++) 
    //如果要覆盖第n个city,则从[n-m+1,n]中选择 
	    ans = min(ans, f[i]);
    printf("%d", ans);
    return 0;
}

  


发现此题关键是在某个大小固定的区间求一个极小值,于是可以单调队列之

 

#include <cstdio>
#include <bits/stdc++.h>
using namespace std;
#define ll long long
inline int read();
const int N = 2e5 + 1;
int n, m, h, t;
int a[N], q[N];
int f[N];
void work() 
{
    h = 0; 
	t=0;//注意h的初值为0 
	f[0]=0;
    for (int i = 1; i <= n; i++) 
    {
        while (q[h] < i - m && h <= t) 
		       h++;
        f[i] = f[q[h]] + a[i];  
       //单调上升队列,取[i-m,i-1]这个区间中值最小的f值
        while (f[i] <= f[q[t]] && h <= t) t--;
        q[++t] = i; //维护一个单调上升的f值组成的数列,注意不是a值
    }
}
int main() 
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) 	
	    scanf("%d", &a[i]);
    work();    
	int ans = 1e9;
    for (int i = n - m + 1; i <= n; i++) 
	    ans = min(ans, f[i]);
    printf("%d", ans);
    return 0;
}

  

绿色通道
高二数学《绿色通道》总共有N道题目要抄,编号1..N,抄第i 题要花ai分钟。小 Y 决定只用不超过t 分钟抄这个,因此必然有空着的题。每道题要么不写,要么抄完,不能写一半。下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。现在,小 Y想知道他在这t分钟内写哪些题,才能够尽量减轻马老师的怒火。由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。

输入
第一行为两个整数n,t 。 第二行为n个整数,依次为a1....an 。

0<N<=50000 0<ai<=3000 0<t<10^8

输出
输出一个整数,表示最长的空题段至少有多长。

样例
输入
17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5
输出
3

Sol:同上一题,二分枚举下答案即可

/*
设k为最长的空题数
问题转换为每k+1个数字至少要取一个数,
取出的数字之和小于等于t
二分枚举k即可
*/

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

const int N = 5e4 + 50;
const int inf = 0x3f3f3f3f;

int n, t, l, r, mid, ans;
int a[N], q[N], f[N];
int head, tail;

bool check(int mid) 
{
    memset(f, 0, sizeof(f));
    memset(q, 0, sizeof(q));
    head = tail = 1;
    for (int i = 1; i <= n; i++) 
	{
        while (head <= tail && q[head] < i - mid - 1) 
		     head++;
        f[i] = f[q[head]] + a[i];
        while (head <= tail && f[i] <= f[q[tail]]) 
		     tail--;
        q[++tail] = i;
    }
    int ans = inf;
    for (int i = n; i >= n - mid; i--) 
	{
        if (f[i] <= t)
            return 1;
    }
    return 0;
}

int main() {
    scanf("%d%d", &n, &t);
    for (int i = 1; i <= n; i++) 
	scanf("%d", &a[i]);

    int l = 1, r = n;
    while (l <= r) 
	{
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid - 1;
        else
            l = mid + 1;
    }
    printf("%d\n", r + 1);

    return 0;
}

  

posted @ 2020-09-07 16:46  我微笑不代表我快乐  阅读(291)  评论(0编辑  收藏  举报