最大权闭合子图
概念
模板题一
poj 2987(http://poj.org/problem?id=2987)
题目大意
某公司想要裁员,裁员的标准是如果某人被裁,那么其下属也会被裁,依此类推,每一个人都有一个贡献度,如果为正,裁掉会增加公司的收益,如果为负,裁掉会减少公司的收益,问怎样裁员才能使得最后的贡献度最大并且裁掉人数最少?
Sample Input
5 5
8
-9
-20
12
-10
1 2
2 5
1 4
3 4
4 5
Sample Output
2 2
思路
1.先记录整个图中,所有正点权值的和;
2.建立对应流网络,求最大流,最大流在数值上等于最小割,故我们得到了流网络的s-t最小割;
3.“所有正点权值的和”减去“s-t最小割”,即得最大权闭合子图的权值和。
4.最小割图的点就是割去的点数
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL maxn=1e5+10;
const LL maxm=2e5+10;
struct E
{
LL v; //每一条边指向的点
LL next;//指向对应点的前一条边
LL w; //每一条边的残量
}e[maxm];
LL s, t;//源点和汇点
LL cut;//边的数量,从0开始编号
LL head[maxm];//每一个点最后一条边的编号
LL d[maxn];//分层图中标记深度
LL inf=(1ll<<60);
LL cur[maxn];//cur就是记录当前点u循环到了哪一条边
LL n, m;
void init()
{
cut=-1;
memset(head, -1, sizeof(head));
}
void addEdge(LL u, LL v, LL w)
{
cut++;
e[cut].next=head[u];
e[cut].v=v;
e[cut].w=w;
head[u]=cut;
}
void add(LL u, LL v, LL w)
{
addEdge(u, v, w);
addEdge(v, u, 0);
}
LL bfs()
{
queue<LL> q;
while(!q.empty()) {
q.pop();
}
memset(d, 0, sizeof(d));
d[s]=1;//源点深度为1
q.push(s);
while(!q.empty()) {
LL u=q.front();
q.pop();
for(LL i=head[u];i!=-1;i=e[i].next) {
LL v=e[i].v, w=e[i].w;
if(w>0&&d[v]==0)//若该残量不为0,且V[i]还未分配深度,则给其分配深度并放入队列
{
d[v]=d[u]+1;
q.push(v);
}
}
}
if(d[t]==0)//当汇点的深度不存在时,说明不存在分层图,同时也说明不存在增广路
{
return 0;
}
return 1;
}
LL dfs(LL u, LL dis)//u是当前节点,dist是当前流量
{
if(u==t) {
return dis;//当已经到达汇点,直接返回
}
for(LL &i=cur[u];i!=-1;i=e[i].next) {
LL v=e[i].v, w=e[i].w;
if((d[v]==d[u]+1)&&w!=0)//注意这里要满足分层图和残量不为0两个条件
{
LL di=dfs(v, min(dis, w));//向下增广
if(di>0)//若增广成功
{
e[i].w-=di;//正向边减
e[i^1].w+=di;//反向边加
return di;//向上传递
}
}
}
return 0;//否则说明没有增广路,返回0
}
LL Dinic() {
LL ans=0;//记录最大流量
while (bfs())
{
/*******************************/
for(LL i=s;i<=t;i++)//每一次建立完分层图后都要把cur置为每一个点的第一条边
{
cur[i]=head[i];
}
/********************************/
while (LL d=dfs(s,inf)) {
ans+=d;
}
}
return ans;
}
queue<int> q;
LL minzxg(){//不在最大权闭合子图的点数
LL ans=0;
memset(d, 0, sizeof(d));
q.push(s);
d[s]=1;
while(!q.empty()){
LL u=q.front();
q.pop();
ans++;
for(LL i=head[u];i!=-1;i=e[i].next){
LL v=e[i].v, w=e[i].w;
if(d[v]==0&&w>0){
d[v]=1;
q.push(v);
}
}
}
return ans-1;//-源点
}
int main()
{
while(~scanf("%lld%lld",&n,&m))
{
init();
s=0, t=n+1;
LL u, v, w, ans=0;
for(int i=1; i<=n; i++){
scanf("%lld", &w);
if(w>=0){
ans+=w;
add(s, i, w);
}
else{
add(i, t, -w);
}
}
for(int i=0;i<m;i++) {
LL u, v;
scanf("%lld%lld",&u,&v);
add(u, v, inf);
}
LL ds=Dinic();
printf("%lld %lld\n",minzxg(), ans-ds);
}
return 0;
}
模板题二
勤奋的杨老师(二)(https://ac.nowcoder.com/acm/problem/15828)
题目描述
众所周知,杨老师是一位十分勤奋的老师,他非常的热爱学习。
勤奋的他为自己罗列了一个学习清单,共有n个知识点,他可以有选择的进行学习。
每个知识点都会对应0个或1个或多个先修知识点(只有学会了先修知识点才能学习该知识点),同时每个知识点都有一个智慧值和一个智力消耗值。
杨老师希望在进行过激烈的学习之后,他的收获可以·量化为所有学过的题的智慧值的和与智力消耗值的和的差值。请问,这个值最大是多少?
输入描述:
第一行:一个整数n(n<=500)接下来n行,每行两个整数,代表第i个知识点的智慧值和智力消耗值接下来若干行,每行2个整数u, v,代表u是v的先修知识点。
输出描述:
一行,表示杨老师的收获的最大值
输入
4
5 1
2 1
1 2
1 2
3 1
2 4
2 1
输出
4
思路
把智慧值-智力消耗值就是点权了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<string>
#include<iostream>
#define LL long long
#define INF 1e9
using namespace std;
const LL maxn =600+10;
struct Edge {
LL from,to,cap,flow;
Edge() {}
Edge(LL f,LL t,LL c,LL fl):from(f),to(t),cap(c),flow(fl) {}
};
struct Dinic {
LL n,m,s,t;
vector<Edge> edges;
vector<LL> G[maxn];
LL cur[maxn];
LL d[maxn];
bool vis[maxn];
void init(LL n,LL s,LL t) {
this->n=n, this->s=s, this->t=t;
edges.clear();
for(LL i=0; i<n; i++)
G[i].clear();
}
void AddEdge(LL from,LL to,LL cap) {
edges.push_back( Edge(from,to,cap,0) );
edges.push_back( Edge(to,from,0,0) );
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BFS() {
queue<LL> Q;
Q.push(s);
memset(vis,0,sizeof(vis));
d[s]=0;
vis[s]=true;
while(!Q.empty()) {
LL x=Q.front();
Q.pop();
for(LL i=0; i<G[x].size(); ++i) {
Edge& e=edges[G[x][i]];
if(!vis[e.to] && e.cap>e.flow) {
d[e.to]=1+d[x];
vis[e.to]=true;
Q.push(e.to);
}
}
}
return vis[t];
}
LL DFS(LL x,LL a) {
if(x==t || a==0)
return a;
LL flow=0,f;
for(LL& i=cur[x]; i<G[x].size(); ++i) {
Edge& e=edges[G[x][i]];
if(d[e.to]==d[x]+1 && (f=DFS(e.to,min(a,e.cap-e.flow) ) )>0) {
e.flow +=f;
edges[G[x][i]^1].flow -=f;
flow +=f;
a-=f;
if(a==0)
break;
}
}
return flow;
}
LL max_flow() {
LL ans=0;
while(BFS()) {
memset(cur,0,sizeof(cur));
ans += DFS(s,INF);
}
return ans;
}
} DC;
LL w[510];
int main() {
LL n; scanf("%lld", &n);
LL s=0, t=505;
DC.init(t+10, s, t);
LL sum=0;
LL x, y;
for(LL i=1; i<=n; i++){
scanf("%lld%lld",&x, &y);
w[i]=x-y;
if(w[i]>0){
sum+=w[i];
DC.AddEdge(s, i, w[i]);
}
else{
DC.AddEdge(i, t, -w[i]);
}
}
while(~scanf("%lld%lld",&x, &y)){
DC.AddEdge(y, x, INF);
}
printf("%lld\n", sum-DC.max_flow());
return 0;
}
二分图的模型
描述
周末,小Hi和小Ho所在的班级决定举行一些班级建设活动。
根据周内的调查结果,小Hi和小Ho一共列出了N项不同的活动(编号1..N),第i项活动能够产生a[i]的活跃值。
班级一共有M名学生(编号1..M),邀请编号为i的同学来参加班级建设活动需要消耗b[i]的活跃值。
每项活动都需要某些学生在场才能够进行,若其中有任意一个学生没有被邀请,这项活动就没有办法进行。
班级建设的活跃值是活动产生的总活跃值减去邀请学生所花费的活跃值。
小Hi和小Ho需要选择进行哪些活动,来保证班级建设的活跃值尽可能大。
比如有3项活动,4名学生:
第1项活动产生5的活跃值,需要编号为1、2的学生才能进行;
第2项活动产生10的活跃值,需要编号为3、4的学生才能进行;
第3项活动产生8的活跃值,需要编号为2、3、4的学生才能进行。
编号为1到4的学生需要消耗的活跃值分别为6、3、5、4。
假设举办活动集合为{1},需要邀请的学生集合为{1,2},则得到的班级活跃值为5-9 = -4。
假设举办活动集合为{2},需要邀请的学生集合为{3,4},则得到的班级活跃值为10-9 = 1。
假设举办活动集合为{2,3},需要邀请的学生集合为{2,3,4},则得到的班级活跃值为18-12 = 6。
假设举办活动集合为{1,2,3},需要邀请的学生集合为{1,2,3,4},则得到的班级活跃值为23-18 = 5。
小Hi和小Ho总是希望班级活跃值越大越好,因此在这个例子中,他们会选择举行活动2和活动3。
输入
第1行:两个正整数N,M。1≤N≤200,1≤M≤200
第2行:M个正整数,第i个数表示邀请编号为i的学生需要花费的活跃值b[i],1≤b[i]≤1,000
第3..N+2行:第i行表示编号为i的活动情况。首先是2个整数a,k,a表示该活动产生的活跃值,k表示该活动需要的学生人数。接下来k个整数列举该活动需要的学生编号。1≤a≤1,000,1≤k≤M
输出
第1行:1个整数,最大可以产生的班级活跃值
样例输入
3 4
6 3 5 4
5 2 1 2
10 2 3 4
8 3 2 3 4
样例输出
6
思路
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
const int INF = 0x3f3f3f3f;
//注释为弧优化
struct max_Folw {
int d[maxn], cur[maxn], start, tend;
struct node {
int to, cap, flow, next;
} edge[maxn << 1];
int head[maxn];
bool vis[maxn];
int cnt;
void init(int s, int t){
memset(head, -1, sizeof(head));
cnt=0;
start=s, tend=t;
}
void add(int start, int to, int cap) {
edge[cnt].to = to;
edge[cnt].cap = cap;
edge[cnt].flow = 0;
edge[cnt].next = head[start];
head[start] = cnt++;
}
void AddEdge(int start, int to, int cap){
add(start, to, cap);
add(to, start, 0);
}
bool BFS() {
memset(d, -1, sizeof(d));
int Q[maxn * 2];
int Thead, Ttail;
Thead = Ttail = 0;
Q[Ttail++] = tend;
d[tend] = 0;
while (Thead<Ttail) {
int x = Q[Thead];
if (x == start)
return true;
for (int i = head[x]; i != -1; i = edge[i].next) {
int temp = edge[i].to;
if (d[temp] == -1 && edge[i^1].cap > edge[i^1].flow) { //没有标记,且可行流大于0
d[temp] = d[x] + 1;
Q[Ttail++] = temp;
}
}
Thead++;
}
return false;//汇点是否成功标号,也就是说是否找到增广路
}
int DFS(int x, int cap) {
if (x == tend)
return cap;
int flow = 0, f;
//for (int i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int temp = edge[i].to;
if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
edge[i].flow += f;
edge[i ^ 1].flow -= f;
flow += f;
if (flow == cap)
return flow;
}
}
d[x] = -2;//防止重搜
return flow;
}
int maxflow() {
int flow = 0, f;
while (BFS()) {
//memcpy(cur, head, sizeof head);
flow += DFS(start, INF);
}
return flow;
}
}flow;
int a[205], b[205];
int main() {
int n, m; scanf("%d%d", &n, &m);
flow.init(0, n+m+5);
for(int i=1; i<=m; i++){
scanf("%d", &b[i]);
flow.AddEdge(n+i, n+m+5, b[i]);
}
int ans=0;
for(int i=1; i<=n; i++){
int k; scanf("%d%d", &a[i], &k);
ans+=a[i];
flow.AddEdge(0, i, a[i]);
for(int j=1; j<=k; j++){
int to; scanf("%d", &to);
flow.AddEdge(i, n+to, INF);
}
}
printf("%d\n", ans-flow.maxflow());
return 0;
}
HDU3879
HDU3879(http://acm.hdu.edu.cn/showproblem.php?pid=3879)
题目大意:
要选择位置建立通信站,在第i个位置建立要花费的代价 为wi。
有m个条件:如果x y两个点都建立了通信站,那么这两个点之间就可以进行通信,而且可以收获c的收益。
问你最大收益是多少?
Input
Multiple test cases (no more than 20), for each test case:
The first line has two integers n (0<n<=5000) and m (0<m<=50000).
The second line has n integers, P1 through Pn, describes the cost of each location.
Next m line, each line contains three integers, Ai, Bi and Ci, describes the ith requirement.
Output
One integer each case, the maximum profit of the company.
Sample Input
5 5
1 2 3 4 5
1 2 3
2 3 4
1 3 3
1 4 2
4 5 3
Sample Output
4
思路
每个原始点为负。那么每个条件可以抽象为一个点,点权为c连接x和y,就是求一个最大权闭合子图。
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const LL maxn = 6e4 + 10;
const LL INF = 1ll<<30;
struct Edge {
int from,to,cap,flow;
Edge() {}
Edge(int f,int t,int c,int fl):from(f),to(t),cap(c),flow(fl) {}
};
struct Dinic {
int n,m,s,t;
vector<Edge> edges;
vector<int> G[maxn];
int cur[maxn];
int d[maxn];
bool vis[maxn];
void init(int n,int s,int t) {
this->n=n, this->s=s, this->t=t;
edges.clear();
for(int i=0; i<n; i++)
G[i].clear();
}
void AddEdge(int from,int to,int cap) {
edges.push_back( Edge(from,to,cap,0) );
edges.push_back( Edge(to,from,0,0) );
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BFS() {
queue<int> Q;
Q.push(s);
memset(vis,0,sizeof(vis));
d[s]=0;
vis[s]=true;
while(!Q.empty()) {
int x=Q.front();
Q.pop();
for(int i=0; i<G[x].size(); ++i) {
Edge& e=edges[G[x][i]];
if(!vis[e.to] && e.cap>e.flow) {
d[e.to]=1+d[x];
vis[e.to]=true;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x,int a) {
if(x==t || a==0)
return a;
int flow=0,f;
for(int& i=cur[x]; i<G[x].size(); ++i) {
Edge& e=edges[G[x][i]];
if(d[e.to]==d[x]+1 && (f=DFS(e.to,min(a,e.cap-e.flow) ) )>0) {
e.flow +=f;
edges[G[x][i]^1].flow -=f;
flow +=f;
a-=f;
if(a==0)
break;
}
}
return flow;
}
int max_flow() {
int ans=0;
while(BFS()) {
memset(cur,0,sizeof(cur));
ans += DFS(s,INF);
}
return ans;
}
} flow;
int main() {
LL n, m;
while(~scanf("%lld%lld", &n, &m)) {
LL ans=0;
flow.init(n+m+10, 0, n+m+1);
for(LL i=1; i<=n; i++) {
LL x;
scanf("%lld", &x);
flow.AddEdge(i, n+m+1, x);
}
for(LL i=1; i<=m; i++) {
LL x, y, c;
scanf("%lld%lld%lld", &x, &y, &c);
ans+=c;
flow.AddEdge(0, n+i, c);
flow.AddEdge(n+i, x, INF);
flow.AddEdge(n+i, y, INF);
}
printf("%lld\n", ans-flow.max_flow());
}
return 0;
}
二分图左右节点数相同
思路
我们要保证左右节点数一样的最大权闭合子图。
我们看最后一句:有完美匹配
说明匹配的时候,只有可能左节点x<右节点数y。
如果我们把每个节点+base。。
如果y>x。那么:
上面:说明不存在。
如果y<x。因为有完美匹配,也不可能。说明y==x。
其他的按正常的最大权闭合子图来写就可以了。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const LL maxn = 2e5 + 10;
const LL maxm = 2e5 + 10;
LL mx = 1e9;
const LL INF = 1e13;
//注释为弧优化
struct max_Folw {
LL d[maxn], cur[maxn], start, tend;
LL Q[maxn * 2];
struct node {
LL to, cap, flow, next;
} edge[maxm << 1];
LL head[maxn];
bool vis[maxn];
LL cnt;
void init(LL s, LL t) {
memset(head, -1, sizeof(head));
cnt = 0;
start = s, tend = t;
}
void add(LL start, LL to, LL cap) {
edge[cnt].to = to;
edge[cnt].cap = cap;
edge[cnt].flow = 0;
edge[cnt].next = head[start];
head[start] = cnt++;
}
void AddEdge(LL start, LL to, LL cap) {
add(start, to, cap);
add(to, start, 0);
}
bool BFS() {
memset(d, -1, sizeof(d));
LL Thead, Ttail;
Thead = Ttail = 0;
Q[Ttail++] = tend;
d[tend] = 0;
while (Thead < Ttail) {
LL x = Q[Thead];
if (x == start)
return true;
for (LL i = head[x]; i != -1; i = edge[i].next) {
LL temp = edge[i].to;
if (d[temp] == -1 && edge[i ^ 1].cap > edge[i ^ 1].flow) { //没有标记,且可行流大于0
d[temp] = d[x] + 1;
Q[Ttail++] = temp;
}
}
Thead++;
}
return false; //汇点是否成功标号,也就是说是否找到增广路
}
LL DFS(LL x, LL cap) {
if (x == tend)
return cap;
LL flow = 0, f;
// for (LL i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
for (LL i = head[x]; i != -1; i = edge[i].next) {
LL temp = edge[i].to;
if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
edge[i].flow += f;
edge[i ^ 1].flow -= f;
flow += f;
if (flow == cap)
return flow;
}
}
d[x] = -2; //防止重搜
return flow;
}
LL maxflow() {
LL flow = 0, f;
while (BFS()) {
// memcpy(cur, head, sizeof head);
flow += DFS(start, INF);
}
return flow;
}
} flow;
// edge[i].cap-edge[i].flow==0 容量-流量=可流的流量 =0说明已经满流
int main() {
LL n;
scanf("%lld", &n);
flow.init(0, 2 * n + 5);
LL ans = 0;
for (LL i = 1; i <= n; i++) {
LL t;
scanf("%lld", &t);
for (LL k = 1; k <= t; k++) {
LL x;
scanf("%lld", &x);
flow.AddEdge(i, n + x, INF);
}
}
for (LL i = 1; i <= n; i++) {
LL x;
scanf("%lld", &x);
flow.AddEdge(0, i, mx - x);
ans += mx - x;
flow.AddEdge(n + i, 2 * n + 5, mx);
}
printf("%lld\n", -(ans - flow.maxflow()));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)