UVa 12507 - Kingdoms(状压dp)

A kingdom has n cities numbered 1 to n, and some bidirectional roads connecting cities. The capital
is always city 1.
After a war, all the roads of the kingdom are destroyed. The king wants to rebuild some of the
roads to connect the cities, but unfortunately, the kingdom is running out of money. The total cost of
rebuilding roads should not exceed K.
Given the list of m roads that can be rebuilt (other roads are severely damaged and cannot be
rebuilt), the king decided to maximize the total population in the capital and all other cities that are
connected (directly or indirectly) with the capital (we call it “accessible population”), can you help
him?
Input
The first line of input contains a single integer T (T ≤ 20), the number of test cases. Each test case
begins with three integers n (4 ≤ n ≤ 16), m (1 ≤ m ≤ 100) and K (1 ≤ K ≤ 100, 000). The second
line contains n positive integers pi (1 ≤ pi ≤ 10, 000), the population of each city. Each of the following
m lines contains three positive integers u, v, c (1 ≤ u, v ≤ n, 1 ≤ c ≤ 1000), representing a destroyed
road connecting city u and v, whose rebuilding cost is c. Note that two cities can be directly connected
by more than one road, but a road cannot directly connect a city and itself.
Output
For each test case, print the maximal accessible population.
Sample Input
2
4 6 6
500 400 300 200
1 2 4
1 3 3
1 4 2
4 3 5
2 4 6
3 2 7
4 6 5
500 400 300 200
1 2 4
1 3 3
1 4 2
4 3 5
2 4 6
3 2 7
Sample Output
1100

1000

题目大意, 给你个n点, m条边的无线图, 顶点1为首都, 每个点的权值为人口数, 每条边的权值为修复此道路的花费, 让你得出花费不超过k的情况下, 以首都为起点的最小生成树所包含的最大人口数。

看了别人的博客, 想了一晚上, 算是搞懂了一些。 基本思路就是暴力搜索, 顶点数为16, 可以用int类型的每一个二进制位表示所有顶点的全部组合形式, 然后验证每一种形式的可行性,最后得出一个最大解。

#include<bits/stdc++.h>
using namespace std;
int pre[20];//并查集
struct node
{
    int v;
    int u;
    int w;
} edge[111]; //边集
int num[20];//存每一点人口数
int k, n, m;
int vis[20];//标记出每一种组合形式
void mix(int a, int b)
{
    pre[a] = b;
}
int Find(int a)
{
    int t = a;
    while(pre[a] != a)
    {
        a = pre[a];
    }
    while(t != a)//并查集优化, 这个循环可以去掉
    {
        int tt = pre[t];
        pre[t] = a;
        t = tt;
    }
    return a;
}
bool cmp(struct node a, struct node b)
{
    return a.w <= b.w;
}
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d%d", &n, &m, &k);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &num[i]);
        }
        for(int i = 0; i < m; i++)
        {
            scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
        }
        sort(edge,edge + m, cmp);
        int len = (1 << n);//将所有组合压缩到 0 - len- 1
        int max_p = -1;//存最大解
        for(int i = 0; i < len; i++)
        {
            int v_cnt = 0;//存每一种组合所包含的点数
            for(int j = 0; j < n; j++)//验证第j + 1个点在第i种组合中是否要连通
            {
                if((1 << j) & i)//第j位存在则将j+1点标记
                {
                    vis[j + 1] = 1;
                    v_cnt ++;
                }
                else
                    vis[j + 1] = 0;
            }
            if(!vis[1])//如果这种组合不包含首都则直接pass掉
            {
                continue;
            }
            for(int j = 1; j <= n; j++)//并查集准备
                pre[j] = j;
            int e_cnt = 0;//存边数量
            int cost = 0;//存花费
            for(int j = 0; j < m; j++)
            {
                int u = edge[j].u;
                int v = edge[j].v;
                int w = edge[j].w;
                int pre_u = Find(u);
                int pre_v = Find(v);
                if(vis[u] && vis[v] && (pre_u != pre_v))//两点被标记且还可以合并则将其合并,边数加一
                {
                    mix(pre_u, pre_v);
                    e_cnt++;
                    cost += w;
                }
                if(e_cnt == v_cnt - 1)
                    break;
            }
            int pop_cnt = 0;
            if(e_cnt == v_cnt - 1 && cost <= k)//满足最小生成树条件且花费不大于k即为一课可行最小生成树,找出各个可行最小生成树的最大人口数
            {
                for(int j = 1; j <= n; j++)
                {
                    if(vis[j])
                    {
                        pop_cnt += num[j];
                    }
                }
                max_p = max(pop_cnt, max_p);
            }

        }
        printf("%d\n", max_p);
    }
    return 0;


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值