\(\cal T_1\) 小学生物理题
Description
假设你在做电学实验:给定 \(n+2\) 个节点在数轴上排成一排,编号 \(0,1,\dots,n+1\),节点 \(0\) 在 \(-10^{100}\) 的位置,节点 \(1\) 在 \(0\) 的位置,对于 \(i=1,2,\dots,n-1\),节点 \(i+1\) 在节点 \(i\) 正方向距离 \(s_i\) 的位置,节点 \(n+1\) 在 \(10^{100}\) 的位置。对于 \(i=0,1,\dots,n\),节点 \(i\) 与节点 \(i+1\) 之间有一条编号为 \(i\) 的导线。
已知恰有一条导线断掉了,你需要找到这条导线并将其修复。你当前在原点,可以在数轴上任意移动,移动 \(L\) 单位长度的代价是 \(L\)。若你当前在某个节点 \(i=1,2,\dots,n\),则可以花费 \(d_i\) 的代价检查其与节点 \(0\) 是否连通。修复导线 \(i\) 的代价是 \(f_i\).
求在所有策略中,所需代价之和的最大值(即最坏情况)的最小值是多少?
\(2\leqslant n\leqslant 3000,0\leqslant s_i,d_i,f_i\leqslant 10^9\).
Solution
首先考虑区间 \(\mathtt{dp}\),记 \(dp(i,j,0/1)\) 表示断掉的导线在区间 \([i,j]\)(指节点 \(i,j\) 之间的导线),当前在位置 \(i/j\) 的答案。那么有
其中 \(s_i\) 表示点 \(i\) 到点 \(1\) 的距离。答案就是 \(dp(0,n+1,0)\).
当时写到这里我就寄了,猜了一个决策单调性但是没猜对 /kk.
事实上,这个 \(\mathtt{dp}\) 数组有一个很好的性质 —— \(dp(S,0)\geqslant dp(T,0),T\subseteq S\)。\(1\) 的情况同理。
我们转回之前的 \(\mathtt{dp}\) 转移(这里以第一个转移为例),发现其瓶颈在于中间那个 "\(\max\)",不过依据性质,有 \(dp(i,k,1)\leqslant dp(i,k',1),dp(k,j,0)\geqslant dp(k',j,0),k\leqslant k'\),于是这两个函数一个不增,一个不降,如果它们画出的图象存在交点,不妨设 \(p\) 为小于等于这个交点的 \(x\) 坐标的 最大的整数,那么有 \(k\leqslant p\) 时,由 \(dp(k,j,0)+s_k+d_k\) 进行贡献;反之,由 \(dp(i,k,1)+s_k+d_k\) 进行贡献。
到此,我们成功地将取 \(\max\) 转化成了查询满足 \(k\leqslant p(i,j)\),且右端点为 \(j\) 的 \(dp(k,j,0)+s_k+d_k\) 的 \(\min\),和满足 \(k>p(i,j)\),且左端点为 \(i\) 的 \(dp(i,k,1)+s_k+d_k\) 的 \(\min\).
不妨从小到大枚举 \(j\),从大到小枚举 \(i\) 进行 \(\mathtt{dp}\),那么
- 由于随着 \(i\) 的减小,\(p(i,j)\) 也在减小(这是因为对于同一个 \(k\),\(dp(k,j,0)\) 虽然不变,但是 \(dp(i,k,1)\) 随着 \(i\) 减小而增大,对应在函数图象上就是 \(y\) 值处处不小于之前的 \(y\) 值,交点自然就往左移了),所以之前合法的 \(dp(k,j,0)+s_k+d_k\) 可能不再合法。于是对每个 \(j\) 都维护一个单调队列,不过由于处理过的 \(j\) 不会再被处理,所以也可以直接将用过的队列清空;
- 随着 \(j\) 的增大,\(p(i,j)\) 也在增大。剩下的内容就是复读上一个讨论了,对每个 \(i\) 都维护一个单调队列即可。
Code
/*
* 这回只花了 1145141919810 min 就打完了。
* 真好。记得多手造几组。Codeforces Gym 题拍什么拍?
*/
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 3005;
const ll infty = 1e17;
int n;
ll dp[maxn][maxn][2];
ll s[maxn], d[maxn], f[maxn];
struct _queue {
int q[maxn], hd, tl; ll v[maxn];
void clear() { hd=1, tl=0; }
void ins(int p, const ll& val) {
while(hd<=tl && v[tl]>=val) --tl;
q[++tl]=p, v[tl]=val;
}
ll calc(int p) {
while(hd<=tl && q[hd]<p) ++hd;
return hd<=tl? v[hd]: infty;
}
} A[maxn], B[maxn];
struct __queue {
int q[maxn], hd, tl; ll v[maxn];
void clear() { hd=1, tl=0; }
void ins(int p, const ll& val) {
while(hd<=tl && v[tl]>=val) --tl;
q[++tl]=p, v[tl]=val;
}
ll calc(int p) {
while(hd<=tl && q[hd]>p) ++hd;
return hd<=tl? v[hd]: infty;
}
} C, D;
void update(int i,int j) {
A[i].ins(j, dp[i][j][1]+s[j]+d[j]);
B[i].ins(j, dp[i][j][1]-s[j]+d[j]);
C.ins(i, dp[i][j][0]+s[i]+d[i]);
D.ins(i, dp[i][j][0]-s[i]+d[i]);
}
signed main() {
freopen("physics.in","r",stdin);
freopen("physics.out","w",stdout);
n=read(9);
for(int i=2;i<=n;++i) s[i] = s[i-1]+read(9);
for(int i=1;i<=n;++i) d[i]=read(9);
for(int i=0;i<=n;++i)
f[i]=read(9),
A[i].clear(), B[i].clear();
for(int j=1;j<=n+1;++j) {
dp[j-1][j][0] = dp[j-1][j][1] = f[j-1];
C.clear(), D.clear(), update(j-1,j); int p = j-1;
for(int i=j-2;i>=0;--i) {
while(p>i && dp[p][j][0]<dp[i][p][1]) --p;
dp[i][j][0] = min(C.calc(p), A[i].calc(p+1))-s[i];
dp[i][j][1] = min(D.calc(p), B[i].calc(p+1))+s[j];
update(i,j);
}
} print(dp[0][n+1][0],'\n');
return 0;
}
\(\cal T_2\)
Description
Solution
Code
\(\cal T_3\) 中学生物理题
Description
你需要在平面上放 \(n\) 块双面镜子,它们均与坐标轴成 \(45°\) 角摆放。它们的位置和摆放方向给定,由点坐标 \((x_i,y_i)\) 和字符 /
或 \
描述。
从沿坐标轴平行的方向射出一束激光,激光经过镜子时会顺/逆时针旋转 \(90°\),最终可能会陷入循环或无限地向某个方向射出。
你需要放 \(4\) 种类型的镜子,要求对于每个可能的循环,一次循环内经过每种类型的镜子的次数相同且为偶数。请构造一种方案,或者证明不存在这种方案。
\(n\leqslant 10^5\).
Solution
神秘建图题。
考虑一面镜子能参与的循环至多只有两个,所以总循环数量是 \(\mathcal O(n)\) 级别的。首先考虑如何找出循环。将每面镜子离散化成两个点,分别表示镜子的两面,再进行最近镜子的连边:具体地,对于 /
的镜子的左部点,它需要找到在此镜子上面/左边最近的镜子,再与其对应离散化点连边。对于没有镜子的情况,建一个虚点连边(注意全局只有 一个虚点)。
如何对镜子染色?把镜子看作一条边,它将镜子拆成的点所属连通块连接起来;同时将连通块看作点。分析一下这张图的构造,实际上就是环与环(可以发现,环也可以和自己连边)、环与虚点、虚点与自己有边。可以证明,所有点的度数均为 \(8\) 的倍数是存在方案的充分必要条件。因为必要性比较显,这里只证明充分性,其实这也是方案的构造方法:考虑利用欧拉回路,由于经过每条边仅一次,那么对于点 \(u\),遍历时每一条入边后都 紧跟着 一条出边,于是可以将图交替染色,就可以将每个点所连的边划分成两个 大小相等 的边集。先分一次,在同色的边集中再分一次即可。
Code
Believe in yourself.