NOIP2024 补题
T1 - 编辑字符串
在不能修改的字符左右,将原字符串断开。问题等价于每一段可以重排列,最大化两个字符串匹配的位置数量。
从前往后依次处理:
- 如果两个段能全部匹配上,就让它们全部匹配上,因为如果放弃这里的一个匹配去凑后面的段的匹配,至多也只能凑出一个。故而这种情况下,贪心全部匹配上是不劣的。
- 如果两个段不能全部匹配上,就意味着除了匹配的部分,一个串剩下的全是
0,另一个剩下的全是1。同理,贪心地把可以匹配上的部分全部匹配,答案也是不劣的。
//https://www.luogu.com.cn/problem/P11361
//https://www.luogu.com.cn/record/218537233
//https://www.luogu.com.cn/record/218581480
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
static char st[40]; int top;
if (x < 0) { putchar('-'); x = -x; }
top = 0; do st[top++]=x%10 + '0'; while(x/=10);
while(top) putchar(st[--top]);
}
const int N = 1e5 + 10;
struct seg {
int r, c[2];
seg(int _r = 0) {
r = _r;
c[0] = c[1] = 0;
}
};
vector<seg> a[2];
char S[2][N], T[2][N];
int lst[2];
int n;
void ins(int x, int r) {
if(r <= lst[x]) return;
a[x].PB(seg(r)); int p = a[x].size() - 1;
for(int i=lst[x]+1; i<=r; ++i)
a[x][p].c[S[x][i]-'0']++;
lst[x] = r;
// cout << x << ": --- " << a[x].back().r << ' ' << a[x].back().c[0] << ' ' << a[x].back().c[1] << endl;
}
int main() {
int ts; rd(ts);
while(ts--) {
scanf("%d", &n);
scanf("%s%s%s%s", S[0]+1, S[1]+1, T[0]+1, T[1]+1);
for(int t=0; t<2; ++t) {
lst[t] = 0;
for(int i=1; i<=n; ++i) {
if(T[t][i] == '0') {
ins(t, i-1);
ins(t, i);
}
}
ins(t, n);
}
int cur[2] = {0, 0};
int lst = 0;
int ans = 0;
while(cur[0] < a[0].size() && cur[1] < a[1].size()) {
seg x0 = a[0][cur[0]];
seg x1 = a[1][cur[1]];
// int len = min(x0.r, x1.r) - lst; lst = min(x0.r, x1.r);
// int t0 = min(min(x0.c[0], x1.c[0]), len);
// int t1 = min(min(x0.c[1], x1.c[1]), len-t0);
// ans += t0 + t1;
// if(x0.r == x1.r) {
// cur[0] ++, cur[1] ++;
// } else if(x0.r > x1.r) {
// cur[1] ++;
// a[0][cur[0]].c[0] -= t0;
// a[0][cur[0]].c[1] -= t1;
// }
// else {
// cur[0] ++;
// a[1][cur[1]].c[0] -= t0;
// a[1][cur[1]].c[1] -= t1;
// }
int len = min(x0.r, x1.r) - lst; lst = min(x0.r, x1.r);
int t0 = min(x0.c[0], x1.c[0]);
int t1 = min(x0.c[1], x1.c[1]);
ans += t0 + t1;
if(x0.r == x1.r) {
cur[0] ++, cur[1] ++;
} else if(x0.r > x1.r) {
cur[1] ++;
a[0][cur[0]].c[0] -= t0;
a[0][cur[0]].c[1] -= t1;
if(t0 + t1 < len) {
if(a[0][cur[0]].c[0] > 0) a[0][cur[0]].c[0] -= len - (t0 + t1);
else a[0][cur[0]].c[1] -= len - (t0 + t1);
}
}
else {
cur[0] ++;
a[1][cur[1]].c[0] -= t0;
a[1][cur[1]].c[1] -= t1;
if(t0 + t1 < len) {
if(a[1][cur[1]].c[0] > 0) a[1][cur[1]].c[0] -= len - (t0 + t1);
else a[1][cur[1]].c[1] -= len - (t0 + t1);
}
}
}
printf("%d\n", ans);
a[0].clear(), a[1].clear();
}
}
T2 - 遗失的赋值
什么样的限制可以带来非法的方案:
- 某一对 \(i, j\) 满足 \(c_i = c_j, d_i \neq d_j\)
- 对于某个区间 \([l, r]\)
- 对于某个 \(i\),\(c_{i} = l\) ,且 \(d_i = a_l\)
- \(a_{x + 1} = b_x\) 对于所有的 \(x \in [l, r-1]\)
- 对于某个 \(j\),\(c_j = r\) ,且 \(d_j \neq b_{r-1}\)
对 \(c_i\) 去重并排序。由上可知,相邻的两个 \(c_i\) 之间的 \((a_x, b_x)\) 的合法性是独立的,分别算出方案数相乘即可。
//https://www.luogu.com.cn/problem/P11362
//https://www.luogu.com.cn/record/218530542
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
static char st[40]; int top;
if (x < 0) { putchar('-'); x = -x; }
top = 0; do st[top++]=x%10 + '0'; while(x/=10);
while(top) putchar(st[--top]);
}
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
inline ll Pow(ll x, ll y) {
int res = 1;
for(; y; y>>=1, x = 1ll * x * x % mod)
if(y&1) res = 1ll * res * x % mod;
return res;
}
int m, c[N];
ll n, v;
map<int,int> mp;
int main() {
int ts; rd(ts);
while(ts--) {
rd(n), rd(m), rd(v);
mp.clear();
bool flag = true;
for(int i=1, d; i<=m; ++i) {
rd(c[i]), rd(d);
if(mp.count(c[i])) {
if(mp[c[i]] != d) flag = false;
} else {
mp[c[i]] = d;
}
}
sort(c + 1, c + m + 1);
m = unique(c + 1, c + m + 1) - c - 1;
if(!flag) {
printf("0\n");
continue;
}
ll res = Pow(v, (c[1] - 1) * 2) % mod;
for(int i=1; i<m; ++i) {
ll tot = Pow(v, (c[i+1] - c[i]) * 2);
ll tmp = 1ll * Pow(v, c[i+1] - c[i] - 1) * (v-1) % mod;
res = res * (tot - tmp) % mod;
}
res = res * Pow(v, (n - c[m]) * 2) % mod;
res = (res % mod + mod) % mod;
printf("%lld\n", res);
}
return 0;
}
T3 - 树的遍历
固定根节点,观察发现新树合法的充要条件为:
- 一个结点的所有邻边,在新树中是连续的一条链
- 结点到根的路径上的那条邻边必定是链的端点
对于某棵新树,合法的根结点必形成一条链,用“点减边”的容斥(每个关键点为根的方案数 - “相邻”关键点均作为根的方案数),即可保证每棵新树枝只被数一次。
//https://www.luogu.com.cn/problem/P11363
//https://www.luogu.com.cn/record/218550260
/*
for a path of length (#edges) L and has X 'roots' (and both ends of the path are 'roots'),
(X may not include all roots on the path)
its contribution is (-1)^(X-1) * (\prod_{i inside the path} (1/(deg[i]-1))
TOT = \prod_i (deg[i]-1)!
*/
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define PII pair<int,int>
#define FIR first
#define SEC second
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
static char st[40]; int top;
if (x < 0) { putchar('-'); x = -x; }
top = 0; do st[top++]=x%10 + '0'; while(x/=10);
while(top) putchar(st[--top]);
}
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
inline ll Pow(ll x, ll y) {
int res = 1;
for(; y; y>>=1, x = 1ll * x * x % mod)
if(y&1) res = 1ll * res * x % mod;
return res;
}
vector<int> G[N];
vector<PII > E;
int mk[N];
int n;
int dep[N], deg[N];
void dfs1(int u, int last) {
dep[u] = dep[last] + 1;
deg[u] = G[u].size();
for(int v : G[u]) if(v != last) dfs1(v, u);
}
int fac[N], inv[N];
void getfac(int n) {
fac[0] = 1; for(int i=1; i<=n; ++i) fac[i] = 1ll * fac[i-1] * i % mod;
inv[n] = Pow(fac[n], mod-2); for(int i=n; i>=1; --i) inv[i-1] = 1ll * inv[i] * i % mod;
for(int i=n; i>=1; --i) inv[i] = 1ll * inv[i] * fac[i-1] % mod;
}
ll tot, ans;
ll f[N];
void dfs2(int u, int last) {
f[u] = 0;
for(int v : G[u]) if(v != last) {
dfs2(v, u);
ans = (ans - f[v] * f[u] % mod * tot) % mod;
f[u] = (f[u] + f[v] * inv[deg[u] - 1]) % mod;
}
if(mk[u]) {
ans = (ans - f[u] * tot) % mod;
f[u] = 1;
}
}
int main() {
getfac(100000);
int tcc, ts; rd(tcc), rd(ts);
while(ts--) {
int q; rd(n), rd(q);
for(int i=1, x, y; i<n; ++i) {
rd(x), rd(y);
G[x].PB(y), G[y].PB(x);
E.PB(MP(x, y));
}
dfs1(1, 0);
for(int i=1, x; i<=q; ++i) {
scanf("%d", &x); --x;
int v = dep[E[x].FIR] < dep[E[x].SEC] ? E[x].SEC : E[x].FIR;
mk[v] = 1;
// cout << v << endl;
}
tot = 1;
for(int i=1; i<=n; ++i) tot = tot * fac[deg[i] - 1] % mod;
ans = tot * q % mod;
dfs2(1, 0);
printf("%lld\n", (ans % mod + mod) % mod);
for(int i=1; i<=n; ++i) G[i].clear(), mk[i] = 0;
E.clear();
}
}
T4 - 树上查询
称满足 \(LCA^*(l, r) \neq LCA^*(l-1, r)\) 且 \(LCA^* (l, r) \neq LCA^*(l, r+1)\) 的区间 \([l, r]\) 是极大的。
如果我们能找出所有的极大区间以及区间 LCA 深度 \((l_i, r_i, dep_i)\),我们就能这样计算答案:
- 对于询问的 \((l, r, k)\),我们对所有的 \(r_i - l_i +1 \ge k\) 且 \(l_i \le r - k + 1 \vee r_i \ge l + k - 1\) 的 \(i\),计算 \(dep_i\) 最大值;如果没有这样的 \(i\),则答案为 \(dep(LCA^*(l, r))\)
- 也就是二维偏序,可以扫描线+线段树可以处理(我的代码写的是可持久化线段树)
考虑如何计算 \(LCA^*(l, r)\):发现以任意顺序(例如,按照编号从小到大的顺序)在树上遍历这些点,必经过它们的 \(LCA\),必不会经过 \(LCA\) 以上的点;进而,\(LCA^*(l, r)\) 就是 \(LCA(l, l+1), LCA(l+1, l+2), \cdots LCA(r-1, r)\) 中深度最浅者,从而有 \(dep(LCA^*(l, r)) = \min_{l \le i < r} dep(LCA(i, i+1))\)。
以 \(dep(LCA(i, i+1))\) 的权值建立笛卡尔树,极大的区间必然是树上某个结点所代表的区间,后者的数量为 \(2n-1\)。总复杂度 \(O(n \log n)\)。
// query if in [l, r-k+1] exists a left end with >= k segment length
// 处理出合并前后所有的区间,以及所需深度
// 对 k 可持久化
// 查出 >=k 的树上,区间内的所需深度最大值
// 询问离线下来就不需要可持久化了,直接线段树即可
// https://www.luogu.com.cn/problem/P11364
// https://www.luogu.com.cn/record/218580277
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define PII pair<int,int>
#define ll long long
#define ull unsigned long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T>
inline void print(T x) {
static char st[40]; int top;
if (x < 0) { putchar('-'); x = -x; }
top = 0; do st[top++]=x%10 + '0'; while(x/=10);
while(top) putchar(st[--top]);
}
const int N = 5e5 + 10;
int n;
int dep[N];
namespace Graph {
vector<int> G[N];
int p[N][20];
void dfs(int u, int last) {
dep[u] = dep[last] + 1;
p[u][0] = last;
for(int v : G[u]) if(v!=last) dfs(v, u);
}
int LCA(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
for(int j=19; j>=0; --j) if(dep[x] - (1<<j) >= dep[y]) x = p[x][j];
if(x == y) return x;
for(int j=19; j>=0; --j) if(p[x][j] != p[y][j]) x=p[x][j], y=p[y][j];
return p[x][0];
}
void predo() {
rd(n);
for(int i=1, x, y; i<n; ++i) {
rd(x), rd(y);
G[x].PB(y), G[y].PB(x);
}
dfs(1, 0);
for(int j=1; j<=19; ++j) {
for(int u=1; u<=n; ++u) {
p[u][j] = p[p[u][j-1]][j-1];
}
}
}
}
using Graph::LCA;
const int M = N * 40;
struct Seg {
int lson[M], rson[M], mx[M], ncnt;
int rt[N];
inline void push_up(int c) {
mx[c] = max(mx[lson[c]], mx[rson[c]]);
}
int pos, dep;
void ins(int l, int r, int &c) {
if(!c) c = ++ncnt;
else {
int u = c;
c = ++ ncnt;
lson[c] = lson[u];
rson[c] = rson[u];
mx[c] = mx[u];
}
if(l == r) {
mx[c] = max(mx[c], dep);
return;
}
int mid = l + r >> 1;
if(pos <= mid) ins(l, mid, lson[c]);
else ins(mid + 1, r, rson[c]);
push_up(c);
}
vector<PII > Q[N];
void ad(int l, int r, int dep) {
// cerr << "add seg: " << l << ' ' << r << ' ' << dep << endl;
// cerr << "add seg: " << r - l + 1 << ' ' << l << ' ' << dep << endl;
Q[r-l+1].PB(MP(l, dep));
}
void main() {
for(int k=n; k>=1; --k) {
if(k < n) rt[k] = rt[k+1];
for(auto X : Q[k]) {
pos = X.FIR, dep = X.SEC;
ins(1, n, rt[k]);
}
}
}
int ql, qr, qt;
void qry(int l, int r, int c) {
if(!c) return;
if(ql <= l && qr >= r) {
qt = max(qt, mx[c]);
return;
}
int mid = l + r >> 1;
if(ql <= mid) qry(l, mid, lson[c]);
if(qr > mid) qry(mid + 1, r, rson[c]);
}
int QRY(int l, int r, int k) {
ql = l, qr = r-k+1, qt = 0;
qry(1, n, rt[k]);
return qt;
}
// int QRY(int l, int r, int k) {
// int res = 0;
// for(int kk = k; kk <= n; ++kk) {
// for(auto X : Q[kk]) {
// int l0 = X.FIR;
// int r0 = X.FIR + kk - 1;
// int l1 = max(l0, l);
// int r1 = min(r0, r);
// if(r1 - l1 + 1 >= k)
// res = max(res, X.SEC);
// }
// }
// return res;
// }
}L, R;
namespace GetSeg {
int LG[N];
int rmq[20][N];
int val[N];
int getmin(int x, int y) {
return val[x] < val[y] ? x : y;
}
void getrmq() {
for(int i=2; i<=n; ++i) LG[i] = LG[i>>1] + 1;
for(int i=1; i<n; ++i) val[i] = dep[LCA(i, i+1)], rmq[0][i] = i;
for(int j=1; j <= 19; ++j) {
for(int i=1; i + (1<<j) - 1 <n; ++i)
rmq[j][i] = getmin(rmq[j-1][i], rmq[j-1][i + (1<<j-1)]);
}
}
int segminpos(int l, int r) {
int L = LG[r - l + 1];
return getmin(rmq[L][l], rmq[L][r - (1<<L) + 1]);
}
int segminval(int l, int r) {
int p = segminpos(l, r-1);
return val[p];
}
void build(int l, int r) {
if(l == r) {
L.ad(l, l, dep[l]);
R.ad(n-l+1, n-l+1, dep[l]);
return;
}
int u = segminpos(l, r-1);
L.ad(l, r, val[u]);
R.ad(n-r+1, n-l+1, val[u]);
build(l, u), build(u + 1, r);
}
void main() {
getrmq();
build(1, n);
}
}
// int ans[N];
int main() {
// freopen("query2.in", "r", stdin);
// freopen("out.txt", "w", stdout);
Graph::predo();
GetSeg::main();
L.main(), R.main();
int l, r, k, q;
rd(q);
while(q--) {
rd(l), rd(r), rd(k);
int tmp = GetSeg::segminval(l, r);
print(max(max(L.QRY(l, r, k), R.QRY(n-r+1, n-l+1, k)), tmp));
putchar('\n');
// printf("!-%d-\n", max(L.QRY(l, r, k), R.QRY(n-r+1, n-l+1, k)));
}
return 0;
}

浙公网安备 33010602011771号