HDU 4081 Qin Shi Huang's National Road System prim + (DP 或 树形DP) 好题

 题意:

有n个(n<=1000)城市,告诉坐标(int),边的权值就是两点的距离,并且每个城市都有人居住,现在要修路n-1条路,使得每个城市都连通。现能让一条边可以不用任何花费。求 这条边的两端点的总人数/(包含这条边的最小生成树的总权值-这条边的权值)最大值。即(Wa+Wb)/(mst-w(a,b))最大。

 

思路:

 先求该图的最小生成树,prim,O(n^2);

 方法一: 枚举边

 枚举最小生成树的每条边,去掉这条边,最小生成树变成了2个各自连通的树,假设为树A,B。分别找到树A,B中人口最多的两个点,这两个点连起来就是去掉这条边所取得的最大比例。用树形DP可以求这个最大比例。

 方法二: 枚举点

在求最小生成树的过程中,预处理求出点i到点j之间在最小生成树里的最大边,用dp[i][j] 保存。枚举每两个点,求出最大值。

 

方法一:

处理1:

1. 用dp[i][j]表示树A中的点i  到 树B及      其子树(j点所在的树)的最大人口,这个过程可以在一边dfs就可以出来,对于每个 i 的dfs 复杂度是O(n) ,外加一个n的循环求出每个点,这里的总复杂度为 O(n^2)。

2. 通过求出来的dp[i][j] 再用一个dfs 求出  树B 到 树A的最大人口,(方法:枚举树A中的所有点 到 树B的最大人口,取其中的最小值。)显然, 这个求出来的值是我们要的去掉这条边所取得的最大比例。

代码1:

View Code
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define maxn 1003
#define inf 1000000000

int x[maxn], y[maxn], p[maxn];
double map[maxn][maxn];
double dis[maxn];
int pre[maxn];
bool vis[maxn];
double mst;


int minz(int a, int b)
{
    return a < b ? a : b;
}

int maxz(int a, int b)
{
    return a > b ? a : b;
}

int n, m;
double ans;

struct E
{
    int v, next;
}edge[maxn<<1];

int tot, head[maxn];

void init()
{
    tot = 0;
    memset(head, -1, sizeof(int)*(n+1));
}

void add(int s, int t)
{
    edge[tot].v = t;
    edge[tot].next = head[s];
    head[s] = tot++;
}

double Dis(int x1, int y1, int x2, int y2)
{
    return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0);
}

void prim()
{
    int i, j, k;
    for(i = 1; i < n; i++)
        dis[i] = map[0][i], vis[i] = 0, pre[i] = 0;
    dis[0] = inf, vis[0] = 1, pre[0] = -1;
    
    mst = 0;
    for(i = 0; i < n-1; i++)
    {
        k = 0;
        for(j = 1; j < n; j++)
            if(!vis[j] && dis[k] > dis[j])
                k = j;
        vis[k] = 1;
        mst += dis[k];

        if(pre[k] != -1)
        {
            add(pre[k], k);
            add(k, pre[k]);
        }

        for(j = 1; j < n; j++)
            if(!vis[j] && dis[j] > map[k][j])
                dis[j] = map[k][j], pre[j] = k;
    }
}

int k;

int dp[maxn][maxn];

int dfs1(int u, int fa, int rt) // 求 点rt 到 以u为根的数及其子树的最大人口
{
    int i;
    for(i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        if(v == fa) continue;
        dp[rt][u] = maxz(dp[rt][u], dfs1(v, u, rt));
    }
    dp[rt][u] = maxz(dp[rt][u], p[u] + p[rt]);
    return dp[rt][u];
}

int dfs2(int u, int fa, int rt)  // 求 以rt为根的数及其子树 到 以u为根的数及其子树的最大人口
{
    int i, ans = dp[u][rt];
    for(i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        if(v == fa) continue;
        ans = maxz(ans, dfs2(v, u, rt));
    }
    return ans;
}

void solve()
{
    int i, j;
    memset(dp, 0, sizeof(dp));
    for(i = 0; i < n; i++)
        dfs1(i, -1, i);
    double ans = 0;
    for(i = 0; i < n ;i++) //枚举删去的每条边,求出删去这边所得的最大比例。
        for(j = head[i]; j != -1; j = edge[j].next)
        {
            int v = edge[j].v;
            double tmp = dfs2(v, i, i) * 1.0/ (mst - map[i][v]); 
            if(tmp > ans) ans = tmp;
        }
    printf("%.2f\n", ans);
}

int main()
{
    int i, j, cas;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%d", &n);
        for(i = 0; i < n; i++)
            scanf("%d%d%d", &x[i], &y[i], &p[i]);
        init();
        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++)
                if(i != j)
                    map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]);
        prim();
        solve();
    }
    return 0;
}

 

处理2:

处理1的优化。

用dp[i][j]表示树A(i点所在的树) 到 树B(j点所在的树)的最大人口。

dfs 的功能是 求树A中的点i 到 树B(j点所在的树)的最大人口,跟上面一种方法一样,对于每个 i 的dfs 复杂度是O(n) ,外加一个n的循环求出每个点,这里的总复杂度为 O(n^2)。 关键是, 一边进行dfs,一边通过每一层的dfs返回值来更新dp[i][j]。

 代码2:

View Code
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define maxn 1003
#define inf 1000000000

int x[maxn], y[maxn], p[maxn];
double map[maxn][maxn];
double dis[maxn];
int pre[maxn];
bool vis[maxn];
double mst;


int maxz(int a, int b)
{
    return a > b ? a : b;
}

int n, m;
double ans;

struct E
{
    int v, next;
}edge[maxn<<1];

int tot, head[maxn];

void init()
{
    tot = 0;
    memset(head, -1, sizeof(int)*(n+1));
}

void add(int s, int t)
{
    edge[tot].v = t;
    edge[tot].next = head[s];
    head[s] = tot++;
}

double Dis(int x1, int y1, int x2, int y2)
{
    return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0);
}

void prim()
{
    int i, j, k;
    for(i = 1; i < n; i++)
        dis[i] = map[0][i], vis[i] = 0, pre[i] = 0;
    dis[0] = inf, vis[0] = 1, pre[0] = -1;
    
    mst = 0;
    for(i = 0; i < n-1; i++)
    {
        k = 0;
        for(j = 1; j < n; j++)
            if(!vis[j] && dis[k] > dis[j])
                k = j;
        vis[k] = 1;
        mst += dis[k];

        if(pre[k] != -1)
        {
            add(k, pre[k]);
            add(pre[k], k);
        }

        for(j = 1; j < n; j++)
            if(!vis[j] && dis[j] > map[k][j])
                dis[j] = map[k][j], pre[j] = k;
    }
}

int dp[maxn][maxn];

int dfs(int u, int fa, int rt) //dfs求rt点 到 以u为根的树及其子树的最大人口
{
    int i, ans = 0;
    for(i = head[u] ; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        if(v == fa) continue;
        int tt = dfs(v, u, rt);  
        ans = maxz(ans, tt);
        dp[u][v] = dp[v][u] = maxz(dp[u][v], tt); //通过dfs的返回值来更新dp[i][j],怎么更新自己理解吧。
    }
    ans = maxz(ans, p[u] + p[rt]);
    return ans;
}

void solve()
{
    int i, j;
    memset(dp, 0, sizeof(dp));
    for(i = 0; i < n; i++)
        dfs(i, -1, i);
    double ans = 0;
    for(i = 0; i < n; i++)
        for(j = head[i]; j != -1; j = edge[j].next)
        {
            int v = edge[j].v;
            double tmp = dp[v][i] * 1.0/(mst - map[v][i]);
            if(tmp > ans) ans = tmp;
        }
    printf("%.2f\n", ans);
}
int main()
{
    int i, j, cas;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%d", &n);
        for(i = 0; i < n; i++)
            scanf("%d%d%d", &x[i], &y[i], &p[i]);
        init();
        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++)
                if(i != j)
                    map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]);
        prim();
        solve();
    }
    return 0;
}

 

处理3:

处理1,2的优化。

删去一边时,我们在分别找树A和树B中的人口最大时,发现人口最大的一个城市一定会被选上,这一点不难证明,那么我们就找出这个城市,以这个城市为根节点,我们只需一个dfs来求删去每条生成树的边所取得的最大比例。

代码3:

View Code
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define maxn 1003
#define inf 1000000000

int x[maxn], y[maxn], p[maxn];
double map[maxn][maxn];
double dis[maxn];
int pre[maxn];
bool vis[maxn];
double mst;


int maxz(int a, int b)
{
    return a > b ? a : b;
}

int n, m;
double ans;

struct E
{
    int v, next;
}edge[maxn<<1];

int tot, head[maxn];

void init()
{
    tot = 0;
    memset(head, -1, sizeof(int)*(n+1));
}

void add(int s, int t)
{
    edge[tot].v = t;
    edge[tot].next = head[s];
    head[s] = tot++;
}

double Dis(int x1, int y1, int x2, int y2)
{
    return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0);
}

void prim()
{
    int i, j, k;
    for(i = 1; i < n; i++)
        dis[i] = map[0][i], vis[i] = 0, pre[i] = 0;
    dis[0] = inf, vis[0] = 1, pre[0] = -1;
    
    mst = 0;
    for(i = 0; i < n-1; i++)
    {
        k = 0;
        for(j = 1; j < n; j++)
            if(!vis[j] && dis[k] > dis[j])
                k = j;
        vis[k] = 1;
        mst += dis[k];

        if(pre[k] != -1)
        {
            add(k, pre[k]);
            add(pre[k], k);
        }

        for(j = 1; j < n; j++)
            if(!vis[j] && dis[j] > map[k][j])
                dis[j] = map[k][j], pre[j] = k;
    }
}

int k;
int dfs(int u, int fa)
{
    int i, tmp = p[u];
    for(i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        if(v == fa) continue;
        int tt = dfs(v, u);
        double pp = (p[k] + tt) / (mst - map[u][v]);
        if(ans < pp) ans = pp; 
        tmp = maxz(tmp, tt);
    }
    return tmp;
}

void solve()
{
    int i, j;
    ans = 0; k = 0;
    for(i = 1; i < n; i++)
        if(p[i] > p[k]) k = i;
    dfs(k, -1);
    printf("%.2f\n", ans);
}



int main()
{
    int i, j, cas;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%d", &n);
        for(i = 0; i < n; i++)
            scanf("%d%d%d", &x[i], &y[i], &p[i]);
        init();
        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++)
                if(i != j)
                    map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]);
        prim();
        solve();
    }
    return 0;
}

 

方法二:

代码4:

View Code
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define maxn 1003
#define inf 1000000000

int x[maxn], y[maxn], p[maxn];
double map[maxn][maxn];
double dis[maxn];
int pre[maxn];
bool vis[maxn];
double mst;
double dp[maxn][maxn];

double maxz(double a, double b)
{
    return a > b ? a : b;
}

int n, m;
double ans;

double Dis(int x1, int y1, int x2, int y2)
{
    return sqrt((x1 - x2) * (x1 - x2) * 1.0 + (y1 - y2) * (y1 - y2) * 1.0);
}

void prim()
{
    int i, j, k;
    memset(dp, 0, sizeof(dp));
    for(i = 1; i < n; i++)
        dis[i] = map[0][i], vis[i] = 0, pre[i] = 0;
    dis[0] = inf, vis[0] = 1, pre[0] = -1;
    
    mst = 0;
    for(i = 0; i < n-1; i++)
    {
        k = 0;
        for(j = 1; j < n; j++)
            if(!vis[j] && dis[k] > dis[j])
                k = j;
        vis[k] = 1;
        mst += dis[k];
        dp[pre[k]][k] = dp[k][pre[k]] = map[k][pre[k]]; 

        for(j = 1; j < n; j++)
            if(!vis[j] && dis[j] > map[k][j])
                dis[j] = map[k][j], pre[j] = k;
        for(j = 1; j < n; j++)  
             if(vis[j] && j != k)  
                  dp[j][k] = dp[k][j] = maxz(dp[j][pre[k]], dp[pre[k]][k]); 
    }
}

void solve()
{
    int i, j;
    double ans = 0;
    for(i = 0; i < n ;i++)
        for(j = 0; j < n; j++)
        {
            if(i == j) continue;
            double tmp = (p[i] + p[j]) * 1.0/ (mst - dp[i][j]);
            if(tmp > ans) ans = tmp;
        }
    printf("%.2f\n", ans);
}

int main()
{
    int i, j, cas;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%d", &n);
        for(i = 0; i < n; i++)
            scanf("%d%d%d", &x[i], &y[i], &p[i]);
        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++)
                if(i != j)
                    map[i][j] = map[j][i] = Dis(x[i], y[i], x[j], y[j]);
        prim();
        solve();
    }
    return 0;
}

 

 

 

 

posted @ 2012-10-07 22:30  To be an ACMan  Views(1113)  Comments(0)    收藏  举报