您的位置:首页 > 其它

BZOJ 3571: [Hnoi2014]画框

2016-04-10 09:54 429 查看
一看TM就不会做

赶紧学了一发最小乘积生成树和最小乘积最大匹配

大概就是把每个完备匹配后的结果看成一个点(sigma(a),sigma(b)),发现答案都在下凸壳上,然后用分治递归找下凸壳就好了。

首先找到下凸壳两端的点(横坐标最小和纵坐标最小的两个点),然后连线,找到离线最远的点(叉积推公式,KM/费用流找匹配),然后分治,直到最远的点就是两点之一了,也就是两点是下凸壳上相邻的两点。

(不会写KM了,写了个费用流水过去了,感觉后面两个点妥妥的T了)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=70+5;
const int inf=1e8;
struct Edge{int from,to,next,v,c;}e[N*N*3];
int head[N<<1],cnt;
void init(){memset(head,0,sizeof(head));cnt=1;}
void ins(int u,int v,int w,int c){
e[++cnt]=(Edge){u,v,head[u],w,c};head[u]=cnt;
}
void insert(int u,int v,int w,int c){ins(u,v,w,c);ins(v,u,0,-c);}
int d[N<<1],from[N<<1];
bool inq[N<<1];
bool spfa(int s,int t,int &flow,int &cost){
memset(d,0x3f,sizeof(d));d[s]=0;queue<int>q;q.push(s);
while(!q.empty()){
int u=q.front();q.pop();inq[u]=false;
for(int i=head[u];i;i=e[i].next)
if(e[i].v&&d[e[i].to]>d[u]+e[i].c){
d[e[i].to]=d[u]+e[i].c;
from[e[i].to]=i;
if(!inq[e[i].to]){
inq[e[i].to]=true;
q.push(e[i].to);
}
}
}
if(d[t]>=inf)return false;
int x=inf;
for(int i=from[t];i;i=from[e[i].from])x=min(x,e[i].v);
flow+=x;cost+=d[t]*x;
for(int i=from[t];i;i=from[e[i].from])
e[i].v-=x,e[i^1].v+=x;
return true;
}
int mcf(int s,int t){int flow=0,cost=0;while(spfa(s,t,flow,cost));return flow;}
struct point{
int x,y;
bool operator == (const point &rhs)const{
return x==rhs.x&&y==rhs.y;
}
bool operator < (const point &rhs)const{
if(x!=rhs.x)return x<rhs.x;
return y<rhs.y;
}
};
int a

,b

;
int n;
point solve(int ka,int kb){
init();
int S=0,T=2*n+1;
for(int i=1;i<=n;i++){
insert(S,i,1,0);insert(i+n,T,1,0);
for(int j=1;j<=n;j++)
insert(i,j+n,1,a[i][j]*ka+b[i][j]*kb);
}
mcf(S,T);
int suma=0,sumb=0;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=e[j].next)
if(!e[j].v&&e[j].to!=S)
suma+=a[i][e[j].to-n],sumb+=b[i][e[j].to-n];
return (point){suma,sumb};
}
int solve(point a,point b){
init();
point t=solve(a.y-b.y,b.x-a.x);
if(t==a||t==b)return min(a.x*a.y,b.x*b.y);
else return min(solve(a,t),solve(t,b));
}
int main(){
//freopen("a.in","r",stdin);
int T;scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)scanf("%d",&b[i][j]);
point p1=solve(1,0),p2=solve(0,1);
printf("%d\n",solve(p1,p2));
}
return 0;
}


不会写KM简直不像话啊,果断写了一发KM压压惊,速度是费用流的十倍QAQ,BTW:宏定义真好用

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define mmt(x,y) memset(x,y,sizeof(x))
const int N=70+5;
const int inf=1e8;
int w

,slack
,n,linked
,lx
,ly
;
bool visx
,visy
;
bool match(int u){
visx[u]=true;
rep(v,1,n){
if(visy[v])continue;
int d=lx[u]+ly[v]-w[u][v];
if(!d){
visy[v]=true;
if(linked[v]==-1||match(linked[v])){
linked[v]=u;
return true;
}
}else slack[v]=min(slack[v],d);
}
return false;
}
struct point{
int x,y;
bool operator == (const point &rhs)const{
return x==rhs.x&&y==rhs.y;
}
};
int a

,b

;
point KM(int ka,int kb){
rep(i,1,n)rep(j,1,n)w[i][j]=a[i][j]*ka+b[i][j]*kb;
mmt(linked,-1);mmt(lx,-0x3f);mmt(ly,0);
rep(i,1,n)rep(j,1,n)lx[i]=max(lx[i],w[i][j]);
rep(i,1,n){
mmt(slack,0x3f);
while(true){
mmt(visx,0);mmt(visy,0);
if(match(i))break;
int d=inf;
rep(j,1,n)if(!visy[j])d=min(d,slack[j]);
rep(j,1,n){
if(visx[j])lx[j]-=d;
if(visy[j])ly[j]+=d;
else slack[j]-=d;
}
}
}
int suma=0,sumb=0;
rep(i,1,n)if(linked[i]!=-1)suma+=a[linked[i]][i],sumb+=b[linked[i]][i];
return (point){suma,sumb};
}
int solve(point a,point b){
point t=KM(b.y-a.y,a.x-b.x);
if(a==t||b==t)return min(a.x*a.y,b.x*b.y);
else return min(solve(a,t),solve(t,b));
}
int main(){
//freopen("a.in","r",stdin);
int T;scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)scanf("%d",&b[i][j]);
point p1=KM(-1,0),p2=KM(0,-1);
printf("%d\n",solve(p1,p2));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: