FZU - 2295 Human life (最大权闭合子图)

题目链接

FZU - 2295 Human life

题目分析

题意:你在玩一个游戏,在其中你可以通过学习一些技能,但是学习某些技能之前,可能还要学习一些其他的技能,并且学习任何技能都有一定的花费;

而我们可以通过掌握某些工作以获取报酬,为了掌握这一工作,我们必须学会特定的技能。

不过有些工作彼此之间是冲突的,简单来说:如果你掌握了工作A,那么将无法掌握工作B

思路:

由于技能之间也存在依赖关系,但实际上如果要获取某一工作的报酬,那么必须选择这个工作的前置技能以及前置技能的前置技能,

那么显然,这些形成依赖关系的技能都是这一工作的前置技能,所以我们的问题就是求最大权闭合子图了。

我们在工作和其所有的前置技能之间建一条容量为inf的边,然后由所有的技能向汇点建一条容量为这一技能学习消耗的边,

再由源点向每个工作建一条容量为这一工作报酬的边。

这个地方还加上了一个条件,有些工作无法同时获取,不过这个地方产生冲突最大对数为5个,那么我们枚举所有的情况就好了,

一共2^5 = 32种,根据每种状态来决定可选取的工作,并构建这一顶点及其相关的边

然后根据:最大闭合子图的权值 = 所有权值为正的结点的权值之和 - 最小割(最大流)求出答案

顺便吐糟一下这个题,首先这个OJ的C++环境不是C11标准,也就是说不支持大括号给结构体变量赋值,比如这样:

我这个语法写了近一年了,从未出错,这个评测机第一次把我这里卡了,一直CE,还不给出错误信息QAQ....

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>
#define bug cout << "**********" << endl
#define show(x,y) cout<<"["<<x<<","<<y<<"] "
//#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int Max = 1e3+ 10;

struct Edge
{
    int to, next, flow;
}edge[Max << 2];

int T, n, m, k;
int s, t;
vector<int>edge_raw[Max];            //记录原图边的关系
int vis[2010][1010];                //vis[i][j]记录j是否是i的前置结点
int select[2010];                    //记录某点是否被删除(处理工作冲突)
int use[1010];                        //记录每个点的是否使用(处理技能之间的前置关系)
int head[2010], tot;
int cost[1010];                        //记录了学习每个技能的花费
int val[2010];                        //记录掌握每个工作的报酬
int dis[2010];                        //记录i的层次编号(Dinic中使用)
pair<int, int>fight[10];            //记录冲突

void init()
{
    s = 0, t = n + m + 1;            //1-m为技能,m+1~n+m为工作
    memset(vis, 0, sizeof(vis));
    memset(use, 0, sizeof(use));
    for (int i = 0;i <= n; i++)
        edge_raw[i].clear();
}

void add(int u, int v, int flow)
{
    edge[tot].to = v;
    edge[tot].flow = flow;
    edge[tot].next = head[u];
    head[u] = tot++;

    edge[tot].to = u;
    edge[tot].flow = 0;
    edge[tot].next = head[v];
    head[v] = tot++;
}

void dfs(int u)                            //找到每个技能所有的前置技能
{
    use[u] = true;                        //代表u已经访问
    for (int i = 0; i < edge_raw[u].size(); i++)
    {
        int v = edge_raw[u][i];
        vis[u][v] = true;
        if (!use[v])                    //自己是自己的前置结点
        {
            dfs(v);
        }
        for (int j = 1;j <= n;j++)        //枚举这个点的前置结点
        {
            if (vis[v][j])                //v的前置结点是j,那么u的前置结点也是j
            {
                vis[u][j] = true;
            }
        }
    }
}

bool bfs()                                //判断连通性,将图分层次
{
    queue<int>q;
    memset(dis, -1, sizeof(dis));
    dis[s] = 0;
    q.push(s);
    while (!q.empty())
    {
        int u = q.front();q.pop();

        for (int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if (dis[v] == -1 && edge[i].flow > 0)
            {
                dis[v] = dis[u] + 1;
                q.push(v);
                if (v == t) return true;
            }
        }
    }
    return false;
}

int dfs(int u, int flow_in)
{
    if (u == t) return flow_in;
    int flow_out = 0;                        //实际流出流量
    for (int i = head[u];i != -1;i = edge[i].next)
    {
        int v = edge[i].to;
        if (dis[v] == dis[u] + 1 && edge[i].flow > 0)
        {
            int flow_part = dfs(v, min(flow_in, edge[i].flow));
            if (flow_part == 0)continue;    //无法形成增广路
            flow_in -= flow_part;
            flow_out += flow_part;
            edge[i].flow -= flow_part;
            edge[i ^ 1].flow += flow_part;
            if (flow_in == 0)break;
        }
    }
    return flow_out;
}

int max_flow()
{
    int sum = 0;
    while (bfs())
    {
        sum += dfs(s, inf);
    }
    return sum;
}

int main()
{
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d%d", &n, &m, &k);
        init();
        for (int i = 1, num;i <= n;i++)
        {
            scanf("%d%d", cost + i, &num);
            for (int j = 1;j <= num;j++)
            {
                int pre;                                //技能i的前置技能点
                scanf("%d", &pre);
                edge_raw[i].push_back(pre);                //构建直系前置技能关系
            }
        }
        for (int i = 1;i <= n;i++)
        {
            if (!use[i])dfs(i);
        }

        for (int i = n + 1, cnt; i <= n + m;i++)        //工作编号n +1 ~n+m
        {
            scanf("%d%d", val + i, &cnt);
            while (cnt--)
            {
                int x;
                scanf("%d", &x);
                vis[i][x] = true;
                for (int j = 1;j <= n;j++)
                {
                    if (vis[x][j])                        //求出工作所有的前置技能
                        vis[i][j] = true;
                }
            }
        }

        for (int i = 0;i < k;i++)
        {
            scanf("%d %d", &fight[i].first, &fight[i].second);
        }
        int max_val = 0;

        for (int state = 0;state < (1 << k);state++)    //枚举状态,对应位置为1表示选first,0表示选second
        {
            memset(select, 0, sizeof(select));            //0表示不删除
            memset(head, -1, sizeof(head));tot = 0;
            for (int i = 0;i < k;i++)
            {
                if ((state >> i) & 1)
                {
                    select[fight[i].second] = true;        //删除second
                }
                else
                {
                    select[fight[i].first] = true;        //删除first
                }
            }
            int sum = 0;                                //记录总价值
            for (int i = 1 + n;i <= n + m;i++)
            {
                if (select[i - n])continue;                //当前状态下不不选取的工作就不用构建与之有关的边了
                sum += val[i];
                add(s, i, val[i]);                        //由源点向可选工作构建一条容量为当前工作报酬的边
                for (int j = 1;j <= n;j++)
                {
                    if (vis[i][j])
                    {
                        add(i, j, inf);                    //有工作向其所有前置技能点建一条容量为inf的边
                    }
                }
            }
            for (int i = 1;i <= n;i++)                    //由所有技能向汇点构建一条容量为其花费的边
            {
                add(i, t, cost[i]);
            }
            int flow = max_flow();
            max_val = max(max_val, sum - flow);            //最大闭合子图的权值 = 所有权值为正的结点的权值之和 - 最小割(最大流)
        }
        printf("%d\n", max_val);
    }
    return 0;
}

FZU 2295

原文地址:https://www.cnblogs.com/winter-bamboo/p/11332667.html

时间: 2024-11-06 12:44:31

FZU - 2295 Human life (最大权闭合子图)的相关文章

FZU2295 Human life:网络流-最大权闭合子图-二进制优化-第九届福建省大学生程序设计竞赛

目录 Catalog Solution: (有任何问题欢迎留言或私聊 && 欢迎交流讨论哦 Catalog Problem:Portal传送门 ?原题目描述在最下面. ?题意就是很裸的最大权闭合子图. ?推荐阅读:胡伯涛<最小割模型在信息学竞赛中的应用> ?完完全全的模板题:新疆大学五月月赛-D-勤奋的杨老师 ?本题题意:m(50)个任务,n个技能.完成每个任务由相应的收益,完成每个任务前必须学一些技能.有些技能由先修技能. ?有些任务不能同时完成. Solution: ?训练

HDU5772 String problem(最大权闭合子图)

题目..说了很多东西 官方题解是这么说的: 首先将点分为3类 第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于w[i][j]+w[j][i](表示得分) 第二类:原串中的n个点每个点拆出一个点,第i个点权值为 –a[s[i]] (表示要花费) 第三类:对于10种字符拆出10个点,每个点的权值为  -(b[x]-a[x]) 那么我们可以得到一个关系图 ,对于第一类中的点Pij,如果想要选择Pij,你就必须要选中第二类中的点i和j,对于第二类中的点如果你想选中第i个点,其对应的字

[BZOJ 1497][NOI 2006]最大获利(最大权闭合子图)

题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1497 分析: 这是在有向图中的问题,且边依赖于点,有向图中存在点.边之间的依赖关系可以考虑最大权闭合子图 假设a与b之间有权值为c的边(根据题意是双向边) 那么我们可以建一个新节点,点的权值为c,并指向a点和b点(单向),同时断掉原本a,b之间的双向边,a,b的点的权值是它们的花费(负的) 那么对于原问题就转化成了求最大权闭合子图的问题了 ——————————————————————

NOI2006 最大获利(最大权闭合子图)

codevs 1789 最大获利 2006年NOI全国竞赛 时间限制: 2 s 空间限制: 128000 KB 题目描述 Description 新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是 挑战.THU 集团旗下的 CS&T 通讯公司在新一代通讯技术血战的前夜,需要做 太多的准备工作,仅就站址选择一项,就需要完成前期市场研究.站址勘测.最 优化等项目. 在前期市场调查和站址勘测之后,公司得到了一共 N 个可以作为通讯信号中 转站的地址,而由于这些地址的地理位置差异,在不同

hiho一下 第119周 #1398 : 网络流五&#183;最大权闭合子图 【最小割-最大流--Ford-Fulkerson 与 Dinic 算法】

#1398 : 网络流五·最大权闭合子图 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 周末,小Hi和小Ho所在的班级决定举行一些班级建设活动. 根据周内的调查结果,小Hi和小Ho一共列出了N项不同的活动(编号1..N),第i项活动能够产生a[i]的活跃值. 班级一共有M名学生(编号1..M),邀请编号为i的同学来参加班级建设活动需要消耗b[i]的活跃值. 每项活动都需要某些学生在场才能够进行,若其中有任意一个学生没有被邀请,这项活动就没有办法进行. 班级建设的活

hdu 3996 Gold Mine 最大权闭合子图

Gold Mine Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2374    Accepted Submission(s): 514 Problem Description Long long ago, there is a gold mine.The mine consist of many layout, so some are

CF 103E Buying Sets 最大权闭合子图,匹配 难度:4

http://codeforces.com/problemset/problem/103/E 这道题首先一看就很像是最大权闭合子图,但是我们可以认为现在有两种点,数字和集合点,我们需要消除数字点的影响才能直接运用最大权闭合子图. 进行二分匹配,使得每个集合都唯一匹配一个数字,买下一个集合点,则意味着该集合中所有数字的对应匹配集合点都要被买下,也就是可以建立一个新图,其中某个集合点向对应数字代表的集合点连单向边,可以证明对于任意权闭合子图中的集合点,集合中所有数字的对应匹配集合点都已经在这个权闭合

HDU 3879 Base Station(最大权闭合子图)

经典例题,好像说可以转化成maxflow(n,n+m),暂时只可以勉强理解maxflow(n+m,n+m)的做法. 题意:输入n个点,m条边的无向图.点权为负,边权为正,输出最大权闭合子图的权值. (最大权闭合子图:图中各点的后继必然也在图中) 构图攻略:将边看做点(有的题边是没有权的,建模稍微有点不同), 对原本的边e[i](u,v,w)连3条边(S,n+i,w),(n+i,u,inf),(n+i,v,inf). 对原本的点v,连1条边(v,T,p[v]). 即正权点与源点连,负权点与汇点连.

HDU 5855 Less Time, More profit(最大权闭合子图)

题目链接:点击打开链接 思路: 最大权闭合子图的裸题,  给个学习资料:点击打开链接 当结点即有正权值又有负数权值时, 怎么求任意闭合子图的最大和呢?  只要求出最小割E, 用总的正数权值TOT 减去E就是答案. 细节参见代码: #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> #include<vector&