模拟赛 R24
T2 - 基站修建
题目描述
A 城计划修建一些通讯基站,因此工程师面临着这样一个问题。
A 城可以抽象为一个 \(3\) 行 \(n\) 列的点阵,其中左上点的坐标为 \((1,1)\),右下点的坐标为 \((3,n)\)。一些位置由于诸多问题可能无法修建基站,因此使用字符 # 表示这个位置不能修建基站,使用字符 . 表示允许修建基站。
为了使得基站的通讯覆盖范围尽可能大,工程师希望建造的所有基站两两欧几里得距离的最小值最大。现在他给了你一个参数 \(m\),他想知道,对所有 \(2\le k\le m\),如果在城市中修建 \(k\) 个基站,这个最大值是多少。
特别地,如果无法修建 \(k\) 个基站,则请输出 -1。
Solution
最大子矩阵 的回旋镖正中眉心(。还记得当时第一个设计的状态是 \(f_{i, j, mask}\) 表示选了前 \(i\) 行,\(j\) 个子矩形,\(mask\) 表示第 \(i\) 行的选择状态。那个题因为一行可能分属两个矩形,所以这个状态寄掉了。然后那题的正解状态是 \(f_{i, j, k}\) 表示第一列选到前 \(i\) 行,第二列选到前 \(j\) 行,选了 \(k\) 个子矩形的最大和。
欸,这题的形式不是很像吗。先二分距离的最小值 \(d\),貌似直接套用最大子矩形的状态,单次 \(O(N^3)\) 算出最多能放几个,但是复杂度有点炸。
结果本题正解状态是最大子矩形被否掉的那个。\(f_{i, mask, j}\) 表示前 \(i\) 列(第 \(i\) 列至少选一个),第 \(i\) 列和 \(i - 1\) 列放了没放的状态是 \(mask\),选了 \(j\) 个点的答案。主要是这题根本不存在什么分属两个矩形的麻烦。然后转移时,发现第 \(i\) 列的一个点,离他最近的,在必选第 \(j\) 列的情况下,可能来自第 \(j\) 列,也可能来自第 \(j - 1\) 列,但绝对不会出现在 \(j - 2\) 列(解方程说明即可)。因此转移就从 \(f_{i', mask, j'}(i' < i)\) 处转,这样就可以 \(O(N^3)\) 把所有答案求出来,有 \(2^9\) 的常数,勉强可过。
代码微史。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e2 + 5;
double f[N][3 * N][8][8], c[N][8][8][8], ans[N];
int n, m, mp[N];
inline void chmax(double &x, double y){ x = (x > y ? x : y); }
inline void chmin(double &x, double y){ x = (x < y ? x : y); }
inline double sq(double x){ return x * x; }
int main(){
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for(int i = 0; i < 3; ++i){
for(int j = 1; j <= n; ++j){
char x; cin >> x;
if(x == '.') mp[j] |= (1 << i);
}
}
for(int i = 1; i <= n; ++i){
for(int p = 1; p < 8; ++p){
for(int o = 0; o < 8; ++o){
for(int q = 0; q < 8; ++q){
c[i][p][o][q] = (p == 1 || p == 2 || p == 4 ? 1e18 : (p == 5 ? 2 : 1));
for(int t = 0; t < 3; ++t){
if(p & (1 << t)){
for(int s = 0; s < 3; ++s){
if(o & (1 << s))
chmin(c[i][p][o][q], sqrt(sq(i) + sq(s - t)));
}
for(int s = 0; s < 3; ++s){
if(q & (1 << s))
chmin(c[i][p][o][q], sqrt(sq(i + 1) + sq(s - t)));
}
}
}
}
}
}
}
f[0][0][0][0] = 1e18;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
for(int s = 1; s < 8; ++s){
int k = __builtin_popcount(s);
if(j < k || !((s | mp[i]) == mp[i])) continue;
for(int t = 0; t < 8; ++t){
for(int o = 0; o < 8; ++o){
double w = min(f[i - 1][j - k][t][o], c[1][s][t][o]);
chmax(f[i][j][s][t], w);
}
}
for(int p = i - 2; p >= 0; --p){
for(int t = 0; t < 8; ++t){
for(int o = 0; o < 8; ++o){
double w = min(f[p][j - k][t][o], c[i - p][s][t][o]);
chmax(f[i][j][s][0], w);
}
}
}
for(int t = 0; t < 8; ++t){
chmax(ans[j], f[i][j][s][t]);
}
}
}
}
for(int i = 2; i <= m; ++i){
if(ans[i] == 0) cout << -1 << '\n';
else cout << fixed << setprecision(12) << ans[i] << '\n';
}
return 0;
}
T3 - 双端队列
题目描述
小 Z 学习了一个新的数据结构——双端队列,于是他有了这样的一个 idea。
给出一个长为 \(n\) 的序列 \(a_{1\cdots n}\) 和一个初始为空的双端队列 \(Q\),接下来小 Z 会将这 \(n\) 个数插入到 \(Q\) 中:
- 先将 \(a_1\) 插入至 \(Q\)。
- 接下来从 \(a_2\) 开始,每个数可以选择插入至 \(Q\) 的左端点或右端点。
在插入完 \(n\) 个数之后,小 Z 希望最小化 \(Q\) 中相邻两个数差值的最大值,请你输出这个结果,并构造任意一组操作方案。
Solution
dp 题。首先有一个朴素的状态 \(f_{i, j}\) 表示左边插的是 \(i\),右边插的是 \(j\) 的答案,bfs 转移即可。
发现这个转移一会改第一维,一会改第二维,还要取 \(\max\),难以优化。
那么我们先二分答案 \(d\),最优性转可行性。再改一下状态,\(f_{i, j, 0/1}\) 表示插到前 \(i\) 个,另一边插的是 \(j\),这个 \(i\) 是被插到了左边/右边是否可行。转移是:
很好看啊,转移 1,3 直接不动或者区间赋值为 0,而转移 2,4 则是查一下 \([a_i - d, a_i + d]\) 里有没有 1 然后单点修改,开两个线段树维护二维 dp 即可。
线段树常数大,无法通过。但是有常数更小的做法,我们直接用两个 set 维护 \(f_{i, 0/1}\) 中有哪些值为 1,转移 1,3 就是什么都不干或者 clear,转移 2,4 就是查询 \([a_i - d, a_i + d]\) 中有没有数然后插入一个新数,都是好维护的。
关于输出方案,先找到一个为 1 的 \((n, j, d)\)。我们发现转移 1,3 形式很好,可以不用记录。那么我们再每次转移 2,4 时记录一下 \(pre_{i, 0/ 1}\) 就行。如果一个 \(i\) 的 pre 是 0 或者此时的 \(j \ne i - 1\) 的话,就意味着它是由转移 1,3 而来的,直接跳到 \((i - 1, j, d)\),否则我们跳到 \((i - 1, pre_{i, d}, d \oplus 1)\)。
时间复杂度 \(O(n \log n \log V)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int pre[N][2], a[N], n;
char ans[N];
struct node{
int vl, id;
bool operator < (const node &b) const {
return vl < b.vl;
}
};
set<node> s[2];
bool check(int k){
for(int i : {0, 1}) s[i].clear(), s[i].insert({a[1], 1});
for(int i = 2; i <= n; ++i){
bool fl[2] = {0};
for(int j : {0, 1}){
pre[i][j] = 0;
auto itl = s[j ^ 1].lower_bound({a[i] - k, 0}), itr = s[j ^ 1].upper_bound({a[i] + k, 0});
if(itl != itr){
fl[j] = 1;
pre[i][j] = itl -> id;
}
}
if(abs(a[i] - a[i - 1]) > k) s[0].clear(), s[1].clear();
for(int j : {0, 1}){
if(fl[j]) s[j].insert({a[i - 1], i - 1});
}
}
return s[0].size() || s[1].size();
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
int T, op;
cin >> T >> op;
while(T--){
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
int l = 1, r = 1e9;
while(l < r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l;
if(op == 1){
cout << '\n';
check(l);
int p, d;
if(!s[0].empty()) p = s[0].begin() -> id, d = 0;
else p = s[1].begin() -> id, d = 1;
for(int i = n; i >= 2; --i){
ans[i] = (d ? 'R' : 'L');
if(p == i - 1 && pre[i][d]) p = pre[i][d], d ^= 1;
}
for(int i = 2; i <= n; ++i) cout << ans[i];
}
cout << '\n';
}
return 0;
}
T4 - 树上操作(Qoj12402)
题目描述
小 Z 得到了两棵均包含 \(n\) 个节点的无根树 \(T_1,T_2\),节点编号均为 \(1\sim n\),现在他想通过一些树上操作将 \(T_1\) 变成 \(T_2\),一次树上操作如下:
- 选择四个互不相同的正整数 \(A,B,C,D\),满足 \((A,B),(B,C),(C,D)\) 之间均有边直接相连,换言之,需要选择一条长为 \(3\) 的简单路径。
- 将三条边 \((A,B),(B,C),(C,D)\) 删去,并添加新的三条边 \((u_1,v_1),(u_2,v_2),(u_3,v_3)\),其中这三条边的端点均来自于集合 \(\{A,B,C,D\}\)。
现在小 Z 希望你能帮助他构造一种操作方案,使树 \(T_1\) 变化为 \(T_2\),或者告诉他不存在合法的操作方案。
称两棵树相同,当且仅当这两棵树的边集完全相同。
请在 \(n^2\) 的操作次数内完成。
Solution
考虑两颗树都是链的情况。发现这等价于将一个序列排序。我们这个树上操作显然可以完成选两个相邻点,然后交换他们。如果这两个点在中间就把它相邻的四个点都选上,在边界特判一下即可。然后我们就用只依赖邻相交换的冒泡排序即可。这一部分操作次数是 \(\frac{n(n - 1)}{2}\)。
现在考虑 \(T_1\) 不是链,\(T_2\) 是链的情况。这相当于我们要先把 \(T_1\) 变成链。首先如果 \(T_1\) 是菊花,根本找不出四个点做操作,无解。那么现在我们在 \(T_1\) 中找一条极长链(点数 \(\ge 4\))。那么这条链上会有一些点直接挂在上面,我们考虑把他们移动到链的末尾。注意到,我们可以选一个点和他链上后面的三个点,一次把它向后移动两格。最后如果这个点与链尾差 1 格,那么我们就类似地选它链上相邻的前三个点,把它向后移一格。这样就调整好了。这样的操作次数最多是 \(1 + 1 + 2 + 2 + \cdots + \frac{n}{2} + \frac{n}{2} + \frac{n}{2} = \frac{n^2}{4}\)。
那么现在考虑把 \(T_2\) 由树变为链。发现跟上面的几乎一样,只是倒过来做即可。这里的倒过来并不是指简单的顺序倒序,而是由树变链的情况反推出链变成树的操作序列。注意,我们在变链时,遇到菊花图是没办法的。但是当我们在链变树时,是可以把链变成菊花的,注意特判这一点即可。
这样总的操作次数大概是 \(\frac{n(n - 1)}{2} + 2\times \frac{n^2}{4} = n^2 - \frac{n}{2}\),可以通过。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
typedef array<int, 4> ar4;
typedef array<int, 6> ar6;
typedef pair<ar4, ar6> op;
typedef vector<op> vec;
int n, a[N];
struct Graph{
set<int> e[N];
int rnk[N], p[N], tp, from[N];
bitset<N> on;
void adde(int u, int v){ e[u].insert(v), e[v].insert(u); }
void erae(int u, int v){ e[u].erase(v), e[v].erase(u); }
bool operator == (const Graph &b) const{
for(int i = 1; i <= n; ++i){
if(e[i] != b.e[i]) return 0;
}
return 1;
}
void dfs(int u, int fa){
rnk[u] = rnk[fa] + 1;
for(int v : e[u]){
if(v != fa) dfs(v, u);
}
}
void get(){
for(int i = 1; i <= n; ++i){
if(e[i].size() == 1){
dfs(i, 0);
return;
}
}
}
bool find(int u, int fa){
p[++tp] = u;
bool fl = 0;
for(int v : e[u]){
if(v != fa){
fl = 1;
if(find(v, u)) return 1;
}
}
if(!fl){
if(tp >= 4) return 1;
}
--tp;
return 0;
}
bool tolink(vec &ret, bool f = 0){
auto work = [&]{
on.reset();
for(int i = 1; i <= tp; ++i)
on[p[i]] = 1;
queue<int> q;
for(int i = 1; i <= tp; ++i){
for(int v : e[p[i]])
if(!on[v])
from[v] = i, q.push(v);
}
auto forw = [&](int x){
int np = from[x];
ar4 s = {x, p[np], p[np + 1], p[np + 2]};
for(int i = 0; i < 3; ++i) erae(s[i], s[i + 1]);
ar6 t = {p[np], p[np + 1], p[np + 1], p[np + 2], p[np + 2], x};
for(int i = 0; i < 3; ++i) adde(t[2 * i], t[2 * i + 1]);
from[x] = np + 2;
if(f == 1)
t = {p[np], x, p[np], p[np + 1], p[np + 1], p[np + 2]},
s = {x, p[np + 2], p[np + 1], p[np]};
ret.push_back(op{s, t});
};
auto back = [&](int x){
int np = from[x];
ar4 s = {p[np - 2], p[np - 1], p[np], x};
for(int i = 0; i < 3; ++i) erae(s[i], s[i + 1]);
ar6 t = {p[np - 2], p[np - 1], p[np - 1], x, p[np - 1], p[np]};
for(int i = 0; i < 3; ++i) adde(t[2 * i], t[2 * i + 1]);
from[x] = np - 1;
if(f == 1)
s = {x, p[np - 1], p[np], p[np + 1]},
t = {x, p[np], p[np], p[np - 1], p[np], p[np + 1]};
ret.push_back(op{s, t});
};
while(!q.empty()){
int u = q.front(); q.pop();
while(from[u] < tp - 1) forw(u);
if(from[u] == tp - 1){
back(u), forw(u);
}
assert(from[u] == tp);
on[u] = 1; p[++tp] = u;
for(int v : e[u]){
if(!on[v]){
from[v] = tp;
q.push(v);
}
}
}
};
if(f == 1){
for(int i = 1; i <= n; ++i){
if(e[i].size() == n - 1){
int u = *e[i].begin(), v = *next(e[i].begin()), w = *next(next(e[i].begin()));
erae(i, w); adde(v, w);
p[1] = u, p[2] = i, p[3] = v, p[4] = w; tp = 4;
ret.push_back({{u, i, v, w}, {u, i, i, v, i, w}});
work();
return 1;
}
}
}
for(int u = 1; u <= n; ++u){
if(e[u].size() == 1 && find(u, 0)){
work();
return 1;
}
}
return 0;
}
}e[2];
void trans(Graph &f, Graph &g, vec &ret){
f.get(), g.get();
for(int i = 1; i <= n; ++i) a[f.rnk[i]] = i;
auto swp = [&](int i){ // swap i & i + 1
int l = (i == n - 1 ? i - 2 : (i == 1 ? 1 : i - 1));
ar4 p = {a[l], a[l + 1], a[l + 2], a[l + 3]};
ar6 q;
swap(a[i], a[i + 1]);
for(int j = 0; j < 3; ++j){
q[2 * j] = a[l + j], q[2 * j + 1] = a[l + j + 1];
}
ret.push_back(op{p, q});
};
for(int i = 1; i <= n; ++i){
for(int j = 1; j < n; ++j){
if(g.rnk[a[j]] > g.rnk[a[j + 1]]){
swp(j);
}
}
}
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin >> n;
for(int j : {0, 1}){
for(int i = 1; i < n; ++i){
int u, v; cin >> u >> v;
e[j].adde(u, v);
}
}
vec ans, o;
if(!e[0].tolink(ans)){
if(e[0] == e[1]) cout << "YES\n0\n";
else cout << "NO\n";
return 0;
}
e[1].tolink(o, 1);
trans(e[0], e[1], ans);
reverse(o.begin(), o.end());
ans.insert(ans.end(), o.begin(), o.end());
cout << "YES\n" << ans.size() << '\n';
for(auto [x, y] : ans){
for(int i = 0; i < 4; ++i) cout << x[i] << ' ';
cout << '\n';
for(int j = 0; j < 6; ++j) cout << y[j] << ' ';
cout << '\n';
}
return 0;
}
Summary
失败的 T2 和 T3,哎。做不下去时试试换方向啊。

浙公网安备 33010602011771号