标签:结束 cout strong image order bsp get clu 费用流
最后一场比赛也结束了……
有n个人以及m部电影,每个人都有一个快乐值。每场电影都有它的开始、结束时间和看了这部电影会得到的快乐值。电影分成两种类型,若同一个人连续(不是时间连续,是顺序连续)看了两部相同类型的电影,他的快乐值会扣除W,数据保证扣除的值不超过电影增加的快乐值。
特别的,一个人换电影不花费时间,即若第一部电影的结束时间等于下一部电影的开始时间,是可以两场都看的;看电影必须看完;一部电影只能一个人看。
输入包含多组数据,第一行为整数T表示数据组数。
每组数据第一行包含四个整数t,m,n,W,t表示电影结束的最晚时间不超过t,m表示电影的数量,n表示人的数量,W表示连续看相同类型的电影扣除的快乐值;接下来m行,每行描述一个电影,包含四个整数——s[i]、e[i]表示第i部电影的开始和结束时间,w[i]表示看第i部电影得到的快乐值,k[i]表示电影的类型,为0或1。
输出所有人的快乐值之和的最大值。
| Input | Output | Explain |
|
2 |
2000 1990 |
第一组数据只有一个人,依次看了 第1,2部电影; 第二组数据只有一个人,依次看了 第1,2部电影,但类型相同,扣除 10; |
这道题是一道网络流的题……其中网络流的部分是队友不知道哪里找来的版,就不解释了QwQ
由于网络流的最大流无法处理多个人的情况,我们使用费用流,那么思路就非常清晰了——网络流中“流”的是人的个数,而费用就是每部电影的快乐值;
也就是说我们要求一个最大费用费用流,其实可以将所有边的费用取相反数,然后跑最小费用就可以了??
唯一难的就是建图。下面就直接列出建图方法了:
① 总共有n个人,为了避免一个人同时看了两部电影,我们先建立n个点每个点表示一个人,连接超级源点,容量为1(一个人),费用为0;
② 总共m部电影,一个人可以从任何一个电影开始看,所以建立m个节点,将每一部电影都跟所有的人连接,容量为1,费用为电影的快乐值(走过这条边就会增加快乐值,相当于看了这部电影,且限制了看电影的人数);
③ 若第i部电影的结束时间小于等于第j部电影的结束时间,则在第i部电影和第j部电影之间连边,容量依然为1,费用为第j部电影的快乐值,若电影i,j的类型相同,边的费用减去W(看完第i部电影再看第j部);
④ 由于一个人可以看完一部电影就不看了,即可以从任何一部电影结束,所以将所有电影与超级汇点连边;没有必要在人与汇点连边,因为看一部电影始终优于不看,则限制每个人都要看电影。
但是交上去就WA了,后面一个dalao来检查了一下~发现了一个BUG:
虽然有边的容量限制人数,但是下面这种情况会出现两个人看了同一部电影:
如何解决这种问题?
根据以前做题的经验(好吧,其实是dalao直接告诉我们的)我们需要拆点——将每一个电影节点拆分出一个虚拟节点,真节点与虚拟节点之间连一条容量为1,花费为0的边,所有以电影i为末尾的边都连在真节点上,而以电影i出发的边都连在虚拟节点上——只要经过电影i,则必然要通过真节点和虚拟节点的边,这样就限制了一个人通过。
虽然话是这么说,但是实际上建边时,边的花费我都取了相反数,这样就能够用跑最小费用流代替最大费用流,有负权边,注意选择合适的方法。
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
/*以下均是模板*/
const int N = 50002;
const int M = 500005;
#define INF 0x3f3f3f3f
struct E
{
int to,cap,cost,flow,next;
}e[2*M];int head[N] , ecnt;
int pre[N];
int dis[N];
bool vis[N];
int n,m,S,T;
void Clear()
{
ecnt = 0;
memset(head,-1,sizeof head);
}
void adde(int fr,int to,int cap,int cost)
{
e[ecnt]=(E){to,cap,-cost,0,head[fr]};
head[fr] = ecnt++;
e[ecnt]=(E){fr,0,cost,0,head[to]};
head[to] = ecnt++;
}
bool SPFA(int s,int t)
{
memset(vis,0,sizeof vis);
memset(dis,0x3f,sizeof dis);
memset(pre,-1,sizeof pre);
queue <int> q;
q.push(s);dis[s] = 0;vis[s]=1;
while (!q.empty())
{
int cur = q.front();q.pop();vis[cur] = false;
for (int j=head[cur];j!=-1;j=e[j].next)
{
int to = e[j].to;
if (dis[to] > dis[cur] + e[j].cost && e[j].cap > e[j].flow )
{
dis[to] = dis[cur] + e[j].cost;
pre[to] = j;
if (!vis[to])
{
q.push(to);
vis[to] = true;
}
}
}
}
return pre[t] != -1;
}
void MCMF (int s,int t,int &maxflow,int &mincost)
{
maxflow = mincost = 0;
while (SPFA(s,t))
{
int MIN = INF;
for (int j=pre[t]; j!=-1;j=pre[e[j^1].to])
{
MIN = min(MIN,e[j].cap - e[j].flow);
}
for (int j=pre[t]; j!=-1;j=pre[e[j^1].to])
{
e[j].flow += MIN;
e[j^1].flow -= MIN;
mincost += MIN * e[j].cost;
}
maxflow += MIN;
}
}
/*模板结束*/
#define MAXN 3000
int L[MAXN+5],R[MAXN+5],Lk[MAXN+5],Kd[MAXN+5];
int main(){
int TT;
cin>>TT;//数据组数
while(TT--){
Clear();//清空
int nn,mm,kk,ht;
cin>>nn>>mm>>kk>>ht;
for(int i=1;i<=mm;i++)
cin>>L[i]>>R[i]>>Lk[i]>>Kd[i];
S=1,T=kk+mm*2+2;
//超级源点为1,超级汇点为最后一个点(因为有kk个人节点,mm*2个电影节点,即真节点和虚拟节点,再加上一个源点)
for(int i=1;i<=kk;i++)
adde(S,i+1,1,0); //在人节点和源点之间连边,第i个人编号为i+1
for(int i=1;i<=kk;i++)
for(int j=1;j<=mm;j++)
adde(i+1,j+kk+1,1,Lk[j]); //在人和电影的真节点之间连边,第i部电影真节点编号为i+kk+1
for(int i=1;i<=mm;i++)
adde(i+kk+1,i+kk+mm+1,1,0); //连接真节点和虚拟节点,第i部电影的虚拟节点编号为i+kk+mm+1
for(int i=1;i<=mm;i++)
adde(i+kk+mm+1,T,1,0); //连接虚拟节点和汇点
for(int i=1;i<=mm;i++)
for(int j=1;j<=mm;j++)
if(i!=j&&R[i]<=L[j]){ //电影之间连边,虚拟节点连真节点
if(Kd[i]!=Kd[j])
adde(i+kk+mm+1,j+kk+1,1,Lk[j]);
else
adde(i+kk+mm+1,j+kk+1,1,Lk[j]-ht);
}
int ans1,ans2;
MCMF(S,T,ans1,ans2);
cout<<-ans2<<"\n"; //由于边权取了相反数,输出答案时也需要取相反数
}
}
- Lucky_Glass
标签:结束 cout strong image order bsp get clu 费用流
原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/9519886.html