省选模拟测试3
期望得分:\(0+0+20 = 20\)
实际得分: $0+0+20 = 20 $
wdnmd,真就全都不会做。
题目难度大概是: \(T1>T2>T3\),对于我这种顺序开题的菜鸡很不友好。
\(T1\) 没学过三维计算几何,连爆搜的分都没拿到(呜呜呜)。
\(T2\) 神仙期望题,不会溜了溜了。
\(T3\) 只打了 \(20\) 分的暴力,虽然往正解的方向想过,但最后还是没想出来。
T1 通技进阶 ( gentech)
题意描述
给你三维平面上的 \(n\) 条直线,请找出一个直线的集合,使得这个集合中的任意两条直线都有交点。输出这个集合的最大大小。保证没有两条直线重合,没有直线垂直与 \(x\) 轴。
数据范围:对于 \(30\%\) 的数据,\(n\leq 16\)。 对于 \(60\%\) 的数据,\(n\leq 80\)。 对于 \(100\%\) 的数据, \(n\leq 1000\)。
solution
直接搬题解。
解法一: \(O(2^nn^2)\)
暴力枚举每条直线是否在答案集合中,然后判断两两直线是否相交。
判断直线相交可以用解方程的方法求解。
解法二:随机算法
随机算法没有前途
我们对于 \(i,j\) 两个点,如果第 \(i\) 条直线和第 \(j\) 条直线有交点,则 \(i,j\) 之间有连边。
然后我们就得到了一个无向图,问题就可以转化为求这个无向图的最大团(导出子图为完全图,即选出的子集内的点两两连边)。
对于求无向图的最大团问题,有一个经典的随机做法。每次随机一个排列 \(P\) ,然后按照 \(P_1-P_n\) 的顺序,每次尝试将 \(P_i\) 加入到最大团集合中,如果 \(P_i\) 和集合中的点都有连边则成功加入,否则不加入。
将上述算法重复若干次(大于1000次左右)就可以得到答案,对于 \(n\leq 80\) 的数据没有太大的问题。
据说考试的时候,有人用这个算法水到了 \(100\) 分, ORZ。
解法三:\(O(n^2logn)\)
三维空间内的直线比较难处理,我们可以将三维空间的直线转化为二维面内的动点。具体地,考虑到没有直线垂直于 \(𝑥\) 轴,我们用动平面 \(𝑥 = 𝑡\) 来截取直线,不难发现,无论平面怎么移动,每个直线和平面都有且仅有一个交点。
如果将动平面的位置看成时间,那么每个交点都是一个动点,并且这个动点的移动速度是不变的。容易算出每个点在 \(0𝑠\) 时所在的坐标和每秒的移动速度。
若一个集合中的所有动点两两相交,那么一定有两种情况之一:
- 在某一时刻全部交于一点;
- 对应的三维直线共面,且不存在两个直线平行。
第一种情况我们枚举其中一个点,然后将其他能与它相交的点按照相交时间排序,计算出对应时间最多能有几个点交于一点即可。
第二种情况的处理比较巧妙,我们考虑对应的这些三维直线所在的同一个平面,这个面和动平面 \(𝑥 = 𝑡\) 的公共部分一定是一条直线,并且随着动平面 \(𝑥 = 𝑡\) 的移动,两个平面相交部分的直线也随之 平移。那么对应到平面上,就是这些动点必须时时刻刻在一条直线上,而且这条直线的斜率不变(用一个平面截两个平行平面,截得的直线平行)。
于是我们仍然枚举其中一个点作为原点,并且将其他能和这个点相交的点,按照 相对于原点的速度极角排序。注意,如果两个动点能相交,那么它们的连线斜率是不变的(可以考虑对应的两条三维直线所在的公共平面)。因此相对于原点的速度的极角序相同的点一定是共线的。
于是我们把极角序相同的点一起取出来,这些点构成的点集一定就两两相交了(排除平行的情况)。
注意到可能会有相对于原点的速度方向 相反的情况,但是我们注意到,如果取最左端或者最右端的点作为原点,又限制了都与原点相交,因此一个合法的点集一定存在一个点作为原点,其他点的速度方向相同,答案会统计完整。因此直接按照极角序排是没有问题的。
这里要注意排除两个动点始终平行的情况。我们只要在极角序相同的时候另外按照 𝑥 坐标为第一关键字、𝑦 坐标为第二关键字排序即可,运动速度相同的动点始终是平行的。
时间复杂度 \(𝑂(𝑛^2log𝑛)\)。
code:
//咕咕咕
T2 序列合并 (sequence)
题意描述
有一个序列,初始化为空。你会不停地网序列末尾添加一个 \([1,m]\) 的随机整数。
任意时刻:
- 若序列末尾的两个整数相同(记为 \(x\))且小于 \(t\),则这两个数合并为 \(t+1\)
- 若序列长度为 \(n\), 且无法合并,则操作结束。
求操作结束后,序列中所有数的和的期望。答案对 \(1e9+7\) 取模。
数据范围: \(1\leq n,m\leq 1000,m\leq t\leq 10^9\)
solution
神仙期望题,不会写,直接搬题解。
观察整个操作过程,不难发现,从最终状态往前考虑,最先变成最终状态的一定是第一个数(即变成最终的数之后保持不变),然后是第二个数……
这就启发我们去限制第一个数最终是什么,然后在限制第一个数保持不变的情况下,去求剩下 \(n-1\) 个数的期望,从而就可以设计出一个 \(DP\)。
首先注意到每个数的上界其实是 \(𝐿 = min(𝑛 + 𝑚 -1,t)\),这样就能保证状态数不太多。
我们记 \(p_{i,j}\) 表示限制序列最长长度为 \(𝑖\),在整个过程中出现过第一个数为 \(𝑗\) 的概率。
注意到第一个数变成 \(𝑗\),可能是第一步直接插入一个 \(𝑗\),也有可能是第一个数和第二个数合并产生的。
第一种情况比较简单,第一步插入 \(𝑗\) 的概率就是 \({1\over m}\times [j\leq m]\)
第二种情况,不难发现前两个 \(𝑗- 1\) 合并为 \(𝑗\) 之前,这个序列一定只有前面的 \(𝑗 -1\) 两个元素。于是可一将产生 \(j\) 的过程分为两步:首先在第一个位置产生 \(j-1\), 然后在第二个位置产生 \(j-1\), 概率为 \(p_{i,j-1}\times p_{i-1,j-1}\)。
为了限制达到我们枚举的最终状态后,第一个数不变,我们还需要求出一个概率:设 \(𝑞_{i,j}\) 表示限制序列最长长度为\(𝑖\),在第一个位置产生了 \(𝑗\) 的前提 前提下,第一个数不变的概率(条件概率)。
显然第一个数不变,当且仅当第二个位置不会产生 \(𝑗\) 或 \(𝑗 = 𝐿\), 那么 \(q_{i,j} = 1-[j<L] \times p_{i-1,j}\)
然后再设 \(g_{i,j}\) 表示限制序列最长长度为 \(𝑖\),第一个位置产生了 \(𝑗\) 且第一个数不变的前提下,最终序列各数之和的期望(条件期望)。
记 \(𝑎𝑛𝑠_i\) 表示限制序列最长长度为 \(𝑖\) 的情况下,最终序列各数之和的期望。题目所求记 \(𝑎𝑛𝑠_n\) 。
那么 \(𝑎𝑛𝑠_i = \displaystyle\sum_{j} p_{i,j} \times q_{i,j}\times g_{i,j}\)
考虑怎么求 \(𝑔_{i,j}\),在限制了第一个位置产生了 \(𝑗\) 且第一个数不变的情况下,最终第一个数显然就是 \(𝑗\)。
简单写出推导过程(注意 \(𝑗 = 𝐿\) 特判):
于是我们记 \(f_{i,j}\) 表示限制序列最大长度为 \(i\), 在第一个位置产生了 \(j\) 的前提下,最终序列个数之和的期望,那么: \(g_{i,j} = j + {ans_{i-1}-p_{i-1,j}\times f_{i-1,j}\over q_{i,j}}\)。
现在的问题就是怎么求 \(𝑓_{i.j}\) (限制序列最大长度为 \(𝑖\),在第一个位置产生了 \(𝑗\) 的前提下,最终序列各数之和的期望)。
只需要分为第一个位置变化不变化讨论即可,即:\(f_{i,j} = q_{i,j}\times g_{i,j} + (1-q_{i,j}\times f_{i.j+1})\)
注意我们可以直接求 \(𝑞_{i,j}\times g_{i,j}\) 而不求 \(𝑔_{i,j }\) ,从而避免转移时求逆元,做到 \(𝑂(𝑛𝑚)\)的时间复杂度
code
\\不会写,咕咕咕
T3 机器决斗 fittest
题意描述
敌方有 \(n\) 台机器人,每台的攻击力为 \(A_i\), 护甲值为 \(D_i\),我方只有一台机器人。攻击力为 \(ATK\), 战斗为回合制,每回合进程如下:
- 我方选择对方的某台机器人冰攻击,令其护甲值减少 \(ATK\), 若护甲值 \(\leq 0\), 则被破坏掉。
- 敌方每台未被破坏的机器人攻击我方机器人造成 \(A_i\) 点的损失。
但是在第一回合开始前,某两台敌方的机器人被提前破坏掉了。问最好的情况下,我方基地会受到的损失最少为多少。
数据范围:
- 子任务一(20分) \(n\leq 10000\)
- 子任务二(20分)\(D_i\leq ATK\)
- 子任务三(60分) \(3\leq n\leq 3\times 10^5, A_i,D_i\leq 10^4, ATK\leq 10^4\)
solution
\(T3\) 貌似是今天三道题里面最可做的一道题了。
我们先不考虑提前破坏掉的两台机器的情况,那么我们最优策略肯定是按照一定的顺序,依次打完所有的机器人。
记 每个机器人需要打多少次会被破坏掉,\(t_i = \lceil {D_i\over ATK}\rceil\)。
一个贪心思路就是把所有的机器人按 \(t_i\over A_i\) 排序,下面我们来证明一下这个思路为什么是对的。
考虑用邻项交换法来证明,对于两个相邻的机器人 \(i,j\),我们考虑将 \(i\) 放在前面和将 \(j\) 放在前面的代价。
若 \(i\) 放在 \(j\) 前面则代价为:\((t_i-1)\times A_i + (t_i+t_j-1) \times A_j\)
若 \(j\) 放在 \(i\) 前面则代价为:\((t_j-1)\times A_j + (t_i+t_j-1)\times A_i\)
所以 \(i\) 放在 \(j\) 前面更优,则需要满足:
\((t_i-1)\times A_i + (t_i+t_j-1)\times A_j < (t_j-1)\times A_j + (t_i+t_j-1)\times A_i\)
化简可得: \(t_i\times A_j < t_j\times A_i\) 即: \({t_i\over A_i} < {t_j\over A_j}\) 。
我们以 \(t_i\over A_i\) 为第一关键字排序,就可以得到我们最优策略下的攻打顺序。
按照这个攻打顺序我们可以求出不考虑提前破坏的情况下,我方基地受到的伤害最少为多少,记为 \(ans\)。
假设提前破坏掉 \(i,j\) \((i<j)\) 两个机器人, 考虑我方基地受到的伤害减少了多少。
记 \(s_i\) 为提前破坏掉第 \(i\) 个机器人基地受到的伤害的减少量,则有:
\(s_i = \displaystyle A_i\times \left[\left(\sum_{j=1}^{i}t_i\right)-1\right] + t_i\times \sum_{j=i+1}^{n} A_j\)
如果提前破坏掉 \(i,j\) 两个机器人,那么伤害减少量可以表示为:\(f(i) = s_i+s_j -t_i\times A_j\), 我们要最大化这个\(f(i)\) 的值。
你会发现这个柿子长得特别像个斜率优化的柿子,移项可得: \(s_j = t_i\times A_j -s_i+f(i)\) 。
我们把每个 \(j\) 映射成平面上 \((A_j,s_j)\) 一个点,问题就转化为对于每个斜率 \(p_i\),求过点 \((A_j,s_j)\) 的最大的截距,对于所有的 \(j\) 维护一个上凸壳即可。
但斜率和横坐标不一定是单调递增的,这个怎么解决呢?
第一种做法就是从大到小枚举每个 \(i\), 用 \(set/平衡树\) 动态维护上凸壳,在凸壳上二分就可以得到答案。
第二种做法就是 \(CDQ\) 维护凸包,对于所有的 \(j\) \((j>i)\) 都可以用来更新 \(f(i)\) ,这个很符合 \(CDQ\) 的模型, 具体来说就是对于当前的 \([l,r]\) 区间,设分界点为 \(mid\), 那么 \([mid+1,r]\) 这一部分的点都可以转移到 \([l,mid]\) 这一部分的点,因此我们对于 \([mid+1,r]\) 这一部分的点维护一个上凸壳,然后枚举 \([l,mid]\) 这一部分的点 \(i\),在上凸壳中查询最优的决策点 \(j\) 来更新 \(f(i)\) ,(算右边对左边的贡献)。
只需要保证右边的点横坐标单调递增,左边的点斜率单调递减,就可以拿单调队列来维护这个上凸壳了。
时间复杂度 \(O(nlogn)\)
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 3e5+10;
int n,M,ans,maxn;
int suma[N],s[N],sumt[N],q[N];
struct QAQ
{
int d,t,a;
}p[N];
struct node
{
int x,y,id,k;
}a[N],b[N];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
bool cmp(QAQ a,QAQ b)
{
return a.t * b.a < b.t * a.a;
}
bool compx(node a,node b)
{
return a.x < b.x;
}
bool compk(node a,node b)
{
return a.k > b.k;
}
int X(int i){return a[i].x;}
int Y(int i){return a[i].y;}
double slope(int i,int j)
{
return (double)(Y(j)-Y(i))/(X(j)-X(i));
}
void cdq(int L,int R)
{
if(L == R) return;
int mid = (L + R)>>1;
cdq(L,mid); cdq(mid+1,R);
sort(a+L,a+mid+1,compk);//左侧的点按斜率排序
sort(a+mid+1,a+R+1,compx);//右侧的点按横坐标排序
int h = 1, t = 0;
for(int i = mid+1; i <= R; i++)//右侧的点来建凸壳
{
while(h < t && slope(q[t-1],q[t]) <= slope(q[t-1],i)) t--;
q[++t] = i;
}
for(int i = L; i <= mid; i++)//左侧的点在凸壳中查询
{
while(h < t && slope(q[h],q[h+1]) >= a[i].k) h++;
maxn = max(maxn,a[i].y+a[q[h]].y-a[i].k*a[q[h]].x);
}
}
signed main()
{
freopen("fittest.in","r",stdin);
freopen("fittest.out","w",stdout);
n = read(); M = read();
for(int i = 1; i <= n; i++)
{
p[i].a = read();
p[i].d = read();
p[i].t = (p[i].d/M)+1;
if(p[i].d % M == 0 && p[i].d) p[i].t--;
}
sort(p+1,p+n+1,cmp);
for(int i = 1; i <= n; i++) sumt[i] = sumt[i-1] + p[i].t;
for(int i = n; i >= 1; i--) suma[i] = suma[i+1] + p[i].a;
for(int i = 1; i <= n; i++) ans += p[i].a * (sumt[i]-1);
for(int i = 1; i <= n; i++) s[i] = p[i].a * (sumt[i]-1) + p[i].t * suma[i+1];
for(int i = 1; i <= n; i++)
{
a[i].k = p[i].t;
a[i].x = p[i].a;
a[i].y = s[i];
a[i].id = i;
}
cdq(1,n);
printf("%lld\n",ans-maxn);
fclose(stdin); fclose(stdout);
return 0;
}

浙公网安备 33010602011771号