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; }