模拟75 题解
A. 导弹袭击
想到了凸包,推出了题中的式子,然而没有继续推下去。
或者说,高考数学线性规划没有学好。
一种导弹的飞行时间函数为:
$z_i=A*\frac{1}{a_i}+B*\frac{1}{b_i}$
似乎显然是一个线性规划式,设$x=\frac{1}{a_i},y=\frac{1}{b_i}$。
那么有$z=A*x+B*y$
$y=\frac{z}{B}-\frac{A}{B}*x$
要使$z$取得最小值,因为斜率是负的,只要维护这些点形成的左下凸包。
将所有点的横坐标排个序,考虑栈顶和栈顶下的元素,就可以直接用单调栈插入,维护凸包。
B. 炼金术士的疑惑
显然用高斯消元。
然而字符串读入很恶心,加上高斯消元差不多忘光了,打了很久。
最终因为输出-0.0挂95分。
我的思路是设每个化学方程式在最终化学方程式中出现的系数为$x_i$,
那么根据物质列出方程,可以解出每个系数$x_i$,直接乘上$h_i$就可以搞最终答案。
然而如果这些方程中存在一些方程可以被其他方程表示。
那么可能会出现的情况是:变量数$n$大于方程数$m$,无法解出任何一个变量$x_i$。
如果数据保证有解,出现这种情况,一定意味着有一些无意义的变量。
也就是说不一定每一个方程$i$都要分配变量$i$。
所以魔改一下高斯消元的方法就可以了。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<map> 5 #define ull unsigned long long 6 using namespace std; 7 const ull p=131; 8 const double eps=1e-8; 9 map<ull,int> mp; 10 int n,cnt; 11 char s[3000]; 12 double val[205],x[205][205],ans[205]; 13 inline double read_double(){ 14 double x=0,t=0,tmp=1; 15 int l=1,f=0; 16 char ch=s[l++]; 17 while(!isdigit(ch)) f=ch=='-',ch=s[l++]; 18 while(isdigit(ch)) x=x*10+(ch^48),ch=s[l++]; 19 if(ch=='.'){ 20 ch=s[l++]; 21 while(isdigit(ch)) t=t*10+(ch^48),ch=s[l++],tmp/=10; 22 } 23 return f?-x-t*tmp:x+t*tmp; 24 } 25 inline ull hashstring(){ 26 ull ans=0; 27 for(int i=1;s[i];++i) ans=ans*p+s[i]+1; 28 return ans; 29 } 30 inline void read(int k){ 31 while(1){ 32 scanf("%s",s+1); 33 if(s[1]=='=') break; 34 if(s[1]=='+') continue; 35 double t=read_double(); 36 scanf("%s",s+1); 37 ull hsh=hashstring(); 38 if(!mp[hsh]) mp[hsh]=++cnt; 39 int id=mp[hsh]; 40 x[id][k]+=t; 41 } 42 while(1){ 43 scanf("%s",s+1); 44 if(s[1]=='H'&&s[2]=='=') break; 45 if(s[1]=='+') continue; 46 double t=-read_double(); 47 scanf("%s",s+1); 48 ull hsh=hashstring(); 49 if(!mp[hsh]) mp[hsh]=++cnt; 50 int id=mp[hsh]; 51 x[id][k]+=t; 52 } 53 if(k!=n+1) scanf("%lf",&val[k]); 54 } 55 inline double _abs(double x){ 56 return x>0?x:-x; 57 } 58 int bl[205]; 59 inline void gauss(){ 60 int i=1,p=1; 61 while(i<=n&&p<=cnt){//第i个元 第p个方程 62 int mxp=p; 63 for(int j=p+1;j<=cnt;++j)//共有cnt个方程 64 if(_abs(x[j][i])>_abs(x[mxp][i])) mxp=j; 65 if(_abs(x[mxp][i])<eps){ 66 ++i; continue; 67 } 68 bl[p]=i; 69 for(int j=i;j<=n+1;++j) swap(x[p][j],x[mxp][j]); 70 for(int j=p+1;j<=cnt;++j){ 71 double f=x[j][i]/x[p][i]; 72 for(int k=i;k<=n+1;++k) x[j][k]-=f*x[p][k]; 73 } 74 ++i; ++p; 75 } 76 for(int i=n;i;--i){ 77 for(int j=bl[i]+1;j<=n;++j) x[i][n+1]-=x[i][j]*ans[j]; 78 if(_abs(x[i][bl[i]])>eps) ans[bl[i]]=x[i][n+1]/x[i][bl[i]]; 79 else ans[bl[i]]=0; 80 } 81 } 82 int main(){ 83 //freopen("knight1.in","r",stdin); 84 //freopen("no.out","w",stdout); 85 scanf("%d",&n); 86 for(int i=1;i<=n+1;++i) read(i); 87 gauss(); 88 double ret=0; 89 for(int i=1;i<=n;++i) ret+=ans[i]*val[i]; 90 printf("%.1lf\n",ret+eps); 91 return 0; 92 }
增加最后一个方程,用前面的方程消掉最后一个方程的未知量,
于是最后剩下的就是要求的$h$。
仍然需要一些奇怪的方法来保证每个元都被消掉,或许在一定意义上和我的做法有些异曲同工之妙。
C. 老司机的狂欢
考场上成功转化奇怪的题意:
显然最多同时存在的老司机个数随着$time$单调。
于是二分出一个$time$值,对于不符合条件的一组老司机连边。
于是最多同时存在的老司机数转化为图的最大独立集。
然而这似乎是一个$NPC$问题,于是死掉了。
当时确实想了利用本题的一些特殊性质,比如边的一些特殊性质,然而没有想到。
事实上确实是这样的。
可以将所有的老司机按$x_i$排序,求出时间$time$之后的最终态,设为$y_i$。
只要$y$值单调上升,就可以满足要求,于是问题转化为$LIS$问题。
于是可以二分出答案。
最后的方案构造方法比较难想:
如果每一步都选择最优的转移点,不妨加入点0,那么$LIS$的转移过程中一定可以形成一棵树。
考虑如何选择最优转移点,
首先一定选择深度最大的(即dp值最大),
对于深度相同的点,因为我们最终的字典序是排序后的,所以我们实际关注的是不同的最小值。
于是在每次转移时都将转移点设为该点的父亲,倍增处理出$2^k$级父亲,和这条链上的最小值。
对于深度相同的两个点$a$,$b$,只要求出两个点分别到$lca$链上的最小值,就可以比较。
可以用树状数组维护最优二元组,进行转移。