APIO2021 简要题解

command_block

2021-07-12 15:10:31

Personal

# T1 [六边形领域](https://www.luogu.com.cn/problem/P7598) 阴间题,当不存在。 有好哥哥教我做吗 /kel # T2 [雨林跳跃](https://www.luogu.com.cn/problem/P7599) **题意** : 给出一个长度为 $n$ 的排列 $A$ 。 当玩家在位置 $u$ ,当且仅当满足下列两个条件之一,能一步移动到 $v$。 - $A_v$ 是 $A_u$ 左侧第一个大于 $A_u$ 的。 - $A_v$ 是 $A_u$ 右侧第一个大于 $A_u$ 的。 记 $dis(u,v)$ 表示从 $u$ 出发到达 $v$ 所需的最小步数。若不能办到,则定义为 $+\infty$。 每次询问给出 $l_1,r_1,l_2,r_2$ ,求 $\min_{u\in[l_1,r_1],v\in[l_2,r_2]}dis(u,v)$。 $n\leq 2\times 10^5,q\leq 10^5,\ l_1\leq r_1<l_2\leq r_2$ ,时限$\texttt{3s}$。 ------------ 先用单调栈求出每个位置的两条出边。 - 求单个 $dis(u,v)$ 途中的权值必然单调增加,若到达 $A_p>A_v$ 则不再能到达 $A_v$。 若 $\max A_{[u,v)}>A_v$ 则 $u$ 必不能到达 $v$ ,反之则必然存在到达 $v$ 的方案,证明略。 每一步只有两种决策,若目的地 $>A_v$ 则排除。 若两种决策都可选,则贪心地选择更大的一个。 - **证明** : 对于 $u$ 左右两侧的目的地 $l,r$ ,它们的后继都不可能经过区间 $[l,r]$ ,于是可以缩成一个点考虑。 命题等价于,在同一个位置 $u$ ,将 $A_u$ 在不超过 $A_v$ 的前提下尽量增大,是否更优。答案是肯定的。 观察我们的决策,先会不断选择两侧更大的一个,然后会不断向右跳。 用倍增加速求解即可。 - $u\in[l_1,r_1]$ 时 若 $[l_1,r_1]$ 中的某个 $u$ 走起一步却仍然在 $[l_1,r_1]$ 内,必不优。 于是也可以将 $[l_1,r_1]$ 缩成一个点考虑。取有解(满足 $\max A_{u\sim l_2-1}<A_v$ )的最大的起点即可。 可以 ST 表加二分求解出最长合法后缀,再取出其中的最大值。 - $v\in[l_2,r_2]$ 时 有解的条件是 $\max A_{[l_2,r_2]}>\max A_{[r_1,l_2)}$。 仍然用上一部分的方法确定 $u$ 。 讨论结束的最后一步 $p\rightarrow v$。 若 $p\in(r_1,l_2)$ ,则必然是 ${\rm arg}\max A_{(r_1,l_2)}$ 。 若 $p\in[l_1,r_1]$ ,则答案为 $1$ ,判据为 $A_u>\max A_{(r_1,l_2)}$。 若 $p\in[1,l_1)$ ,则必然是 $A_{[1,l_1)}$ 有解的最大值,同样二分求出。 复杂度 $O((n+q)\log n)$。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #define MaxN 200500 using namespace std; const int INF=1000000000; #define Pr pair<int,int> #define fir first #define sec second #define mp make_pair struct ST{ Pr t[20][MaxN]; int lg2[MaxN]; Pr qry(int l,int r){ int k=lg2[r-l+1]; return max(t[k][l],t[k][r-(1<<k)+1]); } void Init(int n,int *x) { for (int i=1;i<=n;i++)t[0][i]=mp(x[i],i); for (int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1; for (int j=1;(1<<j)<=n;j++) for (int i=1;i+(1<<j)-1<=n;i++) t[j][i]=max(t[j-1][i],t[j-1][i+(1<<j-1)]); } }T; int h[MaxN],stk[MaxN],tl[MaxN],tr[MaxN] ,f[20][MaxN],fr[20][MaxN],fl[20][MaxN]; void init(int n, std::vector<int> _H) { for (int i=1;i<=n;i++)h[i]=_H[i-1]; T.Init(n,h); h[0]=INF; for (int i=1,top=0;i<=n;i++){ while(top&&h[i]>h[stk[top]])top--; tl[i]=stk[top];stk[++top]=i; } for (int i=n,top=0;i;i--){ while(top&&h[i]>h[stk[top]])top--; tr[i]=stk[top];stk[++top]=i; } for (int i=1;i<=n;i++){ f[0][i]=(h[tl[i]]>h[tr[i]]) ? tl[i] : tr[i]; fr[0][i]=tr[i];fl[0][i]=tl[i]; } for (int j=1;j<=17;j++) for (int i=1;i<=n;i++){ f[j][i]=f[j-1][f[j-1][i]]; fr[j][i]=fr[j-1][fr[j-1][i]]; fl[j][i]=fl[j-1][fl[j-1][i]]; } } int minimum_jumps2(int u, int v) { int ans=0; for (int k=17;k>=0;k--) if (h[f[k][u]]<=h[v]) {u=f[k][u];ans+=(1<<k);} for (int k=17;k>=0;k--) if (h[fr[k][u]]<=h[v]) {u=fr[k][u];ans+=(1<<k);} return ans; } int minimum_jumps3(int u, int v) { int ans=0; for (int k=17;k>=0;k--) if (h[f[k][u]]<=h[v]) {u=f[k][u];ans+=(1<<k);} for (int k=17;k>=0;k--) if (h[fl[k][u]]<=h[v]) {u=fl[k][u];ans+=(1<<k);} return ans; } int minimum_jumps(int l1, int r1, int l2, int r2) { l1++;r1++;l2++;r2++; int lim=T.qry(l2,r2).fir; if (T.qry(r1,l2-1).fir>lim)return -1; int l=l1,r=r1,mid; while(l<r){ mid=(l+r)>>1; if (T.qry(mid,l2-1).fir<lim)r=mid; else l=mid+1; } int u=T.qry(r,r1).sec,ans=INF; if (h[u]>=T.qry(r1,l2-1).fir)return 1; ans=min(ans,minimum_jumps2(u,T.qry(r1,l2-1).sec)+1); if (1<l1){ int l=1,r=l1-1,mid,lim2=T.qry(l1,l2-1).fir; while(l<r){ mid=(l+r+1)>>1; if (T.qry(mid,l1-1).fir>lim2)l=mid; else r=mid-1; } int v=T.qry(l,l1-1).sec; if (lim2<h[v]&&h[v]<lim) ans=min(ans,minimum_jumps3(u,v)+1); }return ans; } ``` # T3 [封闭道路](https://www.luogu.com.cn/problem/P7600) - 重题 : [CF1119F Niyaz and Small Degrees](https://www.luogu.com.cn/problem/CF1119F) **题意** : 给出一棵 $n$ 个点的树,边有边权。 对于每个 $k=1\sim n$ ,要求选出一个边的集合,使得每个点连接的未选择的道路的个数 $\leq k$ ,且这个边集的边权和最小。 $n\leq 10^5$ ,时限$\texttt{1s}$。 ------------ > 树的性质如此特殊,不要先入为主大搞模拟费用流,即使搞了,也应该即使止损,更不能由此支配对难度的判断。 最小化删去的边权和,等同于最大化保留的边权和。 考虑对于单个 $k$ 如何求解。 进行 DP ,记 $f_{u,0/1}$ 表示在 $u$ 的子树内, $u$ 与父亲之间的边“保留/不保留”,所选的最大边权和。 在处理 $u$ 点时,对于直接儿子 $v$ ,若选择边 $(u,v)$ 则贡献为 $f_{v,1}+w_{(u,v)}$ ,否则为 $f_{v,0}$。 对于 $f_{v,1}+w_{(u,v)}\leq f_{v,0}$ 的 $v$ ,显然选择后者。 对于其余的 $v$ ,若不足 $k$ 个,则全选前者。否则选增益较大的前 $k$ 个。(用排序处理) 对于 $f_{u,1}$ ,则相当于预支了一个度数,选前 $k-1$ 个即可。 接下来对所有 $k$ 进行求解。 当 $k$ 较大时限制会很松,具体地,对于度数 $\leq k$ 的点,可以看做没有限制。 我们将度数 $\leq k$ 的点称为小点,度数 $>k$ 的点称为大点。 我们只对大点进行 DP 来决策。DP 所涉及的总点数是 $O(\sum_{u}\sum_{i=1}^n[d_i>i])=O(\sum d_i)=O(n)$ ,可以保证。 对于小点,内部的边全部选择。“边界”上的边则加入度数 $>k$ 的点的 DS(用以维护排序)中。 将 $k$ 从小大处理,对于某条边,先为小点内部的边,然后一头小一头大(插入 DS 中),最后为大点内部的边(从 DS 中删除,等到 DP 时再加入)。 我们需要一个如下的数据结构 : - 插入删除 - 求前 $k$ 大的和。其中 $k$ 单调增加。 这可以用动态开点权值线段树来完成。时空复杂度为 $O(n\log n)$。 ```cpp ```