BZOJ 3571: [Hnoi2014]画框

一看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[N][N],b[N][N];
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[N][N],slack[N],n,linked[N],lx[N],ly[N];
bool visx[N],visy[N];
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[N][N],b[N][N];
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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值