Kingdoms UVA - 12507 状压||dfs搜索+最小生成树

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

做法一:暴力每一个节点的存在或者不存在,存在为1,不存在为0,用最小生成树连接一下,如果这个最小值小于k,并且可以生成最小生成树,那么就算出他的总人口求一个最大值

#include<bits/stdc++.h>//time:40ms
using namespace std;
int pep[20];
int pre[20];
int vis[20];
int maxans=0;
struct aa
{
    int u,v,w;
} edge[111];
int findboss(int x)
{
    if(x==pre[x])
        return x;
    else
    {
        pre[x]=findboss(pre[x]);
        return pre[x];
    }
}
bool comb(int a,int b)//并查集连接
{
    int aa=findboss(a);
    int bb=findboss(b);
    if(aa==bb)
        return 0;
    else
    {
        pre[aa]=bb;
        return 1;
    }
}
bool cmp(const aa a,const aa b)
{
    return a.w<b.w;
}
void dfs(int now,int n,int m,int k,int cnts)//遍历到的节点编号,n,m,k,当前开放的节点数
{
    if(now>n)//叶节点的时候状态已经确定
    {
        for(int i=1; i<=now; i++)
            pre[i]=i;
        int cnt=0;
        int sum=0;
        for(int i=0; i<m; i++)
        {
            int u=edge[i].u;
            int v=edge[i].v;
            int w=edge[i].w;
            if(vis[u]&&vis[v]&&comb(u,v))//u ,v 开放,u,v可以连接
            {
                cnt++;
                sum+=w;
            }
            if(cnt==cnts-1)
            {
                break;
            }
        }
        if((cnt==cnts-1)&&(sum<=k))
        {
            int tsum=0;
            for(int i=1; i<=now; i++)//所有人口
            {
                if(vis[i])
                    tsum+=pep[i];
            }

            // printf("%d\n",tsum);
            if(tsum>maxans)
                maxans=tsum;
        }
        return;
    }
    vis[now+1]=0;
    dfs(now+1,n,m,k,cnts);//下一个节点不开放
    vis[now+1]=1;
    dfs(now+1,n,m,k,cnts+1);//下一个节点开放

}
int main()
{
    // freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m,k;
        maxans=0;
        memset(vis,0,sizeof(vis));
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1; i<=n; i++)
            scanf("%d",&pep[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);
        vis[1]=1;
        dfs(1,n,m,k,1);//从第一个节点开始搜索,开放节点数为1
        printf("%d\n",maxans);

    }
}

做法二:其实是做法一的一种改进,我们根本不用使用dfs去搜索,直接用二进制状态表示这一堆状态就可以了。然后对于每一种状态具体去讨论。用时相对较少。

下面给出代码

#include<bits/stdc++.h>
using namespace std;
int pep[20];
int pre[20];
int vis[20];
int dp[1<<16];
int maxans=0;
struct aa
{
    int u,v,w;
} edge[111];
int findboss(int x)
{
    if(x==pre[x])
        return x;
    else
    {
        pre[x]=findboss(pre[x]);
        return pre[x];
    }
}
bool comb(int a,int b)//并查集连接
{
    int aa=findboss(a);
    int bb=findboss(b);
    if(aa==bb)
        return 0;
    else
    {
        pre[aa]=bb;
        return 1;
    }
}
bool cmp(const aa a,const aa b)
{
    return a.w<b.w;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m,k;
        maxans=0;
        memset(vis,0,sizeof(vis));
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1; i<=n; i++)
            scanf("%d",&pep[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)
        for(int i=0; i<len; i++)
        {
            int cnts=0;
            for(int j=0; j<n; j++)//搜索出该状态存在的点
            {
                if(((1<<j)&i)==0)
                    vis[j+1]=0;
                else
                {
                    vis[j+1]=1;
                    cnts++;
                }
            }
            if(vis[1]!=1)//如果点不包含1节点
                continue;
            for(int j=1; j<=n; j++)//初始化并查集操作
                pre[j]=j;
            int cnt=0;
            int sum=0;
            for(int j=0; j<m; j++)//遍历所有可行点,进行最小生成树操作
            {
                int u=edge[j].u;
                int v=edge[j].v;
                int w=edge[j].w;
                if(vis[u]&&vis[v]&&comb(u,v))//u ,v 开放,u,v可以连接
                {
                    cnt++;
                    sum+=w;
                }
                if(cnt==cnts-1)
                {
                    break;
                }
            }
            if((cnt==cnts-1)&&(sum<=k))//钱数目符合,并且可以生成最小生成树
            {
                int tsum=0;
                for(int j=1; j<=n; j++)//统计所有人口
                {
                    if(vis[j])
                        tsum+=pep[j];
                }
                dp[i]=tsum;
                if(dp[i]>maxans)//最大值求解
                    maxans=dp[i];
            }

        }

        printf("%d\n",maxans);

    }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值