欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-28 19:02:28
题目链接 FZU - 2295 Human life 题目分析 题意:你在玩一个游戏,在其中你可以通过学习一些技能,但是学习某些技能之前,可能还要学习一些其他的技能,并且学习任何技能都有一定的花费; 而我们可以通过掌握某些工作以获取报酬,为了掌握这一工作,我们必须学会特定的技能。 不过有些工作彼此之 ......

题目链接

fzu - 2295 human life

题目分析

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

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

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

思路:

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

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

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

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

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

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

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

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

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

我这个语法写了近一年了,从未出错,这个评测机第一次把我这里卡了,一直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;
}