bzoj3571: [Hnoi2014]画框

来源:互联网 发布:淘宝知识产权扣分 编辑:程序博客网 时间:2024/06/10 18:11

这道题可以类似最小乘积生成树的最小乘积二分图完美匹配。

本题中二分图完美匹配的个数可以达到n^n,将每个匹配看成点(ΣA,ΣB),易得Ans在左下角的凸壳上。

使用分治,先求出最左的和最下的点L,R,寻找mid使mid为直线LR下方最靠近原点的点,然后分治L~mid,mid~R,直到L,mid,R共线。

至于如何求mid,将边权改变即可(窝还不太懂。。)

SPFA比KM慢好多。。

#include<iostream>#include<cstdio>#include<cstring>#define inf 2000000000#define N 205#define M 20005#define Loop for (int i=1;i<=n;i++)for (int j=1;j<=n;j++)using namespace std;int n,a[N][N],b[N][N],Ans,dis[N],fa[N],q[N];int v[N][N],w[M],f[M],af[M],bf[M],l,to[M],next[M],first[N];bool fl[N];struct P{int x,y;};P operator-(P a,P b){return (P){a.x-b.x,a.y-b.y};}int cross(P a,P b){return a.x*b.y-b.x*a.y;}//叉积void link(int x,int y,int v,int a,int b){to[++l]=y;w[l]=1;f[l]=v;af[l]=a;bf[l]=b;next[l]=first[x];first[x]=l;to[++l]=x;w[l]=0;f[l]=-v;af[l]=-a;bf[l]=-b;next[l]=first[y];first[y]=l;}bool SPFA(){memset(dis,0x7f,sizeof dis);int head=0,tail=1;dis[0]=0;q[1]=0;while(head<tail){int x=q[++head];fl[x]=0;for (int i=first[x];i;i=next[i])if (w[i]&&dis[x]+f[i]<dis[to[i]]){fa[to[i]]=i;dis[to[i]]=dis[x]+f[i];if (!fl[to[i]]) fl[to[i]]=1,q[++tail]=to[i];}}return dis[n*2+1]<inf;}P Get()//费用流{P Sum=(P){0,0};l=1;memset(first,0,sizeof first);for (int i=1;i<=n;i++) link(0,i,0,0,0),link(i+n,n*2+1,0,0,0);Loop link(i,j+n,v[i][j],a[i][j],b[i][j]);while(SPFA())for (int i=n*2+1;i;i=to[fa[i]^1]){w[fa[i]]--;w[fa[i]^1]++;Sum.x+=af[fa[i]];Sum.y+=bf[fa[i]];}if (Sum.x*Sum.y<Ans) Ans=Sum.x*Sum.y;//更新答案return Sum;}void work(P l,P r)//二分{P t=l-r;Loop v[i][j]=cross((P){a[i][j],b[i][j]},t);P mid=Get();if (cross(mid-l,r-mid)>0)//共线work(l,mid),work(mid,r);}int main(){int T;scanf("%d",&T);while(T--){scanf("%d",&n);Ans=inf;Loop scanf("%d",&a[i][j]);Loop scanf("%d",&b[i][j]);Loop v[i][j]=a[i][j];P l=Get();Loop v[i][j]=b[i][j];P r=Get();work(l,r);printf("%d\n",Ans);}return 0;}


0 0