uva10859

一道多目标优化题。

一个是要求要在尽量小的节点上放灯,使得所有边都被照亮,这是优化一个最小值,并且这个最小值的优化的优先级最大。

另一个是要求在灯的总数最小的前提下,被两盏灯同时照亮的边数应该尽量大,这是一个最大值的优化。

白书上说,我们可以把这个一个是最小值,一个是最大值的优化改变成两个最小值的优化。

方法是把“被两盏灯同时照亮的边数应该尽量大”的优化改成恰好被一盏灯照亮的边数的最小值。(这两者是等价的)

这样就把问题转变成了两个最小值的优化。

今天新学的trick是以一个变量来同时优化这两个最小值。

令放置的灯数为a,恰好被一盏灯照亮的边数为c,且令M为一个大整数。

令x = a*M + c即可,我们只需要优化x最小。

这么做的道理在哪呢?

首先声明M是一个非常大的整数,以至于x的值主要是由a来决定而不是c。

这样就保证了以a作为前提,a的优先级最大。

当a相同时,x的值自然就取决于c。(这有点像两个关键字的排序问题)

所以M应该不受c的影响,具体而言,M是一个比“c的最大理论值与最小理论值之差”还要大的数,本题中,我们取M = 2000。

并且本题说明这是一个“无向无环图”,也就是树,我们自然想到树形dp。

考虑到当前的节点放灯与不放灯对后续的节点有影响,所以我们设状态的时候要多设一位表示该节点放不放灯。

设d(i,j)表示以i为根的子树(包括i)在i的父亲放不放灯的情况下的x的最小值。(j=0:不放;j=1:放)

决策1:当前节点不放灯。这个决策只能它的父亲节点放了灯或者当前节点就是根节点才能被作出。

d(i,j) = sum{d(son,0)}。

如果当前节点不是根,d(i,j)还要+1。因为此时当前节点与其父亲的连边上只被一盏灯照亮。

决策2:当前节点放灯。当该节点的父亲节点没有放灯时,该节点一定要放灯。(也就是说不能做决策1)

d(i,j) = sum{d(son,1)} + M。

如果父亲节点没有放灯,且当前节点不是根,d(i,j)还要+1。

两个决策取能做的决策中的最小值。

代码部分模仿lrj白书,有修改。

#include <cstdio>
#include <cstring>
#include <algorithm> 

using namespace std;

const int maxn = 1005, maxm = maxn * 2, M = 2000;

int t, n, m, tot;

int h[maxn], vis[maxn][2], d[maxn][2], isroot[maxn];

struct edge
{
    int v, next;
}a[maxm];

void add(int x, int y)
{
    a[tot].v = y;
    a[tot].next = h[x];
    h[x] = tot++;
}

int dfs(int u, int f, int j)
{
    if (vis[u][j]) return d[u][j];
    vis[u][j] = 1;
    int& ans = d[u][j];
    int s1 = 0, s2 = 0;//s1不放灯,s2放灯。 
    for (int i = h[u]; ~i; i = a[i].next)
    {
        int v = a[i].v;
        if (v == f) continue;
        if (j == 1 || isroot[u])
        {
            s1 += dfs(v, u, 0);
        }
        s2 += dfs(v, u, 1);
    }
    if (!isroot[u]) s1++;
    s2 += M;
    if (j == 0 && !isroot[u]) s2++;
    if (j == 0 && !isroot[u]) return ans = s2;//父亲没放灯,当前结点一定要放灯。 
    return ans = min(s1, s2);
}

void solve()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h); tot = 0;
    for (int i = 0; i < m; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y); add(y, x);
    }
    memset(vis, 0, sizeof vis);
    memset(isroot, 0, sizeof isroot);
    int ans = 0;
    for (int i = 0; i < n; i++)
            if (!vis[i][0])
            {
                isroot[i] = 1;
                ans += dfs(i, -1, 0);
            }
    printf("%d %d %d
", ans / 2000, m - ans % 2000, ans % 2000);//最后还原a,c
}

int main()
{
//    freopen("uva10859.in", "r", stdin); 
    scanf("%d", &t);
    while (t--) solve();
    return 0;
}
原文地址:https://www.cnblogs.com/yohanlong/p/7764014.html