模拟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 }
B. 炼金术士的疑惑

增加最后一个方程,用前面的方程消掉最后一个方程的未知量,

于是最后剩下的就是要求的$h$。

仍然需要一些奇怪的方法来保证每个元都被消掉,或许在一定意义上和我的做法有些异曲同工之妙。

 

 

 

C. 老司机的狂欢

考场上成功转化奇怪的题意:

显然最多同时存在的老司机个数随着$time$单调。

于是二分出一个$time$值,对于不符合条件的一组老司机连边。

于是最多同时存在的老司机数转化为图的最大独立集。

然而这似乎是一个$NPC$问题,于是死掉了。

 

当时确实想了利用本题的一些特殊性质,比如边的一些特殊性质,然而没有想到。

事实上确实是这样的。

可以将所有的老司机按$x_i$排序,求出时间$time$之后的最终态,设为$y_i$。

只要$y$值单调上升,就可以满足要求,于是问题转化为$LIS$问题。

于是可以二分出答案。

最后的方案构造方法比较难想:

如果每一步都选择最优的转移点,不妨加入点0,那么$LIS$的转移过程中一定可以形成一棵树。

考虑如何选择最优转移点,

首先一定选择深度最大的(即dp值最大),

对于深度相同的点,因为我们最终的字典序是排序后的,所以我们实际关注的是不同的最小值。

于是在每次转移时都将转移点设为该点的父亲,倍增处理出$2^k$级父亲,和这条链上的最小值。

对于深度相同的两个点$a$,$b$,只要求出两个点分别到$lca$链上的最小值,就可以比较。

可以用树状数组维护最优二元组,进行转移。

posted @ 2019-10-16 10:44  skyh  阅读(176)  评论(0编辑  收藏  举报