poj 2987 Firing【最大权闭合子图+玄学计数】

LYY Orz

第一次见这种神奇的计数方式,乍一看非常不靠谱但是仔细想想还卡不掉

就是把在建图的时候把正权变成w*10000-1,负权变成w*10000+1,跑最大权闭合子图。后面的1作用是计数,因为在最大权闭合子图中划到s点一侧的代表选,这样一来,后四位就是起了计数作用。sum初始统计的个数就是所有正权点,然后dinic中割掉一个正权点的边即相当于在最终答案的后四位+1,也就是点数-1

然后考虑收益相同的方案,点数多的后四位一定小,而当前求得又是最小割,所以会选割掉点数少的,也就是留下员工多的方案数。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const long long N=5005,inf=1e18;
long long n,m,s,t,h[N],cnt=1,le[N],sum;
struct qwe
{
	long long ne,to,va;
}e[N*100];
long long read()
{
	long long r=0,f=1;
	char p=getchar();
	while(p>'9'||p<'0')
	{
		if(p=='-')
			f=-1;
		p=getchar();
	}
	while(p>='0'&&p<='9')
	{
		r=r*10+p-48;
		p=getchar();
	}
	return r*f;
}
void add(long long u,long long v,long long w)
{
	cnt++;
	e[cnt].ne=h[u];
	e[cnt].to=v;
	e[cnt].va=w;
	h[u]=cnt;
}
void ins(long long u,long long v,long long w)
{//cout<<u<<" "<<v<<" "<<w<<endl;
	add(u,v,w);
	add(v,u,0);
}
bool bfs()
{
	queue<long long>q;
	memset(le,0,sizeof(le));
	le[s]=1;
	q.push(s);
	while(!q.empty())
	{
		long long u=q.front();
		q.pop();
		for(long long i=h[u];i;i=e[i].ne)
			if(e[i].va>0&&!le[e[i].to])
			{
				le[e[i].to]=le[u]+1;
				q.push(e[i].to);
			}
	}
	return le[t];
}
long long dfs(long long u,long long f)
{
	if(u==t||!f)
		return f;
	long long us=0;
	for(long long i=h[u];i&&us<f;i=e[i].ne)
		if(e[i].va>0&&le[e[i].to]==le[u]+1)
		{
			long long t=dfs(e[i].to,min(e[i].va,f-us));
			e[i].va-=t;
			e[i^1].va+=t;
			us+=t;
		}
	if(!us)
		le[u]=0;
	return us;
}
long long dinic()
{
	long long re=0;
	while(bfs())
		re+=dfs(s,inf);
	return re;
}
int main()
{
	n=read(),m=read();
	t=n+1;
	for(long long i=1;i<=n;i++)
	{
		long long x=read();
		if(x>0)
			ins(s,i,x*10000ll-1ll),sum+=x*10000ll-1ll;
		else
			ins(i,t,x*-10000ll+1ll);
	}
	for(long long i=1;i<=m;i++)
	{
		long long x=read(),y=read();
		ins(x,y,inf);
	}//cout<<dinic()<<endl;
	sum-=dinic();
	long long x1=0;
	while(sum%10000ll)
		sum++,x1++;
	printf("%lld %lld\n",x1,sum/10000ll);
	return 0;
}

相关推荐