【题解】洛谷9月月赛加时赛 —— Never·island

  有趣有趣~ヾ(✿゚▽゚)ノ真的很有意思的一道dp题!感觉可以提供很多非常有意思的思路~

  现场打的时候考虑了很久,但并没有做出来,主要还是卡在了两个地方:1.考虑到按照端点来进行dp,但没有办法将两个端点绑定(即选择钥匙的决策要同时作用在出发与回来的节点上);2.有一些贡献是需要前后两个队伍共同的决策才能够实现的,也并不会处理……最后的题解完美解决了这两个问题。

  我们可以考虑将两个相邻端点之间能否关门作为贡献加在两个端点所代表的队伍的节点上(点权)。如果左边是出发,右边也是出发,那么将这段的贡献加在左边的节点上;如果左边是回程,右边也是回程,那么将这段的贡献加在右边的节点上。如果左边是回程,右边是出发,则中间这一段的贡献一定会被加入答案中。如果左边是出发,右边是回程,那么中间这段的贡献需要两边均给了钥匙才能获得,我们可以在它们两者之间连一条边,边权为中间的贡献。

  这张图上所有的边我们可以发现构成了一条链(只有一条入边&一条出边且不构成环)。然后我们就可以愉快地dp辣!

  启示是:可以将选/不选的问题抽象成为点&点权,两两之间的关系用边来描述。之后,再考虑序列/树上等等的dp。加油呀!

//代码kuai的题解嘻嘻嘻
#include <bits/stdc++.h>
using namespace std;
#define maxn 2005
#define int long long
int n, m, tot, res, last[maxn];
int id[maxn * 2], f[maxn][maxn][2], val[maxn];
int w[maxn], vis[maxn], nxt[maxn], rec[maxn];
map <int, int> Map;

int read()
{
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

void dfs(int u)
{
    vis[rec[++ tot] = u] = 1;
    if(nxt[u]) dfs(nxt[u]);
}

signed main()
{
    n = read(), m = read();
    for(int i = 1; i <= n; i ++)
    {
        int l = read(), r = read();
        Map[l] = i * 2, Map[r] = i * 2 + 1;
        id[i] = l, id[i + n] = r;
    }
    sort(id + 1, id + 1 + n + n);
    for(int i = 1; i < n * 2; i ++)
    {
        int l = id[i], r = id[i + 1];
        int ld = Map[l], rd = Map[r];
        int a = ld & 1, b = rd & 1, len = r - l;
        if(a && !b) res += len;
        else if(a && b) val[rd >> 1] += len;
        else if(!a && !b) val[ld >> 1] += len;
        else if(!a && b)
        {
            if((ld >> 1) == (rd >> 1)) val[ld >> 1] += len;
            else last[rd >> 1] = ld >> 1, w[rd >> 1] = len;
        }
    }
    for(int i = 1; i <= n + n; i ++)
    {
        int d = Map[id[i]];
        if(!(d & 1) && !vis[d >> 1]) dfs(d >> 1);
    }
    memset(f, 0xbf, sizeof(f)); 
    f[n + 1][0][0] = f[n][0][0] = 0;
    for(int i = n; i > 0; i --, f[i][0][0] = 0)
        for(int j = (n - i + 1, m); j > 0; j --)
        {
            f[i][j][0] = max(f[i + 1][j][0], f[i + 1][j][1]);
            if(!last[rec[i]]) f[i][j][1] = max(f[i + 1][j - 1][0], f[i + 1][j - 1][1]) + val[rec[i]];
            else f[i][j][1] = max(f[i + 1][j - 1][0], f[i + 1][j - 1][1] + w[rec[i]]) + val[rec[i]];
        }
    printf("%lld
", id[n * 2] - id[1] - res - max(f[1][m][0], f[1][m][1]));
        
    return 0;
}
原文地址:https://www.cnblogs.com/twilight-sx/p/9715799.html