acwing 298. 围栏 (单调队列 & dp)

传送门

题意: 有N块木板从左到右排成一行,有M个工匠对这些木板进行粉刷,每块木板至多被粉刷一次。
第 i 个木匠要么不粉刷,要么粉刷包含木板 Si 的,长度不超过 Li 的连续的一段木板,每粉刷一块可以得到 Pi 的报酬。
不同工匠的Si不同。请问如何安排能使工匠们获得的总报酬最多。

输入格式
第一行包含两个整数N和M。
接下来M行,每行包含三个整数Li,Pi,Si。

输出格式
输出一个整数,表示结果。

数据范围
1≤N≤16000,
1≤M≤100,
1≤Pi≤10000
输入样例:
8 4
3 2 2
3 2 3
3 3 5
1 1 7
输出样例:
17

思路:

  • 这种多变量参数的求max肯定是需要d运用p的:先找到状态表示:f[i][j];
    然后就是分析状态转移:(不刷) f[i - 1][j],(刚好刷Si 到j这一段) f[i][j - 1],(刷j - k块,即枚举Si以前符合条件的起点) f[i - 1][k] + (j - k) * Pi。
  • 然后我们就可以利用一个单调队列将问题转换,在解决问题的过程中比当前值小的选择当然要忽略,这样的话用一个滑动的单调队列来存起点是最好的方法了。
  • 细节可见代码的实现,还可以再参考其他题解

代码实现:

#include<bits/stdc++.h>
#define endl '\n'
#define null NULL
#define ll long long
#define int long long
#define pii pair<int, int>
#define lowbit(x) (x &(-x))
#define ls(x) x<<1
#define rs(x) (x<<1+1)
#define me(ar) memset(ar, 0, sizeof ar)
#define mem(ar,num) memset(ar, num, sizeof ar)
#define rp(i, n) for(int i = 0, i < n; i ++)
#define rep(i, a, n) for(int i = a; i <= n; i ++)
#define pre(i, n, a) for(int i = n; i >= a; i --)
#define IOS ios::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int way[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
using namespace std;
const int  inf = 0x7fffffff;
const double PI = acos(-1.0);
const double eps = 1e-6;
const ll   mod = 1e9 + 7;
const int  N = 16010, M = 110;

int n, m;
int q[N];
int f[M][N]; //f[i][j]表示前i个工匠粉刷前快木板

struct node{
    int l, p, s;
    bool operator< (const node &t) const{
        return s < t.s;
    }
}car[M];

signed main()
{
    IOS;
    
    cin >> n >> m;
    for(int i = 1; i <= m; i ++)
        cin >> car[i].l >> car[i].p >> car[i].s;
    sort(car + 1, car + m + 1); //按照Si来排序
    
    for(int i = 1; i <= m; i ++){
        int hh = 0, tt = -1;
        for(int j = 0; j <= n; j ++){
            f[i][j] = f[i - 1][j];  //如果第i个工匠不粉刷
            if(j) f[i][j] = max(f[i][j], f[i][j - 1]); //如果第i个工匠刚好粉刷到木板Si
            
            int l = car[i].l, p = car[i].p, s = car[i].s;
            if(hh <= tt && q[hh] < j - l) hh ++; //如果左侧滑出窗口,因为最小可刷到j - l
            if(j >= s && hh <= tt){ //可以刷到Si,开始枚举刷木板的起点k
                int k = q[hh];
                f[i][j] = max(f[i][j], f[i - 1][k] + (j - k) * p); //粉刷了k块木板
            }
            if(j < s){ //如果刷不到Si
                //队内小于等于当前值的元素需要删掉来维护单调队列q
                while(hh <= tt && f[i - 1][q[tt]] - q[tt] * p <= f[i - 1][j] - j * p) tt --;
                q[++ tt] = j; //再把当前值加入队列
            }
        }
        cout << f[m][n] << endl; //最后的max就是f[m][n]
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Satur9/article/details/107422050