bzoj1027 [JSOI2007]合金(图论/有向图最小环的三种求法 凸包思想)

题目

某公司加工一种由铁、铝、锡组成的合金。

首先进口一些铁铝锡合金原材料,不同种类的原材料中铁铝锡的比重不同。

然后,将每种原材料取出一定量,经过融解、混合,得到新的合金。新的合金的铁铝锡比重为用户所需要的比重。

现在,用户给出了n种他们需要的合金,以及每种合金中铁铝锡的比重。

公司希望能够订购最少种类的原材料,并且使用这些原材料可以加工出用户需要的所有种类的合金。

输入

第一行,m和n(m, n ≤ 500),分别表示原材料种数和用户需要的合金种数。

以下m行,每行三个实数a, b, c(a, b, c ≥ 0 且 a + b + c = 1),分别表示铁铝锡在一种原材料中所占的比重。

以下n行,每行三个实数a, b, c(a, b, c ≥ 0 且 a + b + c = 1),分别表示铁铝锡在一种用户需要的合金中所占的比重。

输出

一个整数,表示最少需要的原材料种数。若无解,则输出–1。

思路来源

bzoj 1027 floyd求有向图最小环_weixin_30635053的博客-CSDN博客

https://oi-wiki.org/graph/min-circle/

题解

题目实际是要求,m种选出x种原材料按你想要的比例混合,能满足出所有的n种用户需求,最小化x

考虑(a,b)一旦能混合出来,则剩下的杂质就是1-a-b的锡,故舍掉一维,

考虑(a_{1},b_{1})(a_{2},b_{2})两个点,任意比混合能混合出的(a_{1}x+a_{2}(1-x),b_{1}y+b_{2}(1-y))一定落在这条线段上

那拓展到x个点的情形,若x个点能围成一个封闭图形,

首先,封闭图形上的点都可以通过对应线段混合出来,

其次,对于图形内的点,可以任意一条过该点的直线与封闭图形交于两点,故可混合出,

即x个点围成一个凸包,要包含用户要求的n个点,且最小化x

考虑m种材料的点i和点j,

如果所有用户点位于向量ij位于的左侧(或线段ij上),就把i向j连一条边,

该问题等价于求m个点构成的有向图的最小环(太神奇了orz),floyd搞搞就可了

心得

bzoj卡时间太严格了,floyd有个非INF的地方没剪枝就T了

OI Wiki里给出了几种最小环的实现方法,然而最短路的复杂度都比较鸡肋

 

有向图最小环,代价不为1的时候似乎只能用floyd搞,

与无向图最小环的floyd不同,有向图比较方便,

开始置dis[i][i]=INF,最后枚举环的必经点i,从而来确定最小的环

 

有向图最小环,代价为1,一边属于多环的情形,万能UOJ群里问了一下

①解法1:可以n^3/64,枚举一个必经点然后压位单源BFS,把S->x的所有x作为源求到S的最短路

压位单源bfs,是什么奇技淫巧啊,回头再学一下,压位floyd都还不会

②解法2:可以nm,就当无向图(同时建正图和反图)来bfs,但把正图和反图的距离存到不同数组里,

如果存在一条正链u+一条反链v+正边u->v,或反过来的情况,就说明有环,此时更新环的长度即可

 

有向图最小环,代价为正(>1)的情形,

①枚举最小环的最后一条边u->v,再加上v到u的最短路值即可,

复杂度是全源最短路的复杂度,floyd,O(n^3),也可用Johnson

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <deque>
#include <queue>
using namespace std;

typedef double db;

const db EPS = 1e-8;
const int N=505,INF=0x3f3f3f3f;

inline int sign(db a) {
	return a < -EPS ? -1 : a > EPS;
}

inline int cmp(db a, db b){//精度比较函数
	return sign(a-b);
}

struct P {//点
	db x, y;
	P() {}
	P(db _x, db _y) : x(_x), y(_y) {}
	P operator+(P p) { return P(x + p.x, y + p.y); }
	P operator-(P p) { return P(x - p.x, y - p.y); }
	P operator*(db d) { return P(x * d, y * d); }
	P operator/(db d) { return P(x / d, y / d); }
	bool operator<(P p) const {
		int c = cmp(x, p.x);
		if (c) return c == -1;
		return cmp(y, p.y) == -1;
	}
	db dot(P p) { return x * p.x + y * p.y; }//点积
	db det(P p) { return x * p.y - y * p.x; }//叉积
}e[N],f[N];

#define cross(p1,p2,p3) ((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))
#define crossOp(p1,p2,p3) sign(cross(p1,p2,p3))

int m,n,dp[N][N];
db z;

int main(){
	int i,j,k;
	scanf("%d%d",&m,&n);
	for(i=1;i<=m;++i){//给出的点集 
		scanf("%lf%lf%lf",&e[i].x,&e[i].y,&z);
	}
	for(i=1;i<=n;++i){//必包含在内的点集 
		scanf("%lf%lf%lf",&f[i].x,&f[i].y,&z);
	}
	//m个点内选出最少的点集S 使S构成的闭包包含n个点构成的闭包 
	memset(dp,INF,sizeof dp);
	for(i=1;i<=m;++i){
		for(j=1;j<=m;++j){
			bool ok=1;
			for(k=1;k<=n;++k){
				int sg=sign(crossOp(e[i],f[k],e[j]));
				if(sg<0)continue;
				P g=f[k]-e[i];
				if(sg==0 && sign(g.dot(f[k]-e[j]))<=0)continue;//k位于向量i->j左侧 或位于线段ij上 
				ok=0;
				break;//不加会TLE 
			}
			if(ok)dp[i][j]=1;
		}
	}
	for(k=1;k<=m;++k){
		for(i=1;i<=m;++i){
			if(dp[i][k]==INF)continue;//不加会TLE 
			for(j=1;j<=m;++j){
				dp[i][j]=min(dp[i][k]+dp[k][j],dp[i][j]);
			}
		}
	}
	int ans=INF;
	for(i=1;i<=m;++i){
		ans=min(ans,dp[i][i]);
	}
	if(ans==INF)ans=-1;
	printf("%d\n",ans);
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值