题目
某公司加工一种由铁、铝、锡组成的合金。
首先进口一些铁铝锡合金原材料,不同种类的原材料中铁铝锡的比重不同。
然后,将每种原材料取出一定量,经过融解、混合,得到新的合金。新的合金的铁铝锡比重为用户所需要的比重。
现在,用户给出了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
考虑一旦能混合出来,则剩下的杂质就是
的锡,故舍掉一维,
考虑,
两个点,任意比混合能混合出的
一定落在这条线段上
那拓展到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;
}