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;
}
posted @ 2022-08-07 21:09  Orzjh  阅读(63)  评论(0)    收藏  举报