2016 Multi-University Training Contest 9题解报告

来源:互联网 发布:python phantomjs 编辑:程序博客网 时间:2024/06/06 16:33

此文章可以使用目录功能哟↑(点击上方[+])

倒数第2场多校,经历5个小时只出了一题,也是蛮心塞的...

链接→2016 Multi-University Training Contest 9

 Problem 1012 Less Time, More profit

Accept: 0    Submit: 0
Time Limit: 2000/1000 MS (Java/Others)    Memory Limit : 65536/65536 K (Java/Others)

 Problem Description

The city planners plan to build N plants in the city which has M shops.

Each shop needs products from some plants to make profit of proi units.

Building ith plant needs investment of payi units and it takes ti days.

Two or more plants can be built simultaneously, so that the time for building multiple plants is maximum of their periods(ti).

You should make a plan to make profit of at least L units in the shortest period.

 Input

First line contains T, a number of test cases.

For each test case, there are three integers N, M, L described above.

And there are N lines and each line contains two integers payi, ti(1<= i <= N).

Last there are M lines and for each line, first integer is proi, and there is an integer k and next k integers are index of plants which can produce material to make profit for the shop.

1 <= T <= 30

1 <= N, M <= 200

1≤L,ti≤1000000000

1≤payi,proi≤30000

 Output

For each test case, first line contains a line “Case #x: t p”, x is the number of the case, t is the shortest period and p is maximum profit in t hours. You should minimize t first and then maximize p.

If this plan is impossible, you should print “Case #x: impossible”

 Sample Input

2

1 1 2
1 5
3 1 1

1 1 3
1 5
3 1 1

 Sample Output

Case #1: 5 2
Case #2: impossible

 Problem Idea

解题思路:

【题意】
比赛的时候,这题提问的人还是蛮多的,所以我好好讲一下该题的题意

有n个工厂,m个商店,建设第i个工厂需要花费payi的钱和ti的时间,多个工厂可以同时建设,即花费时间取最大的ti

第i个商店在指定的k个工厂都建完之后可以一次性获得proi的利润

也就是说k个工厂任何一个没有建完的情况下,该商店都不会获得利润

问最少需要多少时间,获得的总利润不少于L

在满足最少时间的情况下,求最大化的利润


【类型】
二分+网络流(最大权闭合图)

【分析】
很尴尬的
,比赛的时候贪心水过了,还不小心误导了一批小伙伴,在此,我表示抱歉

首先,此题的变量太多了一些,比如我们不知道要建设哪些工厂,相对应的就不知道哪些商店能够获利,而且还不知道最少需要多少时间

所以,我们要做的第一步是先固定一些变量,就比如工厂建设工期t

故我们采取二分时间t,判断该时间下是否可以找到最大利润>=L

那如何判断是否有最大利润>=L?

在解决这个问题之前,我们先来了解一下,何为最大权闭合图

定义一个有向图G=(V,E)的闭合图(closure)是该有向图的一个点集,且该点集的所有出边都还指向该点集,即闭合图内的任意点的任意后继也一定在闭合图中

更形式化地说,闭合图是这样的一个点集V'∈V,满足对于∀uV'引出的<u,v>∈E,必有v∈V'成立

还有一种等价定义为:满足对于<u,v>∈E,若有uV'成立,必有v∈V'成立


就如上图而言,该图的闭合图(含空集):∅,{1,6},{6},{2,7,8,9,10},{7},{3,8},{8},{4,9},{9},{5,10},{10}等等

这个"等等"是因为闭合图相互组合也是闭合图,比如{1,3,6,8}

{2,7}不是闭合图,因为27以外的其他后继(8,9,10)不在该闭合图内

另外,最大权闭合图的含义就是点权之和最大的闭合图,上图最大权闭合图为{2,3,4,5,7,8,9,10},点权之和为10(100+10+10+10-30-30-30-30)

讲清楚最大权闭合图之后,我们来看看,此题和最大权闭合图有什么关系

在许多实际应用中,给出的有向图常常是一个有向无环图(DAG),闭合图的性质恰好反映了事件间的必要条件的关系:一个事件的发生,它所需要的所有前提也都要发生,而此题中的这种关系体现在何处呢?

例如100 4 2 3 4 5(某商店要获利100,必须在建有2、3、4、5号工厂的情况下),这整个图就构成了一个闭合图,如下所示


那么此题终于转化为求一个图的最大权闭合图(商店为正权点,权值为利润;工厂为负权点,权值为成本)

那么,对于下列例子,可转化为图G=(V,E)

1
5 5 10
20 1
30 1
30 1
30 1
30 1
10 1 1
100 4 2 3 4 5
10 1 3
10 1 4
10 1 5


对于最大权闭合图的题目,解法为:

将原图中每条有向边替换为容量为的有向边

增加连接源点s到原图每个正权点v(w>0)的有向边(本题的正权点恰好为所有商店),容量为w

增加连接原图每个负权点v(w<0)到汇点t的有向边(本题的负权点恰好为所有工厂),容量为-w


定义为任意一个大于的整数即可,此时,最大权=正权点的总权和-最小割

而我们又知道,最小割=最大流,所以此题就成了名副其实的最大流题

而至于为什么可以用这种方法求解最大权闭合图,可以看网上证明,此处为链接->算法合集之最小割模型在信息学竞赛中的应用》,证明在3.3

【时间复杂度&&优化】
O(v^2elogn)

题目链接→HDU 5855 Less Time, More profit

 Source Code

/*Sherlock and Watson and Adler*/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<stdio.h>#include<string.h>#include<stdlib.h>#include<queue>#include<stack>#include<math.h>#include<vector>#include<map>#include<set>#include<cmath>#include<complex>#include<string>#include<algorithm>#include<iostream>#define eps 1e-9#define LL long long#define bitnum(a) __builtin_popcount(a)using namespace std;const int N = 205;const int M = 2001;const int inf = 1000000007;const int mod = 1000000007;struct plants{    int pay,t;}s[N];//pay为建设工厂所需要的费用,t为建设工厂所需要的时间struct shop{    int pro,k,index[N];}w[N];//pro为商店可获利润struct Edge{    int v,cap,next;}edge[2*N*N];int level[2*N];//标记层次(距离标号)//间隙优化,定义gap[i]为标号是i的点的个数//在重标记i时,检查gap[level[i]],若减为0,这算法结束。int gap[2*N];int pre[2*N];//前驱int cur[2*N];int head[2*N];int NV,p,n,m,L;//p为边数,初始化为0;void add_edge(int u,int v,int cap){    edge[p].cap=cap;edge[p].v=v;    edge[p].next=head[u];head[u]=p++;    edge[p].cap=0;edge[p].v=u;    edge[p].next=head[v];head[v]=p++;}//参数,源点,汇点int SAP(int vs,int vt,int n){    bool flag;    memset(level,0,sizeof(level));    memset(pre,-1,sizeof(pre));    memset(gap,0,sizeof(gap));    //cur[i]保存的是当前弧    for(int i=0;i<=NV;i++)        cur[i]=head[i];    int u=pre[vs]=vs;//源点的pre还是其本身    int maxflow=0,aug=-1;    gap[0]=n;    while(level[vs]<NV)    {        flag=true;        for(int &i=cur[u];i!=-1;i=edge[i].next)        {            int v=edge[i].v;//v是u的后继            //寻找可行弧            if(edge[i].cap&&level[u]==level[v]+1)            {                //aug表示增广路的可改进量                aug==-1?(aug=edge[i].cap):(aug=min(aug,edge[i].cap));                pre[v]=u;                u=v;                //如果找到一条增广路                if(v==vt)                {                    maxflow+=aug;//更新最大流;                    //路径回溯更新残留网络                    for(u=pre[v];v!=vs;v=u,u=pre[u])                    {                        //前向弧容量减少,反向弧容量增加                        edge[cur[u]].cap-=aug;                        edge[cur[u]^1].cap+=aug;                    }                    aug=-1;                }                flag=false;                break;            }        }        if(!flag)            continue;        int minlevel=n;        //寻找与当前点相连接的点中最小的距离标号(重标号)        for(int i=head[u];i!=-1;i=edge[i].next)        {            int v=edge[i].v;            if(edge[i].cap&&minlevel>level[v])            {                cur[u]=i;//保存弧                minlevel=level[v];            }        }        if((--gap[level[u]])==0)            break;//更新gap数组后如果出现断层,则直接退出。        level[u]=minlevel+1;//重标号        gap[level[u]]++;//距离标号为level[u]的点的个数+1;        u=pre[u];//转当前点的前驱节点继续寻找可行弧    }    return maxflow;}int judge(int t){    int i,j,ans,sum=0;    bool flag;    p=0;    memset(head,-1,sizeof(head));    for(i=1;i<=m;i++)    {        flag=true;        for(j=1;j<=w[i].k;j++)            if(s[w[i].index[j]].t>t)            {                flag=false;                break;            }        if(!flag)            continue;        add_edge(1,1+i,w[i].pro);        sum+=w[i].pro;        for(j=1;j<=w[i].k;j++)            add_edge(1+i,w[i].index[j]+m+1,inf);    }    for(i=1;i<=n;i++)        if(s[i].t<=t)            add_edge(m+1+i,m+n+2,s[i].pay);    ans=sum-SAP(1,m+n+2,m+n+2);    if(ans<L)        return 0;    return ans;}int main(){    int t,i,j,l,r,mid,cas=1,ans,Min,temp;    scanf("%d",&t);    while(t--)    {        Min=inf;        scanf("%d%d%d",&n,&m,&L);        NV=n+m+2;        for(i=1;i<=n;i++)            scanf("%d%d",&s[i].pay,&s[i].t);        for(i=1;i<=m;i++)        {            scanf("%d%d",&w[i].pro,&w[i].k);            for(j=1;j<=w[i].k;j++)                scanf("%d",&w[i].index[j]);        }        l=1;r=1000000000;        while(l<=r)//二分时间t        {            mid=(l+r)/2;            if(temp=judge(mid))//判是否存在最大利润>=L                ans=temp,r=mid-1,Min=min(Min,mid);            else                l=mid+1;        }        if(Min!=inf)            printf("Case #%d: %d %d\n",cas++,Min,ans);        else            printf("Case #%d: impossible\n",cas++);    }    return 0;}

菜鸟成长记

1 0