• FZU


    题目链接

    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
    ", max_val);
        }
        return 0;
    }
    FZU 2295
  • 相关阅读:
    蚂蚁
    N的阶乘
    最小公倍数LCM
    最大公约数GCD
    Truck History(卡车历史)
    亲戚
    [SDOI2011]打地鼠
    连续自然数和
    P4250 [SCOI2015]小凸想跑步
    P4048 【[JSOI2010]冷冻波】
  • 原文地址:https://www.cnblogs.com/winter-bamboo/p/11332667.html
Copyright © 2020-2023  润新知