Time limit 3000 ms
题目大意
(翻译来自洛谷)
John是某公司的CEO
公司内部共n个员工,员工之间可能曾经因为小事有了过节,总是闹矛盾。
若员工u和员工v有矛盾,用边(u,v)表示,共m个矛盾。
最近,该公司内部越来越不团结,John决定裁员。
他想得到一个被裁人员的清单,使得被裁人员间的不团结率最高。
不团结率定义为被裁人员间的矛盾总数与被裁人员数的比值(不团结率 = 被裁人员之间的矛盾总数 / 被裁人员数)
题目分析
看到这种比率的形式马上想到分数规划
即求原图的一个子图
G
′
=
(
V
′
,
E
′
)
G^{'}=(V^{'},E^{'})
G′=(V′,E′)使得
∑
e
∈
E
′
1
∑
v
∈
V
′
1
\frac{\sum_{e\in E^{'}}1}{\sum_{v\in V^{'}}1}
∑v∈V′1∑e∈E′1最大化
那么按照分数规划的套路二分比值(设为g)
判断是否存在方案使得
∑
e
∈
E
′
1
∑
v
∈
V
′
1
≥
g
⇒
∑
e
∈
E
′
1
−
∑
v
∈
V
′
g
≥
0
\frac{\sum_{e\in E^{'}}1}{\sum_{v\in V^{'}}1}\geq g \Rightarrow \sum_{e\in E^{'}}1-\sum_{v\in V^{'}}g\geq 0
∑v∈V′1∑e∈E′1≥g⇒∑e∈E′1−∑v∈V′g≥0成立
根据题意可知选择一条边则必定选择这条边的两个端点
那么上面这个式子恰好对应到了最大权闭合子图的模型
源点s向每对矛盾连边,容量为1
每个员工向汇点t连边,容量为g
每个矛盾向其对应的员工连边,容量为inf
判断最大收益(矛盾数m-最小割)是否大于等于0即可
被精度活活卡死
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef long long lt;
typedef double dd;
#define eps 1e-8
int read()
{
int x=0,f=1;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return x*f;
}
const dd inf=1e9;
const int maxn=10010;
int n,m;
struct node{int v,nxt;dd f;}E[maxn<<2];
int head[maxn],tot=1;
int lev[maxn];
struct edge{int u,v;}edge[maxn];
int judge[110];
vector<int> rem;
void add(int u,int v,dd f)
{
E[++tot].nxt=head[u];
E[tot].v=v; E[tot].f=f;
head[u]=tot;
E[++tot].nxt=head[v];
E[tot].v=u; E[tot].f=0.0;
head[v]=tot;
}
int bfs(int s,int t)
{
memset(lev,-1,sizeof(lev)); lev[s]=0;
queue<int> q; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(lev[v]==-1&&E[i].f>0)
{
lev[v]=lev[u]+1;
if(v==t) return 1;
q.push(v);
}
}
}
return 0;
}
dd dfs(int u,dd cap,int t)
{
if(u==t) return cap;
dd flow=cap;
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(lev[v]==lev[u]+1&&E[i].f>0&&flow>0)
{
dd f=dfs(v,min(E[i].f,flow),t);
E[i].f-=f; E[i^1].f+=f;
flow-=f;
}
}
return cap-flow;
}
dd dicnic(int s,int t)
{
dd maxf=0;
while(bfs(s,t)) maxf+=dfs(s,inf,t);
return maxf;
}
int calc(dd x)
{
memset(head,0,sizeof(head)); tot=1;
int s=n+m+1,t=s+1;
for(int i=1;i<=m;++i)
{
add(s,i,1.0);
add(i,edge[i].u+m,inf);
add(i,edge[i].v+m,inf);
}
for(int i=1;i<=n;++i)
add(i+m,t,x);
dd maxf=dicnic(s,t);
return 1.0*m-maxf;
}
int solve(dd rat)
{
rem.clear();
memset(judge,0,sizeof(judge));
int s=n+m+1,t=s+1,res=0;
calc(rat); bfs(s,t);
for(int i=1;i<=m;++i)
if(lev[i]!=-1)
{
if(++judge[edge[i].u]==1) res++;
if(++judge[edge[i].v]==1) res++;
}
for(int i=1;i<=n;++i)
if(judge[i]) rem.push_back(i);
return res;
}
int main()
{
int cs=0;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(cs++!=0) printf("\n");
if(m==0){ printf("1\n1\n"); continue;}
for(int i=1;i<=m;++i)
edge[i].u=read(),edge[i].v=read();
dd L=1.0/(dd)n,R=m*1.0,rat=0;
while(R-L>eps)
{
dd mid=(L+R)/2.0;
if(calc(mid)>eps) rat=L=mid;
else R=mid;
}
int ans=solve(rat);
printf("%d\n",ans);
for(int i=0;i<rem.size();++i)
printf("%d\n",rem[i]);
}
return 0;
}
集训队论文–胡伯涛《最小割模型在信息学竞赛中的应用》里面还描述了一种进一步优化的方法
但蒟蒻还没完全理解,过段时间再补上