[JZOJ4682] 【GDOI2017模拟8.11】生物学家
题目
描述
题目大意
有一个序列,可以改变状态,每个状态改变都有固定的代价。
接下来有些人想要将一些位置改成特定的状态,如果按照他们要求做了就可以得到一些钱,
否则得不到,有时还要陪钱。
问最后的钱最多是多少。
思考历程
看到这题的第一眼就觉得是一道神题。
只能想到最恐怖的暴力算法……
这题肯定不可以DP,那么就想想贪心和网络流。
觉得这题做法一定是贪心,因为有这么大,肯定不是网络流啊!
到最后还是没有想出来。
正解
正解早就被我否定了!(真香)
这题的正解就是网络流啊。
接下来考虑怎么建图,按照传统套路:
建立源点和汇点,若状态为,则向它连一条边,否则它向连一条边。
容量为它们的代价(意味着如果要割掉这条边就要付出这么多代价)
对于每个人(假设他要求将状态变为0),建一个点(记为)
首先向连一条边,容量为收入(如果有赔钱就讲赔钱加上)。接着向每个要改变的点连一条容量为无限大的边。
反之同理。
然后跑一遍最小割。答案一开始加上所有收入,减去最大流。
接着分析一下这为什么是对的。
向连一条边,如果的要求不能达到,那么必然有一个要改变的点连向了。
所以向连的这一条边会被割掉。
实际上我们可以看成是点对那些点的限制,如果它们和为,那就不用被割,否则或那个点到之间一定会被割掉一条。
这题网络流可以过……这是最不可思议的地方……我跑了20ms……
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 10010
#define M 2010
#define INF 2147483647
int n,m,g;
int sex[N];
struct EDGE{
int to,c;
EDGE *las;
} e[(N+M*10+M)*2];
int ne=-1;
EDGE *last[N+M+2],*cur[N+M+2];
inline void link(int u,int v,int c){
e[++ne]={v,c,last[u]};
last[u]=e+ne;
}
#define rev(ei) (e+(int(ei-e)^1))
int cnt,S,T;
int ans;
int gap[N+M+2],h[N+M+2];
bool BZ;
int dfs(int x,int s){
if (x==T)
return s;
int have=s;
for (EDGE *ei=cur[x];ei;ei=ei->las){
cur[x]=ei;
if (ei->c && h[ei->to]+1==h[x]){
int t=dfs(ei->to,min(ei->c,have));
ei->c-=t,rev(ei)->c+=t,have-=t;
if (!have)
return s;
}
}
cur[x]=last[x];
if (!--gap[h[x]])
BZ=0;
h[x]++;
gap[h[x]]++;
return s-have;
}
inline int flow(){
int res=0;
gap[0]=cnt;
memset(h,0,sizeof h);
BZ=1;
while (BZ)
res+=dfs(S,INF);
return res;
}
int main(){
scanf("%d%d%d",&n,&m,&g);
cnt=n;S=++cnt;T=++cnt;
for (int i=1;i<=n;++i)
scanf("%d",&sex[i]);
for (int i=1;i<=n;++i){
int w;
scanf("%d",&w);
if (sex[i]==0)
link(S,i,w),link(i,S,0);
else
link(i,T,w),link(T,i,0);
}
for (int i=1;i<=m;++i){
int ns,val,k;
scanf("%d%d%d",&ns,&val,&k);
ans+=val;
++cnt;
if (ns==0){
while (k--){
int x;
scanf("%d",&x);
link(cnt,x,INF),link(x,cnt,0);
}
int gf;
scanf("%d",&gf);
link(S,cnt,val+gf*g),link(cnt,S,0);
}
else{
while (k--){
int x;
scanf("%d",&x);
link(x,cnt,INF),link(cnt,x,0);
}
int gf;
scanf("%d",&gf);
link(cnt,T,val+gf*g),link(T,cnt,0);
}
}
ans=ans-flow();
printf("%d\n",ans);
return 0;
}
总结
见到一道题,应该要想到DP、贪心、网络流。
对于网络流,我以前都会分析一下时间复杂度,再看看能不能用网络流。
可我发现我错了,原来网络流的时间复杂度一直都是玄学。
因此以后我要改变一下策略:
见到不会做的题目就用网络流!