浏览器标题切换
浏览器标题切换end

寒假Day13:最小费用最大流问题(变形) POJ3680-Intervals(最大费用最大流)

POJ3680-Intervals(最大费用最大流)

题面:

You are given N weighted open intervals. The ith interval covers (ai, bi) and weighs wi. Your task is to pick some of the intervals to maximize the total weights under the limit that no point in the real axis is covered more than k times.

Input
The first line of input is the number of test case.
The first line of each test case contains two integers, N and K (1 ≤ K ≤ N ≤ 200).
The next N line each contain three integers ai, bi, wi(1 ≤ ai < bi ≤ 100,000, 1 ≤ wi ≤ 100,000) describing the intervals.
There is a blank line before each test case.

Output
For each test case output the maximum total weights in a separate line.
View Code

 

样例:

Sample Input
4

3 1
1 2 2
2 3 4
3 4 8

3 1
1 3 2
2 3 4
3 4 8

3 1
1 100000 100000
1 2 3
100 200 300

3 2
1 100000 100000
1 150 301
100 200 300

Sample Output
14
12
100000
100301

 

题意:

t 组数据,每组数据:给出n个开区间和对应的权值k,选出一些区间,权值和范围尽可能大,满足任意一个点被覆盖小于等于k次。

 

思路:

(重点在于如何建图连边。)(好复杂)

  1. 对所有的点进行排序去重;
  2. 离散化所有的点;(这一题开区间a、b的数据范围是1-100000,比较大,但是用不到全部,所以需要对数轴进行离散化。)
  3. 设:源点s=0,汇点t=n+1=p+1;
  4. 对于相邻的边(包括源点向第一个坐标点、最后一个坐标点向汇点),连流量k,费用0的边。(为什么:因为有些点之间没有联系,我们要让它们都产生联系才能便于建出整幅图,否则样例第二组和第四组错误,所以要建费用为0的边;为什么流量为k?因为根据题意,要在小于等于k的范围之内。)
  5. 对于每一个区间[u,v],连流量为1,费用为-w的边;(因为求的是最大费用最大流,所以要先去负数,求出最小费用,之后取反得到我们所需的最大费用(最大全权值))。(但是我不清楚为什么流量为1???)
  6. 最后跑一次MCMF;
  7. 以数轴最左端的点作为源点,跑最小费用流,把得到的最小花费取反,即为最大权值;

 

注意:

  1. 这一题邻接表开的范围四倍即可过,SPFA的dist数组和book数组需要开到两倍大小(因为n是代表区间个数,离散化之后点的个数需要乘以2)
  2. 题目求的是最大费用最大流
  3. (给自己的注意事项)
int tt;
scanf("%d",&tt);//几组数据的t和汇点t变量写写清楚啊大哥!!!

 

这个是我看的视频比较好的思路解析截图:

 

具体代码实现:

  • 需要用到的数组:

邻接表需要开到四倍大小、建图需要N*N大小,存点的范围需要开到N*2大小(原因上面说过,dist和book同理)

struct node
{
    int to,nextt;
    int cap,flow,cost;//容量、流量、费用

}e[N*N];//建图的范围

int dist[N<<1];//源点s->i的最短路径
bool book[N<<1];//表示该点是否在队列
int pre[4*N],head[4*N],tot,n,k,s,t;
int a[N<<1],id[100010],l[N],r[N],w[N];

 

  • 离散化编号:
for(int i=1; i<=p; i++)
    id[a[i]]=i;

 

  • 邻接表建边:(不知道可不可以vector建边???反正我改不来)
void add(int u,int v,int cap,int cost)
{
    e[tot].to=v;
    e[tot].cap=cap;
    e[tot].cost=cost;
    e[tot].flow=0;
    e[tot].nextt=head[u];

    head[u]=tot++;
    e[tot].to=u;
    e[tot].cap=0;
    e[tot].cost=-cost;
    e[tot].flow=0;
    e[tot].nextt=head[v];
    head[v]=tot++;

}

 

  • SPFA:(上一篇SPFA的基础上改动一点点)
bool SPFA()
{
    for(int i=0; i<=t; i++)
    {
        dist[i]=inf;
        book[i]=0;
        pre[i]=-1;
    }
    book[s]=1;
    dist[s]=0;//源点到自身距离为0
    queue<int>Q;
    Q.push(s);//源点入队
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        book[u]=0;
        for(int i=head[u]; i!=-1; i=e[i].nextt)
        {
            int v=e[i].to;
            if(e[i].cap>e[i].flow&&dist[v]>dist[u]+e[i].cost)
            {
                dist[v]=dist[u]+e[i].cost;
                pre[v]=i;    //记录点的指向:指向上一条边;  目的:便于MAMF的不断回溯,直至指向==-1
                if(book[v]==0)
                {
                    book[v]=1;
                    Q.push(v);
                }
            }
        }
    }
    if(dist[t]!=inf)//pre[t]!=-1也ok
        return 1;
    return 0;
}

 

  • 最最核心代码:

    MCMF算法(模板)

//最大流flow,最小费用cost
//看清楚题目求的是什么,再返回什么
//干脆定义全局变量也ok
int MCMF()
{
    int flow=0,cost=0;
    while(SPFA())//源点到汇点的一条最短路即可行流,不断的找这样的可行流
    {
        int minn=inf;
        for(int i=pre[t]; i!=-1; i=pre[e[i^1].to])
            minn=min(minn,e[i].cap-e[i].flow);
        for(int i=pre[t]; i!=-1; i=pre[e[i^1].to])
        {
            e[i].flow+=minn;
            e[i^1].flow-=minn;
            cost+=e[i].cost*minn; //单位流量费用*流量
        }
        flow+=minn;
    }
   return cost;
}

 

  • 部分主函数:

这三个记得进行初始化:

int tot=0;
int p=0;
memset(head,-1,sizeof(head));

 

 

POJ3608如何建边的代码实现

for(int i=1; i<=n; i++)
{
    scanf("%d %d %d",&l[i],&r[i],&w[i]);
    a[++p]=l[i];
    a[++p]=r[i];
}
sort(a+1,a+1+p);
p=unique(a+1,a+1+p)-(a+1);//对所有的点进行排序去重

for(int i=1; i<=p; i++)
    id[a[i]]=i;//离散化所有的点
    
s=0,t=p+1; //源点s=0,汇点t=n+1=p+1

for(int i=0; i<=p; i++)//包括源点向第一个坐标点、最后一个坐标点向汇点
    add(i,i+1,k,0);//对于相邻的边,连容量k,费用0的边。
    
for(int i=1; i<=n; i++)
    add(id[l[i]],id[r[i]],1,-w[i]);//对于每一个区间[u,v],连容量为1,费用为-w的边
    
int cost=MCMF()*(-1);//最后汇点向源点跑一次MCMF
printf("%d\n",cost);
//以数轴最左端的点作为源点,跑最小费用流,把得到的最小花费取反,即为最大权值

 

 

 

AC代码:

  1 #include<string.h>
  2 #include<iostream>
  3 #include<stdio.h>
  4 #include<algorithm>
  5 #include<queue>
  6 #include<vector>
  7 #include<map>
  8 #include<cmath>
  9 using namespace std;
 10 #define inf 0x3f3f3f3f
 11 const int N=220;
 12 typedef long long ll;
 13 
 14 struct node
 15 {
 16     int to,nextt;
 17     int cap,flow,cost;
 18 
 19 }e[N*N];
 20 
 21 int dist[N<<1];
 22 bool book[N<<1];
 23 int pre[4*N],head[4*N],tot,n,k,s,t;
 24 int a[N<<1],id[100020],l[N],r[N],w[N];
 25 
 26 void add(int u,int v,int cap,int cost)
 27 {
 28     e[tot].to=v;
 29     e[tot].cap=cap;
 30     e[tot].cost=cost;
 31     e[tot].flow=0;
 32     e[tot].nextt=head[u];
 33 
 34     head[u]=tot++;
 35     e[tot].to=u;
 36     e[tot].cap=0;
 37     e[tot].cost=-cost;
 38     e[tot].flow=0;
 39     e[tot].nextt=head[v];
 40     head[v]=tot++;
 41 }
 42 
 43 bool SPFA()
 44 {
 45     for(int i=0; i<=t; i++)
 46     {
 47         dist[i]=inf;
 48         book[i]=0;
 49         pre[i]=-1;
 50     }
 51     book[s]=1;
 52     dist[s]=0;
 53     queue<int>Q;
 54     Q.push(s);
 55     while(!Q.empty())
 56     {
 57         int u=Q.front();
 58         Q.pop();
 59         book[u]=0;
 60         for(int i=head[u]; i!=-1; i=e[i].nextt)
 61         {
 62             int v=e[i].to;
 63             if(e[i].cap>e[i].flow&&dist[v]>dist[u]+e[i].cost)
 64             {
 65                 dist[v]=dist[u]+e[i].cost;
 66                 pre[v]=i;
 67                 if(book[v]==0)
 68                 {
 69                     book[v]=1;
 70                     Q.push(v);
 71                 }
 72             }
 73         }
 74     }
 75     if(dist[t]!=inf)
 76         return 1;
 77     return 0;
 78 }
 79 
 80 int MCMF()
 81 {
 82     int flow=0,cost=0;
 83     while(SPFA())
 84     {
 85         int minn=inf;
 86         for(int i=pre[t]; i!=-1; i=pre[e[i^1].to])
 87             minn=min(minn,e[i].cap-e[i].flow);
 88         for(int i=pre[t]; i!=-1; i=pre[e[i^1].to])
 89         {
 90             e[i].flow+=minn;
 91             e[i^1].flow-=minn;
 92             cost+=e[i].cost*minn;
 93         }
 94         flow+=minn;
 95     }
 96    return cost;
 97 }
 98 
 99 int main()
100 {
101     int tt;
102     scanf("%d",&tt);
103     while(tt--)
104     {
105         scanf("%d %d",&n,&k);
106         tot=0;
107         memset(head,-1,sizeof(head));
108         int p=0;
109         for(int i=1; i<=n; i++)
110         {
111             scanf("%d %d %d",&l[i],&r[i],&w[i]);
112             a[++p]=l[i];
113             a[++p]=r[i];
114         }
115         sort(a+1,a+1+p);
116         p=unique(a+1,a+1+p)-(a+1);
117         for(int i=1; i<=p; i++)
118             id[a[i]]=i;
119         s=0,t=p+1;
120         for(int i=0; i<=p; i++)
121             add(i,i+1,k,0);
122         for(int i=1; i<=n; i++)
123             add(id[l[i]],id[r[i]],1,-w[i]);
124         int cost=MCMF()*(-1);
125         printf("%d\n",cost);
126     }
127     return 0;
128 }
View Code

 

参考博客:

kuangbin的代码清晰,可是没有具体解释,主函数那一部分我没看懂。。。 https://www.cnblogs.com/kuangbin/p/3236051.html

这篇博客思路清晰代码好懂,也便于改成自己的模板来用:https://blog.csdn.net/qq_26564523/article/details/50568990

 

两篇博客综合起来学习,再借鉴一下一些写的清楚易懂的代码和思路,然后写成自己的代码就可以啦。要学会把别人的代码改成自己理解的样子。

重点是一定要理解呀!!!不懂不要写!!!

说白了,代码不是说技巧用的越多越好,反而是自己理解的最明白的代码才是属于自己的代码,才是对于个人而言的一份好代码。

因为现在个人的能力就摆在那,看不懂复杂的代码也就不要去套,然后到最后什么也没搞懂。

总而言之,得只能作为参考,大概理解的差不多自己尝试着写写,OJ上测一下一些数据,才能理解的更到位。


 

待解决:

  • 网络流的写法有一种异或边的形式进行正向边和反向边的存储,可是我不知道怎么使用???
  • POJ3680是否能通过vector的形式进行边的存储建图???(邻接表我不太懂,用起来不是很顺手)
  • POJ3680 也就是以上我的代码id开的范围必须是边的max,为什么不能是n*2,n*2会超时,但是比边的最大范围小一点又是RT???
  • POJ3680 不明白为什么要如此建边,建边的原理是什么???不知道我自己在上面解释的是否正确???
posted @ 2020-01-28 23:51  抓水母的派大星  阅读(259)  评论(0编辑  收藏  举报