- 给定一张\(n\)个点\(m\)条边的无向图,求最大密度子图(即边数除以点数最大的导出子图)。
- \(n\le100,m\le1000\)
分数规划
这种问题一看就是分数规划。
我们直接二分答案\(x\),则\(\frac EP\ge x\)就可以写成\(E-x\times P\ge 0\)。
即选择每条边的价值是\(1\),每个点的价值是\(-x\)。
最大权闭合子图
考虑只有选了两个端点,才能选择这条边,是一个经典的最大权闭合子图模型。
注意有可能不选任何点会取得最优价值,因此我们最大流之后需要加上判断选择的点数不为\(0\)。
至于如何判断一个点是否被选择,根据最大权闭合子图模型的建图方式,断开与超级汇相连的边表示选择,那么此时就与超级源连通。即从超级源出发存在一条到该点的增广路径,也就是最后一次\(BFS\)时的\(dep\not=-1\)。
\(O(Dinic)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define M 1000
#define INF 1e9
#define DB double
using namespace std;
int n,m;struct line {int x,y;}p[M+5];
namespace Dinic
{
#define PS (N+M+2)
#define ES (N+3*M)
#define s (n+m+1)
#define t (n+m+2)
#define E(x) ((((x)-1)^1)+1)
#define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
int ee,lnk[PS+5],cur[PS+5];struct edge {int to,nxt;DB F;}e[2*ES+5];
I void Cl() {ee=0;for(RI i=1;i<=t;++i) lnk[i]=0;}
I void Add(CI x,CI y,Con DB& f) {add(x,y,f),add(y,x,0);}
int q[PS+5],D[PS+5];I bool BFS()
{
RI i,k,H=1,T=1;for(i=1;i<=t;++i) D[i]=-1;D[q[1]=s]=0;
W(H<=T&&!~D[t]) for(i=lnk[k=q[H++]];i;i=e[i].nxt)
e[i].F&&!~D[e[i].to]&&(D[q[++T]=e[i].to]=D[k]+1);return ~D[t];
}
I DB DFS(CI x=s,DB f=INF)
{
if(x==t||!f) return f;RI i;DB o,g=0;for(i=cur[x];i;i=e[i].nxt)
{
if(D[e[i].to]^(D[x]+1)||!(o=DFS(e[i].to,min(f,e[i].F)))) continue;
if(g+=o,e[i].F-=o,e[E(i)].F+=o,!(f-=o)) break;
}return cur[x]=i,g;
}
I DB MaxFlow(Con DB& x)//最大权闭合子图模型
{
RI i;for(Cl(),i=1;i<=n;++i) Add(i,t,x);//点向超级汇
for(i=1;i<=m;++i) Add(s,n+i,1),Add(n+i,p[i].x,INF),Add(n+i,p[i].y,INF);//超级源向边,边向点
DB f=0;W(BFS()) memcpy(cur,lnk,sizeof(cur)),f+=DFS();return f;//最大流
}
I bool Check(CI x) {return ~D[x];}//判断是否选择(是否与超级源连通)
}
int main()
{
RI i,c;DB l,r,mid,res;W(scanf("%d%d",&n,&m)==2)
{
for(i=1;i<=m;++i) scanf("%d%d",&p[i].x,&p[i].y);
if(!m) {puts("1\n1\n");continue;}//特判没有边的情况
l=0,r=m;W(r-l>1e-6) {res=m-Dinic::MaxFlow(mid=(l+r)/2);//分数规划
for(i=1;i<=n;++i) if(Dinic::Check(i)) break;i<=n&&res>0?l=mid:r=mid;}//必须有点被选择
for(Dinic::MaxFlow(l),c=0,i=1;i<=n;++i) Dinic::Check(i)&&++c;printf("%d\n",c);
for(i=1;i<=n;++i) Dinic::Check(i)&&printf("%d\n",i);putchar('\n');
}return 0;
}