『模拟赛』多校A层冲刺NOIP2024模拟赛06
Rank
比较还行
A. 小 Z 的手套(gloves)
签。
最大值最小,一眼二分答案。双指针 check 一下就完了,复杂度 \(\mathcal{O(n\log n)}\)。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
char ch = getchar();lx x = 0 , f = 1;
for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 1e5 + 5;
const int mod = 10007;
int n, m;
int a[N], b[N];
namespace Wisadel
{
bool Wck(int x)
{
int j = 1;
fo(i, 1, n)
{
while(j <= m && abs(a[i] - b[j]) > x) j++;
if(j > m) return 0;
j++;
}
return 1;
}
short main()
{
freopen("gloves.in", "r", stdin) , freopen("gloves.out", "w", stdout);
// freopen(".err", "w", stderr);
n = qr, m = qr;
fo(i, 1, n) a[i] = qr; sort(a + 1, a + 1 + n);
fo(i, 1, m) b[i] = qr; sort(b + 1, b + 1 + m);
if(n > m)
{
fo(i, 1, n) swap(a[i], b[i]);
swap(n, m);
}
int l = 0, r = 1e9;
while(l < r)
{
int mid = (l + r) >> 1;
if(Wck(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", r);
return Ratio;
}
}
int main(){return Wisadel::main();}
B. 小 Z 的字符串(string)
分讨 dp。
赛时考虑了很久贪心做法,一直没有简单易行的方案,于是想了 dp,但没思路,于是交了假做法 50pts。
正解考虑设 \(f_{i,j,k,0/1/2}\) 表示目前 \(i\) 个 0,\(j\) 个 1,\(k\) 个 2,第 \(i+j+k\) 位为 0/1/2 的方案数。首先特判出不合法的情况,然后直接 dp 就完了,转移限制条件即为当前位与上一位不同,为方便统计坐标差,最后 \(\min\left(f_{num_0,num_1,num_2,0/1/2}\right)\) 除以 2 就是最终答案。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
char ch = getchar();lx x = 0 , f = 1;
for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 5e4 + 5;
const int mod = 10007;
int n;
int num[3], q[3][405], f[205][205][205][3];
string s;
namespace Wisadel
{
short main()
{
freopen("string.in", "r", stdin) , freopen("string.out", "w", stdout);
// freopen(".err", "w", stderr);
cin >> s;
n = s.size(); s = " " + s;
fo(i, 1, n)
if(s[i] == '0') num[0]++, q[0][num[0]] = i;
else if(s[i] == '1') num[1]++, q[1][num[1]] = i;
else num[2]++, q[2][num[2]] = i;
int zc = (n + 1) / 2;
if(num[0] > zc || num[1] > zc || num[2] > zc){puts("-1"); return Ratio;}
memset(f, 0x3f, sizeof f);
f[0][0][0][0] = f[0][0][0][1] = f[0][0][0][2] = 0;
fo(i, 0, num[0]) fo(j, 0, num[1]) fo(k, 0, num[2])
{
int zc = i + j + k;
if(i) f[i][j][k][0] = min(f[i - 1][j][k][1], f[i - 1][j][k][2]) + abs(zc - q[0][i]);
if(j) f[i][j][k][1] = min(f[i][j - 1][k][0], f[i][j - 1][k][2]) + abs(zc - q[1][j]);
if(k) f[i][j][k][2] = min(f[i][j][k - 1][0], f[i][j][k - 1][1]) + abs(zc - q[2][k]);
}
int ans = 1e9;
fo(i, 0, 2) ans = min(ans, f[num[0]][num[1]][num[2]][i]);
ans /= 2;
printf("%d\n", ans);
return Ratio;
}
}
int main(){return Wisadel::main();}
C. 一个真实的故事(truth)
我赛时 A 线段树啦! 被 hack 了,悲(
看到修改查询首先考虑线段树。赛时思路被 T2 略微干扰了下于是考虑记 \(nxt_{i,j}\) 为 \(i\) 位置下一个 \(j\) 种类的坐标,答案为 \(\min\left(\max_{i\in\left[1,n\right]\left(nxt_{i,j}\right)}\right)\),线段树统计答案是 \(\mathcal{O(1)}\) 的,但是修改操作很困难,主要是无法实现动态处理 \(nxt\) 数组,赛时尝试自我 hack 但是失败了,最慢跑了 0.5s。
考虑正解是如何优化的,改为记每个区间所有种类最靠左和最靠右的位置,pushup 时答案只会从 \(ans_{ls}\),\(ans_{rs}\) 和左区间靠右、右区间靠左的所有种类合并出的答案。合并时取出上述至多 \(2\times k\) 个位置,然后双指针找出最小答案即可,合并其它信息比较简单。然后就做完了,\(\mathcal{O(\log n)}\) 修改 \(\mathcal{O(1)}\) 查询,带一个 \(k\) 的常数。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
char ch = getchar();lx x = 0 , f = 1;
for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 5e4 + 5;
const int mod = 10007;
int n, k, m;
int a[N];
int las[N << 2], Lt[N << 2][31], Rt[N << 2][31], cnt[31], ans[N << 2];
namespace Wisadel
{
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define mid ((l + r) >> 1)
void Wpushup(int rt)
{
int res = 1e9, ok = 0; memset(cnt, 0, sizeof cnt);
pii zc[2 * k + 1]; int zcz = 0;
fo(i, 1, k) if(Rt[ls][i]) zc[++zcz] = {Rt[ls][i], i};
fo(i, 1, k) if(Lt[rs][i]) zc[++zcz] = {Lt[rs][i], i};
sort(zc + 1, zc + zcz + 1, [](pii &x, pii &y){return x.fi < y.fi;});
int j = 1;
fo(i, 1, zcz)
{
if(j < i) j = i;
while(j <= zcz && ok < k)
{
if(!cnt[zc[j].se]) ok++;
++cnt[zc[j].se]; ++j;
}
if(ok == k) res = min(res, zc[j - 1].fi - zc[i].fi + 1);
cnt[zc[i].se]--;
if(!cnt[zc[i].se]) ok--;
}
if(res != 1e9) ans[rt] = res;
if(ans[ls]) ans[rt] = min(ans[rt], ans[ls]);
if(ans[rs]) ans[rt] = min(ans[rt], ans[rs]);
fo(i, 1, k)
{
if(Lt[ls][i]) Lt[rt][i] = Lt[ls][i];
else Lt[rt][i] = Lt[rs][i];
if(Rt[rs][i]) Rt[rt][i] = Rt[rs][i];
else Rt[rt][i] = Rt[ls][i];
}
}
void Wbuild(int rt, int l, int r)
{
ans[rt] = 1e9;
if(l == r)
{
Lt[rt][a[l]] = Rt[rt][a[l]] = l;
las[rt] = a[l];
return ;
}
Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
Wpushup(rt);
}
void Wupd(int rt, int l, int r, int x, int v)
{
ans[rt] = 1e9;
if(l == r)
{
Lt[rt][las[rt]] = Rt[rt][las[rt]] = 0;
Lt[rt][v] = Rt[rt][v] = x;
las[rt] = v;
return ;
}
if(x <= mid) Wupd(ls, l, mid, x, v);
else Wupd(rs, mid + 1, r, x, v);
Wpushup(rt);
}
short main()
{
freopen("truth.in", "r", stdin) , freopen("truth.out", "w", stdout);
// freopen(".err", "w", stderr);
n = qr, k = qr, m = qr;
fo(i, 1, n) a[i] = qr;
Wbuild(1, 1, n);
fo(i, 1, m)
{
int op = qr, x, y;
if(op == 1) x = qr, y = qr, Wupd(1, 1, n, x, y);
else printf("%d\n", ans[1] == 1e9 ? -1 : ans[1]);
}
return Ratio;
}
}
int main(){return Wisadel::main();}
D. 异或区间(xor)
trie 树和静态 RMQ,要用到笛卡尔树优化,仙姑。
什么笛卡尔树,不用,这里讲解可持久化 trie 树的做法。
思路起源于 Subtask3,即最大值位置已知的情况。此时构建出 01trie,边插边查询即可。因此考虑每一位作为最大值的管辖范围,由于会有重复的数所以要钦定一端开一段闭,单调栈即可线性实现。然后怎么跑?直接枚举一端端点统计另一端容易被卡成 \(\mathcal{O(n)}\) 统计,所以判断一下,每次枚举较短的一边即可。区间统计所以上可持久化 trie。复杂度是 \(\mathcal{O(n\log n\log V)}\) 的。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
char ch = getchar();lx x = 0 , f = 1;
for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 1e5 + 5;
int n;
int a[N], s[N];
int rot[N], L[N], R[N], tot;
stack<int> st;
int t[N << 5][2], sum[N << 5];
ll ans;
namespace Wisadel
{
int Wbuild(int las, int s)
{
int rt = ++tot, now = rt;
sum[now] = sum[las] + 1;
fo(i, 1, 30)
{
int zc = (s >> (30 - i)) & 1;
t[now][zc] = ++tot;
t[now][!zc] = t[las][!zc];
now = t[now][zc];
las = t[las][zc];
sum[now] = sum[las] + 1;
}
return rt;
}
int Wq(int x, int y, int a, int s)
{
int res = 0;
fo(i, 1, 30)
{// 如果 a 在这一位为 1,那么直接加上为 0 时的贡献
int zc1 = (a >> (30 - i)) & 1, zc2 = (s >> (30 - i)) & 1;
if(zc1) res += sum[t[y][zc2]] - sum[t[x][zc2]];
x = t[x][zc1 ^ zc2], y = t[y][zc1 ^ zc2];
}
res += sum[y] - sum[x];
return res;
}
short main()
{
freopen("xor.in", "r", stdin) , freopen("xor.out", "w", stdout);
// freopen(".err", "w", stderr);
n = qr;
rot[0] = Wbuild(rot[0], 0);
fo(i, 1, n) a[i] = qr, s[i] = s[i - 1] ^ a[i], rot[i] = Wbuild(rot[i - 1], s[i]);
fo(i, 1, n)
{
while(st.size() && a[st.top()] <= a[i]) st.pop();
L[i] = st.size() ? st.top() : 0;
st.push(i);
}
while(st.size()) st.pop();
fu(i, n, 1)
{
while(st.size() && a[st.top()] < a[i]) st.pop();
R[i] = st.size() ? st.top() : n + 1;
st.push(i);
} // 单调栈找以 i 为最大值的管辖范围
fo(i, 1, n)
if(i - L[i] <= R[i] - i) // 枚举较短的一边
fo(j, L[i], i - 1) ans += Wq(rot[i - 1], rot[R[i] - 1], a[i], s[j]);
else
fu(j, R[i] - 1, i)
if(L[i] == 0) ans += Wq(0, rot[i - 1], a[i], s[j]);
else ans += Wq(rot[L[i] - 1], rot[i - 1], a[i], s[j]);
printf("%lld\n", ans);
return Ratio;
}
}
int main(){return Wisadel::main();}
末
由于一些原因导致现在才发。
打的还行,可能是前一天的动员产生了一点作用。
总是场切不了 T2 有点恼,总是 T3 打假做法有点恼,总是不会 T4 很多暴力有点恼。
起码在进步,稳住就行。
你说得对但我去的时候是 5 车厢,有一块的吗。
完结撒花~