2022“杭电杯”中国大学生算法设计超级联赛(5) 题解
A. Pandaemonium Asphodelos: The First Circle (Savage)
用珂朵莉树维护同类颜色的所有区间并进行合并分裂,记一个 \(tag\) 数组维护操作3中每个颜色所加的权值,用一个线段树进行权值的区间修改操作。
对于每个块的权值修改和查询。我们考虑通过类似于时间差分的形式,在赋予一个新的颜色时,将原颜色的贡献减去新颜色的贡献存入线段树中。注意需要动态开点。
对于区间的合并和修改颜色操作。我们需要利用珂朵莉树暴力合并分裂颜色相同的区间,并用Map存储对应的颜色。
时间复杂度 \(O(能过)\),均摊 \(O(q\log{n})\)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXQ = 1e5 + 5;
int n, q, tag[MAXQ];
struct Segment_Tree {
struct Node {
int ls, rs, sum;
void init() { ls = rs = sum = 0; }
}tree[MAXQ * 40];
int root, cnt;
void Modify(int nl, int nr, int k, int l, int r, int& p) {
if(!p) p = ++cnt, tree[p].init();
if(nl <= l && nr >= r) return void(tree[p].sum += k);
int mid = l + r >> 1;
if(nl <= mid) Modify(nl, nr, k, l, mid, tree[p].ls);
if(nr > mid) Modify(nl, nr, k, mid + 1, r, tree[p].rs);
}
int Query(int loc, int l, int r, int p) {
if(!p) return 0;
int mid = l + r >> 1, ans = tree[p].sum;
if(loc <= mid) ans += Query(loc, l, mid, tree[p].ls);
else ans += Query(loc, mid + 1, r, tree[p].rs);
return ans;
}
void Modify(int nl, int nr, int k) { Modify(nl, nr, k, 1, n, root); }
int Query(int loc) { return Query(loc, 1, n, root); }
void Clear() { root = cnt = 0; }
}segt;
struct ODT {
map<int, int> Map;
map<int, int>::iterator Find(int pos) { return prev( Map.upper_bound(pos) ); }
void Split(int pos) {
auto it = Find(pos);
Map[pos] = it->second;
}
void Clear() { Map.clear(); }
}odt;
void Solve() {
scanf("%lld%lld", &n, &q);
segt.Clear();
odt.Clear();
odt.Map[0] = odt.Map[n + 1] = -1;
odt.Map[1] = 0;
for(int k = 0; k <= q; k++) tag[k] = 0;
int lastans = 0;
for(int k = 1; k <= q; k++) {
int op, x, c, y, v;
scanf("%lld", &op);
if(op == 1) {
scanf("%lld%lld", &x, &c);
x = ((x - 1) ^ lastans) % n + 1;
c = ((c - 1) ^ lastans) % ((n - 1) / 2) + 1;
int l = max(1ll, x - c), r = l + 2 * c;
if(r > n) r = n, l = r - 2 * c;
odt.Split(l), odt.Split(r + 1);
for(auto i = odt.Find(l); i->first != r + 1; i = odt.Map.erase(i) ) {
segt.Modify(i->first, next(i)->first - 1, tag[i->second]);
}
odt.Map[l] = k;
} else if(op == 2) {
scanf("%lld%lld", &x, &y);
x = ((x - 1) ^ lastans) % n + 1;
y = ((y - 1) ^ lastans) % n + 1;
auto i = odt.Find(x), j = odt.Find(y);
if(i->second == j->second) continue;
segt.Modify(j->first, next(j)->first - 1, tag[j->second] - tag[i->second]);
j->second = i->second;
while(j->second == next(j)->second) odt.Map.erase( next(j) );
while(j->second == prev(j)->second) j = prev(j), odt.Map.erase( next(j) );
} else if(op == 3) {
scanf("%lld%lld", &x, &v);
x = ((x - 1) ^ lastans) % n + 1;
tag[ odt.Find(x)->second ] += 1ll * v;
} else {
scanf("%lld", &x);
x = ((x - 1) ^ lastans) % n + 1;
int ans = segt.Query(x) + tag[ odt.Find(x)->second ];
printf("%lld\n", ans);
lastans = (ans & 1073741823);
}
}
for(int k = 1; k <= q; k++) tag[k] = 0;
}
signed main()
{
signed T; scanf("%d", &T);
while(T--) Solve();
return 0;
}
D. The Surveying
考虑对于所有的Control Points,预处理出所有能到达的Detail Points。
具体方法是枚举所有矩形的每个线段(Detail Points为端点的线段除外),判断以Control Points和Detail Points为两端的线段是否和矩形中的线段相交,若存在一次相交则不能被观测到。
然后预处理Control Points之间是否能被观测到,具体方法同上。
用bitset维护每个Control Points能到达的Detail Points,然后状压选取Control Points进行判断即可。
时间复杂度 \(O(nm^2+n^2+\frac{2^nn}{32})\),需要卡常。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
//#define int long long
#define pii pair<int, int>
#define fi first
#define se second
#define x1 x123456789
#define y1 y123456789
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
struct Line {
pii p1, p2;
void init(int x1, int y1, int x2, int y2) { p1 = {x1, y1}, p2 = {x2, y2}; }
void init(pii a, pii b) { p1 = a, p2 = b; }
void output() { cout << p1.fi << " " << p1.se << " " << p2.fi << " " << p2.se << " line\n"; }
};
int n, m, o[25][25], segcnt;
pii a[25], b[105][7];
bitset<405> f[25], f0;
Line Seg[405];
int B(vector<int> vec) {
int st = 0;
for(auto i : vec) st += (1 << i - 1);
return st;
}
bool Intersection(Line l1, Line l2) {
pii a = l1.p1, b = l1.p2, c = l2.p1, d = l2.p2;
bool flag = ( min(a.fi, b.fi) < max(c.fi, d.fi) && max(a.fi, b.fi) > min(c.fi, d.fi) &&
min(a.se, b.se) < max(c.se, d.se) && max(a.se, b.se) > min(c.se, d.se) );
if(flag) {
double ac, ad, cb, ca;
ac = 1.0 * (c.fi - a.fi) * (b.se - a.se) - 1.0 * (c.se - a.se) * (b.fi - a.fi);
ad = 1.0 * (d.fi - a.fi) * (b.se - a.se) - 1.0 * (d.se - a.se) * (b.fi - a.fi);
ca = 1.0 * (a.fi - c.fi) * (d.se - c.se) - 1.0 * (a.se - c.se) * (d.fi - c.fi);
cb = 1.0 * (b.fi - c.fi) * (d.se - c.se) - 1.0 * (b.se - c.se) * (d.fi - c.fi);
if(ac * ad < eps && ca * cb < eps) return 1;
else return 0;
} else return 0;
}
bool Observe(int x1, int y1, int x2, int y2) {
Line cur; cur.p1 = {x1, y1}, cur.p2 = {x2, y2};
for(int j = 1; j <= segcnt; j++) {
Line i = Seg[j];
if(cur.p1 == i.p1 || cur.p2 == i.p1 || cur.p1 == i.p2 || cur.p2 == i.p2) continue;
if( Intersection(cur, i) ) return 0;
}
return 1;
}
void Solve() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) f[i].reset();
f0.reset();
segcnt = 0;
for(int i = 1; i <= n; ++i) scanf("%d%d", &a[i].fi, &a[i].se);
for(int i = 1; i <= m; ++i) {
for(int j = 1; j <= 4; ++j) scanf("%d%d", &b[i][j].fi, &b[i][j].se);
for(int j = 1; j <= 4; ++j) {
int nxt = j - 1;
if(!nxt) nxt += 4;
Line cur; cur.p1 = b[i][j], cur.p2 = b[i][nxt];
Seg[++segcnt] = cur;
}
}
for(int k = 1; k <= n; ++k) {
for(int i = 1; i <= m; ++i) {
for(int j = 1; j <= 4; ++j) {
if( Observe(a[k].fi, a[k].se, b[i][j].fi, b[i][j].se) ) f[k][ (i - 1) * 4 + (j - 1) ] = 1;
}
}
}
for(int i = 1; i <= n; ++i) {
o[i][i] = 0;
for(int j = i + 1; j <= n; j++) o[i][j] = o[j][i] = Observe(a[i].fi, a[i].se, a[j].fi, a[j].se);
}
int ans = INF, sze; vector<int> vec;
for(int s = 0; s < (1 << n); ++s) {
vec.clear(); f0.reset();
for(int i = 0; i < n; ++i) if(s & (1 << i)) vec.push_back(i + 1);
bool flag = 1; sze = vec.size();
for(int i = 0; i < sze; i++) {
bool cur = 0;
for(int j = 0; j < sze; j++) cur |= o[ vec[i] ][ vec[j] ];
if(!cur) flag = 0;
}
if(!flag) continue;
for(int i = 0; i < sze; i++) f0 |= f[ vec[i] ];
if(f0.count() >= m * 4) ans = min(ans, sze);
}
if(ans >= INF) printf("No Solution!\n");
else printf("%d\n", ans);
}
signed main()
{
int T; cin >> T;
while(T--) Solve();
return 0;
}
F. BBQ
发现对于长度大于等于8(设其为 \(n\) )的字符串 \(S\),没必要变成长度为4,形式类似于 \(abba\) 的字符串。
若直接变成类似于 \(abba\) 的串,则至少要删除 \(n-4\) 个字符,代价一定大于等于 \(n-4\)。
而我们可以先删去 \(n-8\) 个字符,再对剩下8个字符中的4个进行修改,一定能得到两组类似于 \(abba\) 的串,代价为 \(n-4\)。
这提醒我们直接对长度小于8的子串进行枚举转移即可。
考虑预处理长度小于8的所有子串的转移代价。我们可以通过子串中各个字符的相对关系来设置状态,这样的话状态不会太多(小于 \(7^7\))。
然后对于增删改三种操作进行转移,直接枚举所有字符填充一组类似于 \(abba\) 的串,设其为 \(S=S_0S_1S_1S_0\)。再设当前长度小于8的子串为 \(T\)
\(g[k][l]\) 为 \(T[1...i]\) 中匹配到 \(S\) 中第 \(j\) 个字符的最小代价。
① 对于删除操作,有 \(g[k - 1][l] + 1\)
② 对于增加操作,有 \(g[k - 1][l - 1] + (nums[k] != cur[l])\)
③ 对于修改操作,有 \(g[k][l - 1] + 1\)
三种情况取最小值即可转移。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 1e6 + 5;
const int INF = 0x3f3f3f3f;
string S;
int f[MAXN], n, cost[MAXN * 10], nums[30], g[30][30], cur[MAXN][10];
void DFS(int n, int up, int st) {
if(n > 0) {
cost[st] = n;
for(int i = 1; i <= 7; i++) {
for(int j = 1; j <= 7; j++) {
int cur[5] = {0, i, j, j, i};
for(int k = 0; k <= n; k++) for(int l = 0; l <= 4; l++) g[k][l] = INF;
for(int l = 0; l <= 4; l++) g[0][l] = l;
for(int k = 0; k <= n; k++) g[k][0] = k;
for(int k = 1; k <= n; k++) {
for(int l = 1; l <= 4; l++) {
g[k][l] = min({
g[k - 1][l] + 1,
g[k - 1][l - 1] + (nums[k] != cur[l]),
g[k][l - 1] + 1
});
}
}
cost[st] = min(cost[st], g[n][4]);
}
}
//cout << st << " : " << cost[st] << " cost\n";
}
if(n < 7) {
for(int i = 1; i <= up; i++) {
nums[n + 1] = i;
DFS(n + 1, up, st * 10 + i);
}
nums[n + 1] = up + 1;
DFS(n + 1, up + 1, st * 10 + (up + 1) );
}
}
void Solve() {
cin >> S; n = S.length(); S = " " + S;
for(int i = 1; i <= n; i++) f[i] = INF;
f[0] = 0;
for(int i = 0; i < n; i++) {
if(i) f[i] = min(f[i], f[i - 1] + 1);
int st = 0, cnt = 0;
for(int j = 1; j <= 7 && i + j <= n; j++) nums[ S[i + j] - 'a' ] = 0;
for(int j = 1; j <= 7 && i + j <= n; j++) {
if( !nums[ S[i + j] - 'a' ] ) nums[ S[i + j] - 'a' ] = ++cnt;
st = st * 10 + nums[ S[i + j] - 'a' ];
//cout << i << " " << j << " : " << st << " " << cost[st] << " cur\n";
f[i + j] = min(f[i + j], f[i] + cost[st]);
}
}
//for(int i = 0; i <= n; i++) cout << f[i] << " "; cout << " f\n";
cout << f[n] << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
DFS(0, 0, 0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
H. AC/DC
查询子串在原串的出现次数可以考虑用SAM解决。但是难以处理增加字符和删除字符的操作。
考虑对于 \(k\) 次修改操作后重构SAM,这 \(k\) 次操作中的查询通过hash来求出前后位置的出现次数即可。
时间复杂度 \(O(\frac{nq}{k}+qk+\sum|T|)\),其中 \(\frac{nq}{k}+qk\) 是对勾函数,求导可知当 \(k=\sqrt{n}\) 时取得最小值,不过测试发现 \(k=4000\) 时也能过。
时间复杂度 \(O(q\sqrt{n}+\sum|T|)\),略卡常。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int MAXN = 4e5 + 5;
const int MOD = 1e9 + 7;
const int BASE = 29;
const int M = 4000;
int n, m, lastans, cnt, L, R, lastL, lastR;
ll Hashval[MAXN], Pow[MAXN], Inv[MAXN];
char S[MAXN], T[MAXN];
struct state {
int len, link, sze, nxt[30];
}st[MAXN << 1], EMPTY;
int last, sz;
vector<int> G[MAXN << 1];
void Extend(char c) {
c -= 'a';
int cur = sz++;
st[cur].sze = 1;
st[cur].len = st[last].len + 1;
int p = last;
while(p != -1 && !st[p].nxt[c] ) {
st[p].nxt[c] = cur;
p = st[p].link;
}
if(p == -1) st[cur].link = 0;
else {
int q = st[p].nxt[c];
if(st[p].len + 1 == st[q].len) st[cur].link = q;
else {
int clone = sz++;
st[clone].len = st[p].len + 1;
for(int j = 0; j < 26; j++) st[clone].nxt[j] = st[q].nxt[j];
st[clone].link = st[q].link;
while(p != -1 && st[p].nxt[c] == q) {
st[p].nxt[c] = clone;
p = st[p].link;
}
st[q].link = st[cur].link = clone;
}
}
last = cur;
}
void DFS(int u) {
for(auto v : G[u]) DFS(v), st[u].sze += st[v].sze;
}
void Create(int l, int r) {
for(int i = 0; i < sz; i++) {
G[i].clear();
st[i] = EMPTY;
}
st[0].len = 0, st[0].link = -1;
sz = 1; last = 0;
for(int i = l; i <= r; i++) Extend(S[i]);
for(int i = 1; i < sz; i++) G[ st[i].link ].push_back(i);
DFS(0);
}
int qpow(ll a, ll p, int MOD) {
ll res = 1;
while(p) {
if(p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
int Hash(int l, int r) {
return 1ll * (Hashval[r] - Hashval[l - 1] + MOD) % MOD * Inv[l - 1] % MOD;
}
int Find() {
int ans = 0, len = strlen(T), flag = 1;
ll cur = 0;
for(int i = 0; i < len; i++) {
cur = st[cur].nxt[ T[i] - 'a' ];
if(!cur) flag = 0;
}
if(flag) ans += st[cur].sze;
cur = 0;
for(int i = 0; i < len; i++) cur = (cur + 1ll * Pow[i] * (T[i] - 'a' + 1) % MOD) % MOD;
for(int i = max(lastR + 1, lastL + len - 1); i <= R; i++) {
if( Hash(i - len + 1, i) == cur ) ++ans;
}
for(int i = lastL; i <= min(L - 1, R - len + 1); i++) {
if( Hash(i, i + len - 1) == cur ) --ans;
}
return ans;
}
void Solve() {
cin >> S + 1; n = strlen(S + 1);
cin >> m;
Create(1, n);
for(int i = 1; i <= n; ++i) Hashval[i] = (Hashval[i - 1] + 1ll * Pow[i - 1] * (S[i] - 'a' + 1) % MOD) % MOD;
lastL = L = 1, lastR = R = n, lastans = cnt = 0;
for(int k = 1; k <= m; ++k) {
int op; char c;
cin >> op;
if(op == 1) {
cin >> c;
c = ((c - 'a') ^ lastans) % 26 + 'a';
S[++R] = c;
Hashval[R] = (Hashval[R - 1] + 1ll * Pow[R - 1] * (S[R] - 'a' + 1) % MOD) % MOD;
++cnt;
} else if(op == 2) {
++L;
++cnt;
} else {
cin >> T;
for(int i = 0; i < strlen(T); i++) T[i] = ((T[i] - 'a') ^ lastans) % 26 + 'a';
lastans = Find();
cout << lastans << "\n";
}
if(cnt >= M) {
Create(L, R);
lastL = L, lastR = R;
cnt = 0;
}
}
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
Pow[0] = 1;
for(int i = 1; i <= 2e5; i++) Pow[i] = Pow[i - 1] * BASE % MOD;
for(int i = 0; i <= 2e5; i++) Inv[i] = qpow(Pow[i], MOD - 2, MOD);
signed T; cin >> T;
while(T--) Solve();
return 0;
}
L. Buy Figurines
线段树维护 \(m\) 个队列的当前人数和最后一个人出来的时刻,对 \(n\) 个人按到达时间排序后按照题目要求选择队列并更新线段树。
注意可以用一个优先队列维护已经在队列的人,当有人出队的时候更新线段树即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 2e5 + 5;
struct Node {
int sum, tot, id;
}tree[MAXN << 2];
int n, m;
pii a[MAXN];
priority_queue<pii, vector<pii>, greater<pii> > Q;
Node PushUp(Node ls, Node rs) {
Node res;
if(ls.tot == rs.tot) res.id = min(ls.id, rs.id), res.tot = ls.tot;
else if(ls.tot < rs.tot) res.id = ls.id, res.tot = ls.tot;
else res.id = rs.id, res.tot = rs.tot;
res.sum = max(ls.sum, rs.sum);
return res;
}
void Build(int l = 1, int r = m, int p = 1) {
if(l == r) {
tree[p].id = l;
tree[p].sum = tree[p].tot = 0;
return ;
}
int mid = l + r >> 1;
Build(l, mid, p << 1);
Build(mid + 1, r, p << 1 | 1);
tree[p] = PushUp(tree[p << 1], tree[p << 1 | 1]);
}
void Modify1(int loc, int k, int l = 1, int r = m, int p = 1) {
if(l == r) {
tree[p].sum = k;
tree[p].tot++;
return ;
}
int mid = l + r >> 1;
if(loc <= mid) Modify1(loc, k, l, mid, p << 1);
else Modify1(loc, k, mid + 1, r, p << 1 | 1);
tree[p] = PushUp(tree[p << 1], tree[p << 1 | 1]);
}
void Modify2(int loc, int l = 1, int r = m, int p = 1) {
if(l == r) {
tree[p].tot--;
if(tree[p].tot == 0) tree[p].sum = 0;
return ;
}
int mid = l + r >> 1;
if(loc <= mid) Modify2(loc, l, mid, p << 1);
else Modify2(loc, mid + 1, r, p << 1 | 1);
tree[p] = PushUp(tree[p << 1], tree[p << 1 | 1]);
}
Node Query(int nl, int nr, int l = 1, int r = m, int p = 1) {
if(nl <= l && nr >= r) return tree[p];
int mid = l + r >> 1;
if(nr <= mid) return Query(nl, nr, l, mid, p << 1);
if(nl > mid) return Query(nl, nr, mid + 1, r, p << 1 | 1);
return PushUp( Query(nl, nr, l, mid, p << 1), Query(nl, nr, mid + 1, r, p << 1 | 1) );
}
void Solve() {
cin >> n >> m; Build();
for(int i = 1; i <= n; i++) cin >> a[i].fi >> a[i].se;
sort(a + 1, a + 1 + n);
while(!Q.empty()) Q.pop();
int ans = 0;
for(int i = 1; i <= n; i++) {
while(!Q.empty() && Q.top().fi <= a[i].fi) Modify2(Q.top().se), Q.pop();
int pos = tree[1].id;
Node cur = Query(pos, pos);
int maxtime = max(cur.sum, a[i].fi) + a[i].se;
Modify1(pos, maxtime);
ans = max(ans, maxtime);
Q.push({maxtime, pos});
}
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}

浙公网安备 33010602011771号