BZOJ 1040 骑士 & POJ 2152 Fire (树形DP)

bzoj1040 一群骑士,每个人都有战斗力和一个讨厌的人,要求选出一系列骑士,被选中的人不能与讨厌的人同时选中,求战斗力之和的最大值。

显然厌恶关系构成的图是基环树森林,如果没有环那么就是简单的树形dp比如没有上司的舞会……对于每一棵树或者基环树,如果是树,就是树形dp;如果是基环树,就用dfs找环,在环中任意取一条边,把这条边在图中删去构成一棵树。然而因为这条边仍然是真实存在的,就对于这条边的两个顶点,分别以这两个点为根,做一次不取这个点的树形dp。dp[i][0/1]表示以i为根的子树中取/不取i的最大战斗力。

/*ргргрг*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1000050;
const int INF = 0x3f3f3f3f;
const ll mod = 1000000007;
const double eps = 1e-8;

int n, x, no, tp1, tp2, ban;
bool vis[maxn];
int head[maxn];
ll w[maxn], dp[maxn][2], ans;
struct node
{
    int to;
    int nxt;
}e[maxn << 1];

void add(int a, int b)
{
    e[no].to = b;
    e[no].nxt = head[a];
    head[a] = no++;
}

void sea_ring(int u, int fa)
{
    vis[u] = 1;
    for(int i = head[u];i != -1;i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == fa) continue;
        if(vis[v])
        {
            ban = i;
            tp1 = u, tp2 = v;
        }
        else sea_ring(v, u);
    }
}

void dfs(int u, int fa)
{
    dp[u][0] = 0, dp[u][1] = w[u];
    for(int i = head[u];i != -1;i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == fa) continue;
        if(i == ban || i == (ban ^ 1)) continue;
        dfs(v, u);
        dp[u][0] += max(dp[v][0], dp[v][1]);
        dp[u][1] += dp[v][0];
    }
}

int main()
{
    scanf("%d", &n);
    no = 0;
    memset(head, -1, sizeof(head));
    for(int i = 1;i <= n;i++)
    {
        scanf("%lld%d", &w[i], &x);
        add(i, x);
        add(x, i);
    }
    ans = 0;
    memset(vis, 0, sizeof(vis));
    for(int i = 1;i <= n;i++)
    {
        if(vis[i]) continue;
        sea_ring(i, -1);
        dfs(tp1, -1);
        ll tmp = dp[tp1][0];
        dfs(tp2, -1);
        ans += max(tmp, dp[tp2][0]);
    }
    printf("%lld\n", ans);
    return 0;
}

poj2152 有一些城市构成了树形结构,现在需要在某些城市修建消防站,在第i个城市修建消防站需要wi的费用,同时第i个城市可以被距离不超过di的消防站覆盖。现要求所有的城市都被消防站覆盖,求最小的总费用。

由于是树形结构,可以在n^2的时间内预处理出图中各点之间的距离,用dis(i, j)表示图中i和j之间的距离。

考虑在树上dp,ans[i]表示以i为根的子树的结果,dp[u][v]表示u被v覆盖时,以u为根的子树的结果。对于每一个dis(u, v)<=d(u)的节点v,都尝试用v去覆盖u。此时对于u的每一个子节点k,都考虑被v覆盖或不被v覆盖的情况,在这两种情况中取较小值加入到u的答案中。如果dis(u, v)>d(u)那么dp[u][v]就设成INF。即状态转移方程为:

dp(u,v)=\begin{cases} w(v)+\sum_{k}^{(u,k)}min\left \{ ans(k),dp(k,v)-w(v) \right \} & \text{ if } dis(u,v)<=d(u) \\ inf& \text{ if } dis(u,v)>d(u) \end{cases}

ans(u)=min\left \{ dp(u,v)|(u,v) \right \}

显然有由于对u转移的过程中用到了u的子节点的结果,在dp的时候需要先递归,在回溯的时候进行转移。

/*ргргрг*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1005;
const int INF = 0x3f3f3f3f;
const ll mod = 1000000007;
const double eps = 1e-8;

int t, n, no, a, b, x, ans[maxn];
int dis[maxn][maxn], dp[maxn][maxn];
int w[maxn], d[maxn], head[maxn];
bool vis[maxn];
struct node
{
    int to;
    int nxt;
    int w;
}e[maxn << 1];

void add(int a, int b, int x)
{
    e[no].to = b;
    e[no].w = x;
    e[no].nxt = head[a];
    head[a] = no++;
}

void cal_dis(int s)
{
    queue<int>q;
    memset(vis, 0, sizeof(vis));
    vis[s] = 1;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i = head[u];i != -1;i = e[i].nxt)
        {
            int v = e[i].to;
            if(!vis[v])
            {
                dis[s][v] = dis[s][u] + e[i].w;
                vis[v] = 1;
                q.push(v);
            }
        }
    }
}

void init()
{
    no = 0;
    memset(head, -1, sizeof(head));
    memset(dp, 0, sizeof(dp));
    memset(dis, 0, sizeof(dis));
    for(int i = 1;i <= n;i++)
        ans[i] = INF;
}

void dfs(int u, int fa)
{
    vis[u] = 1;
    for(int i = head[u];i != -1;i = e[i].nxt)
    {
        int v = e[i].to;
        if(!vis[v]) dfs(v, u);
    }
    for(int v = 1;v <= n;v++)
    {
        if(dis[u][v] <= d[u])
        {
            int tmp = 0;
            for(int i = head[u];i != -1;i = e[i].nxt)
            {
                int k = e[i].to;
                if(k == fa) continue;
                tmp += min(ans[k], dp[k][v] - w[v]);
            }
            dp[u][v] = tmp + w[v];
        }
        else dp[u][v] = INF;
    }
    for(int i = 1;i <= n;i++)
        ans[u] = min(ans[u], dp[u][i]);
}

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        init();
        for(int i = 1;i <= n;i++) scanf("%d", &w[i]);
        for(int i = 1;i <= n;i++) scanf("%d", &d[i]);
        for(int i = 1;i < n;i++)
        {
            scanf("%d%d%d", &a, &b, &x);
            add(a, b, x);
            add(b, a, x);
        }
        for(int i = 1;i <= n;i++) cal_dis(i);
        memset(vis, 0, sizeof(vis));
        dfs(1, 0);
        printf("%d\n", ans[1]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/NPU_SXY/article/details/81704092
今日推荐