SSL 2401天地一抹红(斜率优化 DP)

Posted あおいSakura

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SSL 2401天地一抹红(斜率优化 DP)相关的知识,希望对你有一定的参考价值。

有一个 n*m 的网格,要从 (1,1) 走到 (n,m)。 然后你可以花费当前格的代价从 (i,j) 走到 (i+1,j),或者走到 (i,k) 其中 k>j。 当你走到 (i,k) 的时候,你可以选择 (i,j)~(i,k-1) 中地方含有宝石价值的最大值,然后就会给你贡献这个最大值乘 (i,k) 位置的法阵强度。 然后要你最大化最后走到 (n,m) 的贡献,如果无法非负就输出 -1。

天地一抹红

题目链接:SSL 2401

题目大意

有一个 n*m 的网格,要从 (1,1) 走到 (n,m)。
然后你可以花费当前格的代价从 (i,j) 走到 (i+1,j),或者走到 (i,k) 其中 k>j。
当你走到 (i,k) 的时候,你可以选择 (i,j)~(i,k-1) 中地方含有宝石价值的最大值,然后就会给你贡献这个最大值乘 (i,k) 位置的法阵强度。
然后要你最大化最后走到 (n,m) 的贡献,如果无法非负就输出 -1。

思路

那肯定是一行一行的走,那行之间的转移只有一种而且很简单。
就直接 \\(f_i,j=\\max(f_i,j,f_i-1,j-w_i-1,j)\\)

然后考虑列的,那一个 \\(O(m^2)\\) 每次的转移就是:
\\(f_i,j=\\max(f_i,j,\\max\\limits_k=1^j-1f_i,k-w_i,k+V_i,k,j-1a_i,j)\\)
\\(V_i,l,r=\\max\\limits_j=l^rv_i,j\\)

那注意到特别的就是 \\(a_i,j\\geqslant a_i,j+1\\)
那按这么来说,考虑转移点,\\(f_i,k-w_i,k\\) 越来越重要,\\(V_i,k,j-1\\) 越来越不重要,那就其实有一个决策的单调性,可以用斜率优化 DP。
考虑改一下,固定 \\(v_i,j\\),然后前面的 \\(f_i,k-w_i,k\\) 是最大值,这个好处是可以直接顺着枚举过来的时候直接维护,因为维护的事 \\(\\max\\limits_j=1^k f_i,j-w_i,j\\)
然后你队列维护单调递减的 \\(V\\),然后这些地方作为转移点,然后斜率优化就行了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N = 105;
const int M = 20005;
int n, m, F, sta[M];
ll w[N][M], h[N][M], a[N][M], f[N][M], g[M];

int re; char c;
int read() 
	re = 0; c = getchar();
	while (c < \'0\' || c > \'9\') c = getchar();
	while (c >= \'0\' && c <= \'9\') 
		re = (re << 3) + (re << 1) + c - \'0\';
		c = getchar();
	
	return re;


double clac(int i, int x, int y) 
	return 1.0 * (g[y] - g[x]) / (h[i][x] - h[i][y]);


int main() 
	freopen("red.in", "r", stdin);
	freopen("red.out", "w", stdout);
	
	n = read(); m = read(); F = read();
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) w[i][j] = read();
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) h[i][j] = read();
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) a[i][j] = read();
	
	memset(f, -0x3f, sizeof(f)); f[0][1] = F;
	for (int i = 1; i <= n; i++) 
		f[i][1] = f[i - 1][1] - w[i - 1][1]; g[1] = f[i][1] - w[i][1];
		int l = 1, r = 0; sta[++r] = 1;
		for (int j = 2; j <= m; j++) 
			while (l < r && clac(i, sta[l], sta[l + 1]) >= a[i][j]) l++;
			f[i][j] = max(f[i - 1][j] - w[i - 1][j], g[sta[l]] + h[i][sta[l]] * a[i][j]);
			g[j] = max(g[j - 1], f[i][j] - w[i][j]);
			while (l <= r && h[i][j] >= h[i][sta[r]]) r--;
			while (l < r && clac(i, sta[r - 1], j) >= clac(i, sta[r - 1], sta[r])) r--;
			sta[++r] = j;
		
	
	
	if (f[n][m] >= 0) printf("%lld", f[n][m]);
		else printf("-1");
	
	return 0;

SSL 2732_导弹拦截_dp+最小路径覆盖

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
敌国的导弹形成了立体打击,每个导弹可以抽象成一个三维空间中的点(x; y; z)。拦截系统发射的炮弹也很好地应对了这种情况,每一发炮弹也可以视为一个三维空间中的点。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达三维空间中任意的点,但是以后每一发炮弹到达点的坐标(x; y; z) 的三个坐标值都必须大于前一发炮弹的对应坐标值。
某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹飞来的坐标,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。注意: 所有导弹都是同时飞来的


 

思路

orz人生导师jpwang

前一问一个dp,f[i]表示拦截第i个可以拦截最多拦截到的导弹

f[i] = 1

f[i] = max(f[j]+1) (1 <= j < i) 且受题目限制

 

来考虑第二问

我们可以将i拆点为 i, i‘, 对于两个导弹i,j,若从i可以打j,那么我们就连一条(i,j‘)的边

这样就变成了一个二分图,因为我们要将全部点都达到,所以就是求一个二分图的最小路径覆盖 = 点数 - 最大匹配

跑网络流或其他算法都可以


#include <stdio.h>
#include <queue>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
#define max(x, y) (x) > (y) ? (x) : (y)
#define min(x, y) (x) < (y) ? (x) : (y)
#define INF 0x7f7f7f7f
#define fill(x, y) memset(x, y, sizeof(x))
struct edge
{
    int x, y, z;
}e[2000001];
struct arr
{
    int to, w, next;
}e1[2000001];
int n, f[10001], ls[20001], cur[20001], state[20001], S, E;
inline int read()
{
    int x=0,p=1;char ch=getchar();
    while (ch<0||ch>9){if (ch==-)p=-1;ch=getchar();}
    while (ch>=0&&ch<=9){x=(x<<1)+(x<<3)+ch-0;ch=getchar();}
    return x*p;
}
int cmp(edge a, edge b)
{
    return a.x < b.x ;
}
int maxE = 1;
int add(int x, int y, int w)
{
    e1[++maxE] = (arr) {y, w, ls[x]};
    ls[x] = maxE;
    e1[++maxE] = (arr) {x, 0, ls[y]};
    ls[y] = maxE;
}
int bfs(int S, int E)
{
    queue<int> t;
    fill(state, 0);
    t.push(S);
    state[S] = 1;
    while (!t.empty())
    {
        int now = t.front();
        t.pop();
        for (int i = ls[now]; i; i = e1[i].next)
        {
            if (e1[i].w > 0 && !state[e1[i].to])
            {
                state[e1[i].to] = state[now] + 1;
                t.push(e1[i].to);
                if (e1[i].to == E)
                    return true;
            }
        }
    }
    return false;
}
int find(int now, int mn)
{
    if (!mn || now == E)
        return mn;
    int ret = 0;
    for (int &i = cur[now]; i; i = e1[i].next)
        if (state[now] + 1 == state[e1[i].to] && e1[i].w > 0)
        {
            int d = find(e1[i].to, min(e1[i].w, mn - ret));
            e1[i].w -= d;
            e1[i^1].w += d;
            ret += d;
            if (ret == mn) break;
        }
    return ret;
}
int dinic()
{
    int ans = 0;
    while (bfs(S, E))
    {
        for (int i = S; i <= E; i++)
            cur[i] = ls[i];
        ans += find(S, INF);
    }
    return ans;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        {
            e[i].x = read();
            e[i].y = read();
            e[i].z = read();
        }
    sort(e + 1, e + n + 1, cmp);
    for (int i = 1; i <= n; i++)
    {
        f[i] = 1;
        for (int j = 1; j < i; j++)
        {
            if (e[j].x < e[i].x && e[j].y < e[i].y && e[j].z < e[i].z)
                f[i] = max(f[i], f[j] + 1);
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ans = max(ans, f[i]);
    }
    printf("%d\n", ans);
    int l = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j < i; j++)
            if (e[j].x < e[i].x && e[j].y < e[i].y && e[j].z < e[i].z)
                add(j, i + n, 1);
    S = 0;
    E = n * 2 + 1;
    for (int i = 1; i <= n; i++)
    {
        add(S, i, 1);
        add(i + n, E, 1);
    }
    printf("%d", n - dinic());
}

 

以上是关于SSL 2401天地一抹红(斜率优化 DP)的主要内容,如果未能解决你的问题,请参考以下文章

斜率优化DP

模板斜率优化dp

斜率优化DP和四边形不等式优化DP整理

『土地征用 Land Acquisition 斜率优化DP』

DP斜率优化

[DP优化方法之斜率DP]