YBTOJ:矛盾指数(网络流-最大权闭合图)

网络流要大胆建图

题目描述

公司内部共 n n n个员工,员工之间可能有矛盾。若员工 u u u和员工 v v v有矛盾,用边 ( u , v ) (u,v) (u,v)表示,共 m m m个矛盾。

现在公司决定裁员,使得被裁人员间的矛盾指数最高。矛盾指数定义为被裁人员间的矛盾总数与被裁人员数的比值

解析

感觉很神仙的题
一开始自己的思路是暴力枚举裁员人数求最大矛盾,但是如何强制控制裁员人数把我卡住了…(费用流或许能做?)
最后还是看了题解qwq
关键是建图的方法
把每个职员和每条矛盾关系都看作一个点,那么 ( u , v ) (u,v) (u,v)可以选取的前提就是 u u u v v v都选取
这样又成了求最大权闭合图的经典问题

但是如何确定点权呢?
考虑二分矛盾指数g,考虑是否存在一种方案,使得:
边 数 / 点 数 > = g 边数/点数>=g />=g
移项,得:
边 数 ∗ 1 + 点 数 ∗ ( − g ) > = 0 边数*1+点数*(-g)>=0 1+g>=0
这样就可以直接上dinic求了

代码

#include<bits/stdc++.h>
#define ll double
using namespace std;
const int N=5050;
const int M=1e9;
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();};
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int n,m,s,t;
struct node{
	int to,nxt;ll cap;
}p[300500];
int fi[N],cnt;
void addline(int x,int y,ll cap){
	p[++cnt]=(node){y,fi[x],cap};fi[x]=cnt;
	p[++cnt]=(node){x,fi[y],0};fi[y]=cnt;
}
int col[N],cur[N];
queue<int>q;
int bfs(){
	memset(col,0,sizeof(col));col[s]=1;
	q.push(s);
	while(!q.empty()){
		int now=q.front();q.pop();
		for(int i=cur[now]=fi[now];~i;i=p[i].nxt){
			int to=p[i].to;
			if(col[to]||!p[i].cap) continue;
			col[to]=col[now]+1;
			q.push(to);
		}
	}
	return col[t];
}
ll dfs(int x,ll lim){
	if(x==t||!lim) return lim;
	ll res=0;
	for(int &i=cur[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(!p[i].cap||col[to]!=col[x]+1) continue;
		ll add=dfs(to,min(lim,p[i].cap));
		lim-=add;res+=add;
		p[i].cap-=add;p[i^1].cap+=add;
		if(!lim) break;
	}
	if(lim) col[x]=-1;
	return res;
}
ll dinic(){
	ll res=0;
	while(bfs()){
		while(ll tmp=dfs(s,2e15)) res+=tmp;
	}
	return res;
}
int vis[N];
void find1(int x){
	if(vis[x]) return;
	vis[x]=1;
	for(int i=fi[x];~i;i=p[i].nxt){
		if(!p[i].cap) continue;
		find1(p[i].to);
	}
	return;
}
void find2(int x){
	if(vis[x]) return;
	vis[x]=2;
	for(int i=fi[x];~i;i=p[i].nxt){
		if(!p[i^1].cap) continue;
		find2(p[i].to);
	}
	return;
}
int num;
int u[N],v[N];
bool check(double x){
	memset(fi,-1,sizeof(fi));cnt=-1;
	double tot=m;
	s=n+m+1;t=n+m+2;
	for(int i=1;i<=n;i++){
		addline(i,t,x);
	}
	for(int i=1;i<=m;i++){
		addline(s,i+n,1);
		addline(i+n,u[i],2e15);
		addline(i+n,v[i],2e15);
	}
	return tot-dinic()>0;
}
int main(){
	n=read();m=read();
	if(!m){
		printf("1\n1");return 0;
	}
	for(int i=1;i<=m;i++){
		u[i]=read();v[i]=read();
	}
	double st=0,ed=2e9;
	for(int i=1;i<=100;i++){
		double mid=(st+ed)/2;
		if(check(mid)) st=mid;
		else ed=mid;
	}
	check(st);
	find1(s);
	int num=0;
	for(int i=1;i<=n;i++){
		num+=vis[i]?1:0;
	}
	printf("%d\n",num);
	for(int i=1;i<=n;i++){
		if(vis[i]) printf("%d\n",i);
	}
    return 0;
}
/*
3 2 1 3
1 2 10
2 3 10
*/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值