2022年ICPC西安站简易题解


比赛链接:https://codeforces.com/gym/104077

A题 Bridge

题意

完整题意见上方链接。

转换之后的题意可以理解为:有$n$条链,每条链上有$m+1$个点,标号从$1$到$m+1$,每个点可以用$(a,b)$表示,意为第$a$条链上第$b$个点。存在两种操作:

  1. 交换点$(a, b)$与点$(a + 1, b)$的指向。
  2. 查询第$a$条链最终指向的终点$(i,m+1)$,输出$i$。

tags:分块/无旋treap

题解

分块做法

每个点维护它的下一个点与上一个点,但这样对于一次查询是$O(m)$复杂度的,显然很慢。对于链上查找终点的题,显然存在三种做法:

  1. 维护每个点的终点,直接$O(1)$访问终点
  2. 考虑倍增,维护每个点跳$2^i$步之后到达的点,$O(\log m)$访问终点
  3. 维护每个点跳$\sqrt m$步到达的点,$O(\sqrt m)$访问终点

再考虑操作1的交换操作,相当于将两条链断开,再交换断开的后半部分重新拼接成两条链。则两条链断开时链的前半部分的终点都会发生改变,不太方便维护(当然,下面用无旋treap维护这种做法)。

再考虑倍增,同样的,断链的前半部分的倍增点因为拼接上新的链都会发生改变,所以不好维护。

最后考虑根号步数的跳跃,可以发现断链前半部分只有后$\sqrt m$的点需要修改跳跃点,其它点的跳跃都没有影响,于是可以在$\sqrt m$的时间复杂度内完成修改操作。

时间复杂度:$O(m\sqrt m)$

/*************************************************************************
> File Name: 1.cpp
> Author: Knowledge_llz
> Mail: 925538513@qq.com
> Blog: https://www.cnblogs.com/Knowledge-Pig/ 
************************************************************************/

#include<bits/stdc++.h>
#define LL long long
#define endl '\n'
using namespace std;
const int maxx = 5e5 + 10;
int n, m, B, Q, cnt, a[maxx], to[maxx], pre[maxx], jump[maxx];
struct query{
	int t, a, b;
}q[maxx];
vector<int> vec[maxx];
map<pair<int, int>, int> id;
int main(){
	ios::sync_with_stdio(false); cin.tie(0);
#ifndef ONLINE_JUDGE
	freopen("input.txt", "r", stdin);
	freopen("output.txt","w", stdout);
#endif
	auto st = clock();
	cin >> n >> m >> Q;
	for(int i = 1; i <= n; ++i) vec[i].push_back(0);
	for(int i = 1; i <= Q; ++i){
		cin >> q[i].t >> q[i].a;
		if(q[i].t & 1){
			cin >> q[i].b;
			vec[q[i].a].push_back(q[i].b);
			vec[q[i].a].push_back(q[i].b + 1);
			vec[q[i].a + 1].push_back(q[i].b);
			vec[q[i].a + 1].push_back(q[i].b + 1);
		}
	}
	for(int i = 1; i <= n; ++i){
		sort(vec[i].begin(), vec[i].end());
		auto it = unique(vec[i].begin(), vec[i].end());
		vec[i].erase(it, vec[i].end());
		B = max(B, (int)vec[i].size());
	}
	B = sqrt(B / 2) + 1;
	for(int i = 1; i <= n; ++i){
		int x = 0;
		for(auto j : vec[i]){
			id[make_pair(i, j)] = ++cnt;
			a[cnt] = i;
			if(x) to[x] = cnt;
			pre[cnt] = x;
			x = cnt;
		}
		x = id[make_pair(i, 0)];
		for(int j = 1; j <= B; ++j) x = to[x];
		for(auto j : vec[i]){
			jump[id[make_pair(i, j)]] = x;
			x = to[x];
			if(!x) break;
		}
	}
	// cout << "Times : " << (double)(clock() - st) / CLOCKS_PER_SEC * 1000 << endl;
	for(int i = 1; i <= Q; ++i){
		if(q[i].t & 1){
			int idx = id[make_pair(q[i].a, q[i].b)], idy = id[make_pair(q[i].a + 1, q[i].b)];
			pre[to[idx]] = idy;
			pre[to[idy]] = idx;
			swap(to[idx], to[idy]);
			int jx = jump[idx], jy = jump[idy];
			for(int k = 0, x = idx; k <= B && x; ++k, jy = pre[jy], x = pre[x]) jump[x] = jy;
			for(int k = 0, y = idy; k <= B && y; ++k, jx = pre[jx], y = pre[y]) jump[y] = jx;
		}
		else{
			int x = id[make_pair(q[i].a, 0)];
			while(jump[x]) x = jump[x];
			while(to[x]) x = to[x];
			cout << a[x] << endl;
		}
	}
	// cout << "Times : " << (double)(clock() - st) / CLOCKS_PER_SEC * 1000 << endl;
	return 0;
}

开始将分块设置在300左右,一直TLE。最后将B按照最长的链长除以2再开根号,终于在842ms通过本题。

无旋treap解法

链的切开,再交换重组,非常符合无旋treap中的split与merge操作。

因为有$n$条链,就建$n$个无旋treap,以$b$作为treap上的$key$值。每次按照题意模拟即可。

还剩下一个问题:现在有$n$棵平衡树,每次修改操作时我只知道两个点的编号,我怎么知道它们在哪棵平衡树上?

于是,我们还要用treap上维护每个点$father$,对于一次修改来说,直接暴力找$father$,找到根节点即可。再维护每个根节点的节点标号与第几棵平衡树的对应关系。

时间复杂度:$O(n\log n)$。最终只用233ms就可以通过,但是代码比上述分块长了很多。

#include<bits/stdc++.h>
#define LL long long
#define endl '\n'
using namespace std;
const int maxx = 4e5 + 10;
int n, m, Q, num, rt[maxx], rt_id[maxx], to[maxx];
struct query{
    int t, a, b;
}q[maxx];
struct node{
    int key, l, r, fa, rd;
}tr[maxx << 2];
set<int> s[maxx];
vector<int> vec[maxx], id[maxx];
int get_id(int x, int y){
    int pos = lower_bound(vec[x].begin(), vec[x].end(), y) - vec[x].begin();
    return id[x][pos];
}
int get_rt(int x){
    while(tr[x].fa) x = tr[x].fa;
    return x;
}
int add(int val){
    tr[++num].key = val;
    tr[num].rd = rand();
    return num;
}
void push_up(int p){
    tr[p].fa = 0;
    tr[tr[p].l].fa = tr[tr[p].r].fa = p;
}
void split(int p, int &x, int &y, int val){
    if(!p){
        x = y = 0;
        return ;
    }
    if(tr[p].key <= val){
        x = p;
        split(tr[p].r, tr[p].r, y, val);
    }
    else{
        y = p;
        split(tr[p].l, x, tr[p].l, val);
    }
    push_up(p);
}
void merge(int &p, int x, int y){
    if(!x || !y){
        p = x + y;
        return;
    }
    if(tr[x].rd < tr[y].rd){
        p = x;
        merge(tr[p].r, tr[p].r, y);
    }
    else{
        p = y;
        merge(tr[p].l, x, tr[p].l);
    }
    push_up(p);
}
void ins(int &p, int val){
    int l = 0, r = 0, new_node = add(val);
    split(p, l, r, val);
    merge(l, l, new_node);
    merge(p, l, r);
}
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt","w", stdout);
#endif
    cin >> n >> m >> Q;
    srand(n);
    for(int i = 1; i <= Q; ++i){
        cin >> q[i].t >> q[i].a;
        if(q[i].t & 1){
            cin >> q[i].b;
            s[q[i].a].insert(q[i].b);
            s[q[i].a + 1].insert(q[i].b);
            s[q[i].a].insert(q[i].b + 1);
            s[q[i].a + 1].insert(q[i].b + 1);
        }
    }
    for(int i = 1; i <= n; ++i){
        for(auto u : s[i]){
            ins(rt[i], u);
            vec[i].push_back(u);
            id[i].push_back(num);
            // cout << i << " " << u << " " << num << endl;
        }
        rt_id[rt[i]] = to[i] = i;
    }
    // for(int i = 1; i <= num; ++i) cout << i << " " << tr[i].fa << endl;
    for(int i = 1; i <= Q; ++i){
        if(q[i].t & 1){
            int node1_id = get_id(q[i].a, q[i].b), node2_id = get_id(q[i].a + 1, q[i].b);
            int id1 = rt_id[get_rt(node1_id)], id2 = rt_id[get_rt(node2_id)];
            // cout << node1_id << " --- " << id1 << endl;
            // cout << node2_id << " === " << id2 << endl;
            int l1 = 0, r1 = 0, l2 = 0, r2 = 0;
            swap(to[id1], to[id2]);
            split(rt[id1], l1, r1, q[i].b);
            split(rt[id2], l2, r2, q[i].b);
            // cout << l2 << " " << r2 << endl;
            merge(rt[id1], l1, r2);
            merge(rt[id2], l2, r1);
            rt_id[rt[id1]] = id1;
            rt_id[rt[id2]] = id2;
            // for(int i = 1; i <= num; ++i) cout << i << " " << tr[i].fa << " " << tr[i].key << endl;
        }
        else cout << to[q[i].a] << endl;
    }
    return 0;
}

文章作者: Knowledge
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Knowledge !
  目录