2023 做题记录

就是 2023 做过的个人觉得还不错的题。

1 CF1601D Difficult Mountain
Problem

\(n\) 个人相约去爬山。每座山有一个初始的攀登难度 \(d\)。每个人有两个属性 \(a,s\),当满足 \(s_i \le d_j\) 时第 \(i\) 个人可以爬上第 \(j\) 座山,攀上后第 \(j\) 座山的高度会变为 \(\max(d_j,a_i)\)。问在最优情况下有多少人能爬上山。

\(n\le 5\times10^5\)\(a_i,s_i,d_i\le 10^9\)

\(2s\)\(500MB\)

Sol

忘了当时是从哪里看到的了。但是那时候猜了一手按 \((\max(a_i,s_i),s_i)\) 的双关键字排序,当时不会证但是过了,太不牛了。

考虑证明这个结论。

先把人分为两类:\(a_i\le s_i\)\(a_i>s_i\)。两边的集合如果单独贪心的话是简单的,即按照 \(s_i\)\(a_i\) 分别排序贪心即可,记这两个集合分别为 \(S,T\)。然后肯定是优先考虑 \(S\) 的。考虑 \(S\)\(T\) 的影响,发现当且仅当 \([a_i,s_i] \bigcap [s_j,a_j]\neq \varnothing\)\(i,j\) 只能取一个。这时候肯定是取 \(i\),因为 \(a_i\le a_j\)

所以要按照 \(\max\{a_i,s_i\}\) 排序,但这样会有一个小瑕疵,就是有可能存在 \(s_j<a_i\le s_i=a_j\) 的情况,但这样要求要选 \(i\),所以还要让 \(s_i\) 成为第二关键字。然后就做完了。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N = 5e5 + 5;
int n, d;
struct node {
	int s, a, mx;
	friend bool operator < (node x, node y) {
		return x.mx != y.mx ? x.mx < y.mx : x.s < y.s;
	}
} a[N];

int main() {
	cin >> n >> d;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].s >> a[i].a;
		a[i].mx = max(a[i].s, a[i].a);
	}
	sort(a + 1, a + n + 1);
	int ans = 0;
	for (int i = 1; i <= n; i++)
		if (d <= a[i].s) {
			ans++;
			d = max(d, a[i].a);
		}
	cout << ans << endl;
	return 0;
}
2 CF690A3 Collective Mindsets (hard)
Problem

一共有 \(n\) 个人,当前人的编号是 \(r\),每个人头顶上有一个数字 \(a_i\)。每个人能看到其他人头顶的数字,所有人需同时给出自己的答案,使得至少有一个人是对的。多测。

\(T\le 50000\)\(1\le r,a_i\le n\le 6\)

\(4s\)\(250MB\)

Sol

一些构造题如果 \(a_i\in [1,n]\),可以考虑一下性质。发现是 \(n\) 个人中只需要让一个人猜对,这个东西可以考虑猜测所有数的和,因为一共只有 \(n\) 种余数,并且 \(1\le a_i\le n\),所以这样就至少有一个人是对的了。

Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;

int read() {
	int ret = 0, sgn = 0;
	char ch = getchar();
	while (!isdigit(ch)) sgn |= ch == '-', ch = getchar();
	while (isdigit(ch)) ret = ret * 10 + ch - '0', ch = getchar();
	return sgn ? -ret : ret;
}

int main() {
	int T = read();
	while (T--) {
		int n = read(), r = read(), sum = 0;
		for (int i = 1; i < n; i++) {
			int x = read();
			sum += x;
		}
		cout << (r - sum % n + n) % n + 1 << endl;
		// 这里要 +1, 因为之前是按余数算的,但要求的是 1~n。
	}
	return 0;
}
3 CF727F Polycarp's problems
Problem

给定一个长为 \(n\) 的数列 \(a\)\(m\) 次询问,每次给出 \(a_0\) 的值,求至少删去多少个数使任意位置的前缀和 \(\ge 0\)

\(n\le 750\)\(m\le 2\times 10^5\)\(|a_i|\le 10^9\)\(0\le b_i\le 10^{15}\)

\(2s\)\(250MB\)

Ex:存在 \(\mathcal{O}((n+m)\log n)\) 的做法 。

Sol

虽然这道题的 dp 是比较显然的,贪心做法却比较妙。

先说 dp。如果只有一次询问的话,令 \(f_{i}\) 表示使前 \(i\) 个数满足条件的最小操作次数,但这样转移还需要查询区间最值,而且比较难以优化到多次查询。正难则反,考虑从后面 dp。但是要这个 dp 不能受到 \(b\) 的影响,不妨设 \(f_{i,j}\) 表示后 \(i\) 个数,保留 \(j\) 个的最小的 \(s_k\)。则有转移:\(f_{i,j}=\min(f_{i+1,j},\max(0,f_{i+1,j-1}-a_i))\)

然后在 \(f_{1,i}\) 上二分即可。时间复杂度:\(\mathcal{O}(n^2+m\log n)\)。没有 dp 的代码。

然后说一下贪心的做法。就是有一个贪心就是用一个正数去消多个负数,然后最后剩下的消不掉的就由 \(a_0\) 去消,最后剩下的负数个数就是操作次数,这样显然是错的,就是在一个整数后面接一个极小(绝对值大)的负数,然后再接一个极大的正数,最后都是极大(绝对值小)的负数。

考虑怎么解决掉这个问题,发现是因为前面的决策后面就考虑不到了,所以就不能局部贪心了,所以倒过来就行了。然后用堆维护并在查询时二分即可做到 \(\mathcal{O}((n+m)\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 755;
int n, m;
ll a[N], tp, sum[N];
priority_queue <int> q;

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = n; i; i--) {
		if (a[i] < 0) q.push(a[i]);
		else {
			while (!q.empty() && a[i] >= 0) {
				a[i] += q.top();
				q.pop();
			}
			if (a[i] < 0) q.push(a[i]);
		}
	}
	while (!q.empty()) {
		sum[++tp] = -q.top();
		q.pop();
	}
	for (int i = 1; i <= tp; i++) sum[i] += sum[i - 1];
	while (m--) {
		ll x;
		scanf("%lld", &x);
		int res;
		if (x >= sum[tp]) res = 0;
		else res = tp - (upper_bound(sum + 1, sum + tp + 1, x) - sum) + 1;
		printf("%d\n", res);
	}
	return 0;
}
4 AGC058C Planar Tree
Problem

给定一个 \(n\) 个点的环,每个点有权值 \(a_i\),当 \(|a_i-a_j|=1\)\(i,j\) 之间有边,问是否能选出其中 \(n-1\) 条 在环上除端点外不相交 的边构成一棵树。\(T\) 组数据。

\(T\le 7.5\times 10^4\)\(4\le n\le \sum n\le 3\times 10^5\)\(a_i\in\{1,2,3,4\}\)

\(2s\)\(1GB\)

Sol

当时看了题解,现在自己做出来了。

首先如果有连续的一段的 \(a\) 值时相等的,肯定可以把它们缩成一个点,因为可以同时连向一个相同的点,这样也不会相交。然后发现 \(2,3\) 的决策比 \(1,4\) 要多,所以要尽量的让 \(1,4\) 作为叶子分别连向 \(2,3\)。所以如果有 \((3,4)/(1,2)\) 的无序相邻对的话肯定就要消掉。然后这个序列就会剩下形如 \((3,2,4),(2,3,1),(3,2,3),(2,3,2)\) 的段。像 \((3,2,4)\) 这种可以让 \(2\) 作为一个叶子,然后抹掉 \(2\),然后 \(3,4\) 就相邻了,显然是抹掉 \(4\)\((2,3,1)\) 也是同理。

然后这样就可以通过一些操作抹掉所有的 \(1,4\),然后像 \((3,2,3)\) 这种段,显然将 \(2\) 作为叶子,\((2,3,2)\) 这种同理,将 \(3\) 作为叶子即可,所以只有 \(2,3\) 的话是必然有解的。

考虑什么情况下才能消掉所有的 \(1,4\)。直接模拟显然不是明智的选择,发现此时消 \(1\) 需要先消一个 \(3\),消 \(4\) 需要先消掉 \(2\)。所以消掉的是 \((1,3),(2,4)\)。记合并相邻的相同项过后,\(i\) 的出现次数为 \(c_i\)。则当且仅当 \(c_1>c_3,c_2>c_4\) 满足。不能取等的原因是如果先消完 \((1,3)\) 的话,是不能消 \((2,4)\) 的,因为消 \((2,4)\) 至少需要一个 \(3\),然后如果只有一个取等是不可能存在的。

实现上可以先从左到右扫一遍,然后再从右到左做一遍就行。注意枚举到 \(2n\),因为是一个环。

Code
#include<bits/stdc++.h>
using namespace std;
#define Fin(file) freopen(file,"r",stdin)
#define Fout(file) freopen(file,"w",stdout)
#define L(i,j,k) for(int i=(j);i<=(k);++i)
#define R(i,j,k) for(int i=(j);i>=(k);--i)
int n;
int a[300010],vis[300010],c[5];
void solve(){
	cin>>n;
	L(i,1,n)cin>>a[i],vis[i]=0;
	L(_,0,1){
		int pre=-1;
		L(i,1,2*n)if(!vis[(i-1)%n+1]){
			int v=a[(i-1)%n+1];
			if((pre==v)||(pre==2&&v==1)||(pre==3&&v==4))vis[(i-1)%n+1]=1;
			else pre=a[(i-1)%n+1];
		}
		reverse(a+1,a+n+1);reverse(vis+1,vis+n+1);
	}
	L(i,1,4)c[i]=0;
	L(i,1,n)if(!vis[i])++c[a[i]];
	cout<<((c[4]>=c[2]||c[1]>=c[3])?"No":"Yes")<<"\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--)solve();
	return 0;
}
5 P8565 Sultan Rage
Problem

有一个数列 \(\{a_n\}\) 满足对 \(n > m\) 均有 \(a_n=\sum\limits_{j=1}^m a_{n-j}\),并且 \(a_1,a_2,\cdots,a_m\) 是输入中给出的正整数。

\(q\) 次询问,每一次给出一个正整数 \(x\),问有多少个不可重正整数集 \(S\) 满足 \(\sum\limits_{s\in S}a_s=x\)。答案对质数 \(998244353\) 取模,多测。

\(T=5\)\(2 \le m \le 100\)\(1 \le q, a_i \le 100\)\(1 \le x \le 10^{18}\)

\(1s\)\(512MB\)

Sol

发现数列 \(a\) 增长的特别快,项数最多时是 \(a_1 = a_2 = 1, m = 2\),也就是斐波那契数列,这样也只需要不到 \(100\) 项就可以超过 \(10^{18}\)

发现值域上点的分布越来越稀疏且点极少,可以考虑搜索,函数 dfs(val, cur) 表示凑出 \(x\) 还需要 \(val\),现在在考虑 \(cur\)

但光是搜索肯定不能过这道题,考虑优化。

先记忆化掉重复的操作,可以用一个 map 来存储,然后可以进行可行性剪枝,如果以后再怎么凑都不行,直接剪枝,可以用前缀和优化一下。

但是这样只有 30 分。

可以改变一下搜索的顺序,因为先考虑小元素的话,会有较多的无用的搜索,且小元素较灵活,更容易凑到 \(x\),故可以从大到小的考虑。这样就可以过了。

注意:判断能否返回时是 mp[cur].conut(val),而不是 mp[cur][val],否则会超时。因为如果这样判断的话,会直接额外开一个节点,对每一层都是如此,但第一次搜索时会额外开 \(2^{dep - 1}\) 个节点,会使后面的搜索查询越来越慢(尽管迟早也要开,等于这样使常数变大了不少)。

时空可过。

Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
ll read() {
	ll ret = 0, sgn = 0;
	char ch = getchar();
	while (!isdigit(ch)) sgn |= ch == '-', ch = getchar();
	while (isdigit(ch)) ret = ret * 10 + ch - '0', ch = getchar();
	return sgn ? -ret : ret;
}
const int N = 180, mod = 998244353;
const ll INF = 1e18;
int n, q;
ll a[N], sum[N];
map<ll, int> mp[N];

int dfs(ll x, int cur) {
	if (x < 0 || x > sum[cur]) return 0;
	if (!cur) return (x == 0);
	if (mp[cur].count(x)) return mp[cur][x];
	return mp[cur][x] = (dfs(x - a[cur], cur - 1) + dfs(x, cur - 1)) % mod;
}

int main() {
	int T = read();
	while (T--) {
		n = read(), q = read();
		for (int i = 1; i <= n; i++) a[i] = read();
		int m = n;
		while (a[m] <= INF) {
			a[++m] = 0;
			for (int i = 1; i <= n; i++) a[m] += a[m - i];
		}
		n = m - 1;
		for (int i = 1; i <= n; i++) {
			sum[i] = sum[i - 1] + a[i];
			mp[i].clear();
		}
		while (q--) {
			ll x = read();
			printf("%d\n", dfs(x, n));
		}
	}
	return 0;
}
6 ABC298H Sum of Min of Length
Problem

给定一棵 \(n\) 个节点的树。\(m\) 次询问,每次询问给出 \(L, R\),求 \(\sum\limits_{i = 1}^{n}\min(d(i, L), d(i, R))\)\(d(i, j)\) bi表示点 \(i\) 到点 \(j\) 的距离。

\(1\le n,m\le 2\times 10^5\)\(1\le L,R\le n\)

\(3s\)\(1GB\)

Sol

因为是求 \(\sum\limits_{i = 1} ^ n\min \{d(i, L), d(i, R)\}\) 的值,考虑讨论 \(d(i, L)\)\(d(i, R)\) 的大小。

\(p = \text{LCA}(L, R)\)\(dep_L > dep_R, dist = dep_L + dep_R - 2\times dep_p\)\(now\) 满足 \(dep_L - dep_{now} = \lfloor\dfrac{dist}{2}\rfloor\)

\(L\) 一定在 \(now\) 的子树内,且对于 \(\forall i\in subtree_{now}\) 时均有 \(d(i, L) \le d(i, R)\),否则 \(d(i, L) > d(i, R)\)。其中 \(subtree_x\) 表示 \(x\) 的子树。

容易想到求一个点到其他点的距离和。

\(val_i\) 表示 \(\sum\limits_{j = 1}^{n} d(i, j)\)

在 dfs 时处理一下即可,显然可以做到 \(\mathcal{O}(n)\)

最后把距离分奇偶讨论一下即可。

时间复杂度:\(\mathcal{O}(n + (n + m)\log n)\)

Code
#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int inf = 2147483647, mod = 1011451423;
const ll INF = 1e18;

const int N = 2e5 + 10;
int n, m;
int siz[N], d[N], fa[N][20];
ll dis[N], v[N];
vector<int> e[N];

void dfs1(int u, int f) {
	d[u] = d[f] + 1, siz[u] = 1, fa[u][0] = f;
	for (int i = 1; i <= 18; i++)
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for (int v : e[u])
		if (v != f) {
			dfs1(v, u);
			siz[u] += siz[v];
			dis[u] += dis[v] + siz[v];
		}
}
void dfs2(int u, int f) {
	if (u == 1)
		v[u] = dis[u];
	else
		v[u] = v[f] + (siz[1] - siz[u]) - siz[u];
	for (int v : e[u])
		if (v != f)
			dfs2(v, u);
}
int lca(int u, int v) {
	if (d[u] < d[v])
		swap(u, v);
	for (int i = 18; i >= 0; i--)
		if (d[fa[u][i]] >= d[v])
			u = fa[u][i];
	if (u == v)
		return u;
	for (int i = 18; i >= 0; i--)
		if (fa[u][i] != fa[v][i])
			u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}
int tonode(int u, int st) {
	for (int i = 18; i >= 0; i--)
		if (st >> i & 1)
			u = fa[u][i];
	return u;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		e[u].pb(v), e[v].pb(u);
	}
	dfs1(1, 0), dfs2(1, 0);
	cin >> m;
	while (m--) {
		int l, r;
		cin >> l >> r;
		if (d[l] < d[r])
			swap(l, r);
		int p = lca(l, r), dist = d[l] + d[r] - 2 * d[p];
		if (dist % 2 == 0) {
			int now = tonode(l, dist / 2);
			cout << v[l] + v[r] - v[now] - 1ll * n * dist / 2 << "\n";
		} else {
			int now = tonode(l, dist / 2);
			cout << v[l] + v[r] - v[now] - 1ll * n * (dist / 2) - siz[now] << "\n";
		}
	}
	return 0;
}
7 P7834 [ONTAK2010] Peaks 加强版
Problem

给定一张 \(n\) 个点、\(m\) 条边的无向图,第 \(i\) 个点的权值为 \(a_i\),边有边权。

\(q\) 组询问,每组询问给定三个整数 \(u, x, k\),求从 \(u\) 开始只经过权值 \(\leq x\) 的边所能到达的权值第 \(k\) 大的点的权值,如果不存在输出 \(-1\),强制在线。

\(1 \leq n \leq 10^5\)\(0 \leq m, q \leq 5 \times 10^5\)\(1 \leq s, t \leq n\)\(1 \leq a_i, w \leq 10^9\)\(0 \leq u', x', k' < 2^{31}\)

\(2.5s\)\(128MB\)

Sol

发现有只经过权值 \(\le x\) 的边的限制,容易想到 Kruskal 重构树,因为 Kruskal 重构树可以轻松求出从 \(u\) 出发只经过不超过 \(x\) 的边的点集,这是一个常见的 trick。然后每次询问就是问一个子树中的第 \(k\) 大的点是多少,这个东西暴力线段树合并,也可以求出 dfs 序,然后用可持久化线段树维护。注意线段树合并也需要可持久化,所以空间常数可能较大。

下面的代码是可持久化线段树合并写法 by Alex_Wei。

Code
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
#define fi first
#define se second
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using ll = long long;
using uint = unsigned int;
using ld = long double;
// using lll = __int128;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using ull = unsigned long long;
inline ll read() {
	ll x = 0, sgn = 0;
	char s = getchar();
	while(!isdigit(s)) sgn |= s == '-', s = getchar();
	while(isdigit(s)) x = x * 10 + s - '0', s = getchar();
	return sgn ? -x : x;
}
inline void print(ll x) {
	if(x < 0) return putchar('-'), print(-x);
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
bool Mbe;

constexpr int N = 1e5 + 5;
constexpr int M = 5e5 + 5;
constexpr int V = N << 1;
constexpr int K = N * 40;

// linklist
int cnt, hd[V], nxt[V], to[V];
void add(int u, int v) {nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v;}

// dsu
int fa[V];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}

struct SegTree {
	int node, ls[K], rs[K], val[K];
	void modify(int l, int r, int p, int &x) {
		if(!x) x = ++node;
		val[x]++;
		if(l == r) return;
		int m = l + r >> 1;
		if(p <= m) modify(l, m, p, ls[x]);
		else modify(m + 1, r, p, rs[x]);
	}
	int merge(int x, int y) {
		if(!x || !y) return x | y;
		int z = ++node;
		ls[z] = merge(ls[x], ls[y]);
		rs[z] = merge(rs[x], rs[y]);
		return val[z] = val[x] + val[y], z;
	}
	int binary(int l, int r, int x, int k) {
		if(l == r) return l;
		int m = l + r >> 1;
		if(val[rs[x]] >= k) return binary(m + 1, r, rs[x], k);
		return binary(l, m, ls[x], k - val[rs[x]]);
	}
} tr;

// basic
int n, m, q, a[N], d[N];

// kruskal
int node, lg, val[V], R[V], anc[18][V];
struct edge {
	int u, v, w;
	bool operator < (const edge &x) const {return w < x.w;}
} e[M];
void dfs(int id) {
	if(id <= n) tr.modify(1, n, a[id], R[id]);
	for(int i = hd[id]; i; i = nxt[i]) {
		int it = to[i];
		anc[0][it] = id, dfs(it);
		R[id] = tr.merge(R[id], R[it]);
	}
}

bool Med;
int main() {
	fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
	#ifdef ALEX_WEI
		FILE* IN = freopen("1.in", "r", stdin);
		FILE* OUT = freopen("1.out", "w", stdout);
	#endif
	cin >> n >> m >> q;
	for(int i = 1; i <= n; i++) d[i] = a[i] = read();
	sort(d + 1, d + n + 1);
	for(int i = 1; i <= n; i++) a[i] = lower_bound(d + 1, d + n + 1, a[i]) - d;
	for(int i = 1; i <= m; i++) e[i].u = read(), e[i].v = read(), e[i].w = read();
	sort(e + 1, e + m + 1), node = n;
	for(int i = 1; i <= n + n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++) {
		int u = find(e[i].u), v = find(e[i].v);
		if(u == v) continue;
		val[++node] = e[i].w, fa[u] = fa[v] = node;
		add(node, u), add(node, v);
	}
	 lg = 31 - __builtin_clz(node), dfs(node);
	 for(int i = 1; i <= lg; i++)
		 for(int j = 1; j <= node; j++)
			 anc[i][j] = anc[i - 1][anc[i - 1][j]];
	 for(int _ = 1, lst = 0; _ <= q; _++) {
		 int u = (read() ^ lst) % n + 1, x = read() ^ lst;
		 int k = (read() ^ lst) % n + 1;
		 for(int i = lg; ~i; i--) {
			 int v = anc[i][u];
			 if(v && val[v] <= x) u = v;
		 }
		 if(tr.val[R[u]] < k) print(-1), lst = 0;
		 else print(lst = d[tr.binary(1, n, R[u], k)]);
		 putchar('\n');
	 }
	cerr << TIME << " ms\n";
	return 0;
}
8 P5313 [Ynoi2011] WBLT
Problem

给定一个长为 \(n\) 的序列,有 \(m\) 次查询,每次查询给定参数 \(l, r, b\),求找到最大的 \(x\) 使得存在 \(a\in[0, b)\)\(b, 2b, \dots, a + (x - 1)b\) 均出现在 \([l, r]\) 中。

保证所有数均在 \([0, 10^5]\) 之间。

\(1.5s\)\(128MB\)

Sol

看到支持离线,可以想到莫队。

先想一下暴力是怎么做的:每次枚举 \(a\),每次加 \(b\),看什么时候 \(buc_{a + kb} = 0\)

考虑怎么转化到莫队上去,莫队左右端点 \(\pm 1\) 的复杂度是 \(\mathcal{O}(1)\) 的,想一下怎么优化查询。发现如果对于每个令 \(arr_{k} = buc_{a + kb}\),然后发现这是一个类似于 \(\text{mex}\) 的东西,即值是第一个 \(0\) 的位置,可以写成前缀与一直到 \(0\)

考虑先将桶按 \(b\) 分段,将每段分别进行按位与运算,做完第 \(i\) 段时用于运算的桶全都为 \(0\),就可以直接得到答案。这显然可以用 bitset 优化。但是 STL 的 bitset 不支持分裂操作,所以需要手写。

\(b\) 较大时时间复杂度为 \(\mathcal{O}(n\sqrt{m} + m\dfrac{V}{b}\times\dfrac{b}{w})\)(不考虑排序)。

但是发现当 \(b < w\) 时时间复杂度是错的,此时就变为了 \(\mathcal{O}(n\sqrt{m} + m\dfrac{V}{b})\),因为这时候小段太短了,bitset 的压位就失去了作用。

现在考虑 \(b < 64\) 的情况。

由于 \(b\) 很小,考虑对于每一个 \(b\in[1, w - 1])\) 做一遍莫队。

这时就只需在 \(\mod b\) 的位置加 \(1\),即维护 \(b\) 剩余类。又发现 \(\mod b\) 的值较小,直接对每一个 \(i\) 维护其 \(\text{mex}\) 即可,最后的答案就是 \(\max\{\text{mex}\}\),对于每一个 \(i\)\(\text{mex}\),依然可以用 bitset 优化。

这一部分的时间复杂度为 \(\mathcal{O}(\sum\limits_{i = 1}^{w - 1}(n\sqrt{cntq_i} + i\dfrac{V}{i}\times\dfrac{1}{w}))\),由均值不等式 \(\dfrac{\sum\limits_{i = 1}^{n}a_i}{n}\le \sqrt{\dfrac{\sum\limits_{i = 1}^{n}a_i^2}{n}}\) 可得这部分的时间复杂度上界为 \(\mathcal{O}(n\times w\sqrt{\dfrac{\sum (\sqrt{cntq_i})^2}{w}} + V)\),即 \(\mathcal{O}(n\sqrt{mw} + V)\),其中的 \(\sqrt{w}\) 为常数。

时间复杂度:\(\mathcal{O}(n\sqrt{m} + m\dfrac{V}{w})\)

代码好像有 UB /gg。

Code
#include<bits/stdc++.h>
#define pb push_back
#define ios ios::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 2147483647, mod = 1e9 + 7;
const ll INF = 1e18;

const int N = 1e5 + 10, V = 1e5 + 1, MOD = 63, W = 64, bit = 6;
int n, m, len, cq, nowb;
int a[N], cnt[N], ans[N];
ull filter[W];
struct que {
	int l, r, b, id;
} q[N];
bool cmp(que a, que b) {
	int x = a.l / len, y = b.l / len;
	if (x == y) {
		if (x & 1) return a.r < b.r;
		return a.r > b.r;
	}
	return a.l < b.l;
}
vector<que> qb[W];

struct bitsett {
  int siz;
	vector<ull> v;
	void reset() { for (int i = 0; i <= siz; i++) v[i] = 0; }
	void set() { for (int i = 0; i <= siz; i++) v[i] = ~0ull; }
	void set1(int x) { v[x >> bit] |= (1ull << (x & MOD)); }
	void set0(int x) { v[x >> bit] &= (~(1ull << (x & MOD))); }
	void flip() { for (int i = 0; i <= siz; i++) v[i] = ~v[i]; }
	void operator &= (const bitsett &a) { for (int i = 0; i <= siz; i++) v[i] &= a.v[i]; }
	int mex() { for (int i = 0; ; i++) if (~v[i]) return i << bit | __builtin_ctzll(~v[i]); }
	void init(int x) {
		v.resize((x >> bit) + 2);
		siz = x >> bit;
		reset();
	}
	bool any() {
		for (int i = 0; i <= siz; i++) if (v[i]) return 1;
		return 0;
	}
};
bitsett cur, res, bl[1605], blb[W + 5];
void split(int len) {
	for (int i = 0; i <= V / len + 2; i++) bl[i].init(len);
	int ned = bl[0].siz, bg = 0, np = 0, num = 0;
	for (int i = 0; i <= cur.siz; i++)
		if (np == ned) {
			if (bg + (len & MOD) <= MOD) {
				bl[num].v[np] = (cur.v[i] & (filter[bg + (len & MOD) - 1] - (bg ? filter[bg - 1] : 0))) >> bg;
				i--;
			}
			else if (i != cur.siz) {
				bl[num].v[np] = (cur.v[i] >> bg) | ((cur.v[i + 1] & filter[bg + (len & MOD) - MOD]) << (W - bg));
			} else
				bl[num].v[np] = (cur.v[i] >> bg);
			bg = (bg + (len & MOD)) & MOD, np = 0, num++;
		} else {
			if (bg == 0) bl[num].v[np] = cur.v[i];
			else if (i != cur.siz) bl[num].v[np] = (cur.v[i] >> bg) | ((cur.v[i + 1] & filter[bg - 1]) << (W - bg));
			else bl[num].v[np] = cur.v[i] >> bg;
			np++;
		}
}
void add1(int x) {
	cnt[x]++;
	if (cnt[x] == 1) cur.set1(x);
}
void del1(int x) {
	cnt[x]--;
	if (cnt[x] == 0) cur.set0(x);
}
void add2(int x) {
	cnt[x]++;
	if (cnt[x] == 1) blb[x % nowb].set1(x / nowb);
}
void del2(int x) {
	cnt[x]--;
	if (cnt[x] == 0) blb[x % nowb].set0(x / nowb);
}

int main() {
	filter[0] = 1;
	for (int i = 1; i < W; i++) filter[i] = filter[i - 1] + (1ull << i);
	cin >> n; 
	for (int i = 1; i <= n; i++) cin >> a[i];
	cin >> m;
	for (int i = 1, l, r, b; i <= m; i++) {
		cin >> l >> r >> b;
		if (b > 63) q[++cq] = (que){l, r, b, i};
		else qb[b].pb((que){l, r, 0, i});
	}
	len = n / sqrt(cq) + 1;
	sort(q + 1, q + cq + 1, cmp);
	cur.init(V);
	for (int i = 1, l = 1, r = 0; i <= cq; i++) {
		while (l > q[i].l) add1(a[--l]);
		while (r < q[i].r) add1(a[++r]);
		while (l < q[i].l) del1(a[l++]);
		while (r > q[i].r) del1(a[r--]);
		split(q[i].b);
		res.init(q[i].b);
		res.flip();
		for (int j = 0; ; j++) {
			res &= bl[j];
			if (!res.any()) {
				ans[q[i].id] = j;
				break;
			}
		}
	}
	int mxb = 63;
	for (int i = 1; i <= mxb; i++) {
		if (qb[i].size() == 0) continue;
		memset(cnt, 0, sizeof(cnt));
		nowb = i;
		len = n / sqrt(qb[i].size()) + 1;
		sort(qb[i].begin(), qb[i].end(), cmp);
		for (int j = 0; j < i; j++) blb[j].init(V / i + 1);
		for (int j = 0, l = 1, r = 0; j < (int)(qb[i].size()); j++) {
			while (l > qb[i][j].l) add2(a[--l]);
			while (r < qb[i][j].r) add2(a[++r]);
			while (l < qb[i][j].l) del2(a[l++]);
			while (r > qb[i][j].r) del2(a[r--]);
			for (int k = 0; k < i; k++)
				ans[qb[i][j].id] = max(ans[qb[i][j].id], blb[k].mex());
		}
	}
	for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
	return 0;
}
9 P9410 『STA - R2』机场修建
Problem

\(n\) 个城市排成一列,最开始是互不连通的,每个城市初始都没有人口。
会出现以下操作 / 查询共 \(m\) 个:

  1. 1 x y 开通城市 \(x\) 和城市 \(y\) 之间的双向航班。
  2. 2 l r a 城市 \([l, r]\) 的人口数都 \(+a\)
  3. 3 x 如果所有能够到达城市 \(x\) 的人都来到城市 \(x\),城市 \(x\) 有多少人。

\(1\le n,m\le 2\times 10^5\)\(0 \le a \le 10^9\)。保证答案在 64 位有符号整形表示的范围内。

\(1s\)\(125MB\)

Sol

感觉这种序列上各个部分不一定连续的查询以及区间加,对 \(x, y\) 分别增加一个查询点这种操作很适合分块 / 根号分治。

下面是分块的题解(当然根号分治也可以做):

考虑稍微暴力的分块。区间加的话,散块部分可以直接加到全局的 \(sum\) 数组中,毕竟不是区间求和,然后整块部分直接打标记,记录块内每个连通块的大小即可。然后合并就是对于每一个块分别合并就行。但这样空间是 \(\mathcal{O}(n\sqrt{n})\) 的,考虑优化。

占内存的是要维护每一块的 \(sz\) 数组,发现 \(sz\) 数组会占用很多无用的内存。因为 \(sz\) 总数为 \(\mathcal{O}(n)\),对每一个颜色存储在每一块的数量,这里因为没有出现的不会影响答案,可以不记录,因此使用 vector 动态扩容即可。具体地,维护每个连通块出现的块的编号和它在该块内的元素数量。这样就做到了空间线性。

时间复杂度:\(\mathcal{O}(n\sqrt{n})\)。空间线性。

Code
// Problem:P9410
#include<bits/stdc++.h>
#define pb push_back
#define ios ios::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9, infi32 = 2147483647, mod = 1e9 + 7;
const ll INF = 1e18;

const int N = 2e5 + 10, B = 450 + 10;
int n, m, len, b;
int fa[N], sz[N], L[B], R[B], pos[N];
ll tg[B], sum[N], tmp[B];

vector<pair<int, ll> > v[N];

int find(int x) {
	if (fa[x] == x) return x;
	return fa[x] = find(fa[x]);
}

int main() {
//	freopen("data.in", "r", stdin);
//	freopen("myans.out", "w", stdout);
	ios
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
		sz[i] = 1;
	}
	len = sqrt(n), b = n / len + (n % len != 0);
	for (int i = 1; i <= b; i++) {
		L[i] = (i - 1) * len + 1, R[i] = min(i * len, n);
		for (int j = L[i]; j <= R[i]; j++) pos[j] = i;
	}
	for (int i = 1; i <= n; i++) v[i].pb(make_pair(pos[i], 1));
	for (int t = 1, opt, x, y, z; t <= m; t++) {
		cin >> opt >> x;
		if (opt == 1) {
			cin >> y;
			x = find(x), y = find(y);
			if (x == y) continue;
			memset(tmp, 0, sizeof(tmp));
			if (sz[x] > sz[y]) swap(x, y);
			fa[x] = y, sz[y] += sz[x], sum[y] += sum[x];
			for (int i = 0; i < v[x].size(); i++) tmp[v[x][i].first] += v[x][i].second;
			for (int i = 0; i < v[y].size(); i++) tmp[v[y][i].first] += v[y][i].second;
			v[x].clear(); v[y].clear();
			for (int i = 1; i <= b; i++)
				if (tmp[i])
					v[y].pb(make_pair(i, tmp[i]));
		} else if (opt == 2) {
			cin >> y >> z;
			int p = pos[x], q = pos[y];
			if (p == q) {
				for (int i = x; i <= y; i++) sum[find(i)] += z;
				continue;
			}
			for (int i = x; i <= R[p]; i++) sum[find(i)] += z;
			for (int i = L[q]; i <= y; i++) sum[find(i)] += z;
			for (int i = p + 1; i < q; i++) tg[i] += z;
		} else {
			x = find(x);
			ll res = sum[x];
			for (int i = 0; i < v[x].size(); i++)
				res += tg[v[x][i].first] * v[x][i].second;
			cout << res << "\n";
		}
	}
	return 0;
}
10 P7735 NOI2021 轻重边
Problem

给定一棵 \(n\) 个点的树,树上的边有黑白两种颜色,初始时所有点都是白色。要求支持 \(m\) 次操作,操作分为两种:

  1. 给定两个点 \(u, v\),对于 \(u, v\) 路径上的所有点 \(x\),将与其相连的边变为白色,然后将路径 \(u, v\) 染黑。
  2. 给定两个点 \(u, v\),求 \(u, v\) 路径经过的黑色边的数量。

\(n \le 10^5\)\(m\le 10^5\)

\(1s\)\(1GB\)

Sol

还是有点智慧的?

发现如果要正常树剖(即在实质上更改每一条边的颜色,如用数据结构维护)的话,是需要同时维护 BFS 序和 DFS 序,这是无法做到的。考虑将边的颜色进行一个转化,即满足另外的某种条件使得可以判断边的颜色,且要能够方便修改与查询。

发现其实边的修改是很麻烦的,操作修改的都是边,而边又依赖于点,考虑以点的条件来判断边的颜色。那么边的颜色显然会取决于两边的点。如果只要一端(如钦定深度较小的点)是有问题的,因为 \(u\) 可能有多个儿子,就没法判断了。发现是对于 \(x\),其所有出边均会染为白色(在路径上的除外),那么不妨给每个点赋一个权重 \(w_x\),当边的两端 \(u, v\) 的权重相等时,边才为黑色。那修改就只需要令 \(\forall x\in \text{path}(u, v), w_x\gets val\)\(val\) 为一个之前没有出现过的权值。统计显然可以用线段树解决(这个如果不会可以参考 P2486)。

于是这道题就在 \(\mathcal{O}(n\log^2n)\) 的时间复杂度内完成了。

Code
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 2147483647, mod = 1e9 + 7;
const ll INF = 1e18;

const int N = 1e5 + 10;
int n, m, cnt, numc;
int son[N], fa[N], dfn[N], rdfn[N], top[N], siz[N], d[N];
int head[N], cnte;
struct edge {
	int v, nxt;
} e[N << 1];
void adde(int u, int v) {
	e[++cnte].v = v;
	e[cnte].nxt = head[u];
	head[u] = cnte;
}
struct node {
	int s, lc, rc, tg;
	friend bool operator != (node a, node b) {
		return a.s != b.s || a.lc != b.lc || a.rc != b.rc || a.tg != b.tg;
	}
	friend bool operator == (node a, node b) {
		return !(a != b);
	}
} t[N << 2], unt;
struct segt {
	void pushup(int u) {
		t[u].s = t[u << 1].s + t[u << 1 | 1].s;
		if (t[u << 1].rc == t[u << 1 | 1].lc) t[u].s++;
		t[u].lc = t[u << 1].lc, t[u].rc = t[u << 1 | 1].rc;
	}
	void pushdown(int u, int l, int r) {
		if (t[u].tg) {
			int mid = l + r >> 1;
			t[u << 1].tg = t[u << 1 | 1].tg = t[u].tg;
			t[u << 1].lc = t[u << 1].rc = t[u << 1 | 1].lc = t[u << 1 | 1].rc = t[u].tg;
			t[u << 1].s = mid - l, t[u << 1 | 1].s = r - mid - 1;
			t[u].tg = 0;
		}
	}
	void build(int u, int l, int r) {
		if (l == r) {
			t[u].lc = t[u].rc = ++numc;
			t[u].s = 0;
			return;
		}
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	void update(int u, int l, int r, int L, int R, int col) {
		if (L <= l && r <= R) {
			t[u].tg = t[u].lc = t[u].rc = col;
			t[u].s = r - l;
			return;
		}
		pushdown(u, l, r);
		int mid = l + r >> 1;
		if (L <= mid) update(u << 1, l, mid, L, R, col);
		if (mid < R) update(u << 1 | 1, mid + 1, r, L, R, col);
		pushup(u);
	}
	node query(int u, int l, int r, int L, int R) {
		if (L <= l && r <= R) return (node){t[u].s, t[u].lc, t[u].rc, 0};
		pushdown(u, l, r);
		int mid = l + r >> 1;
		node ax = unt, ay = unt, res = unt;
		if (L <= mid) ax = query(u << 1, l, mid, L, R);
		if (mid < R) ay = query(u << 1 | 1, mid + 1, r, L, R);
		if (ax == unt) return ay;
		else if (ay == unt) return ax;
		res.lc = ax.lc, res.rc = ay.rc;
		res.s = ax.s + ay.s + (ax.rc == ay.lc);
		return res;
	}
} tr;

void dfs1(int u, int f) {
	siz[u] = 1, fa[u] = f, d[u] = d[f] + 1;
	int maxp = 0;
	for (int i = head[u], v; i != 0; i = e[i].nxt)
		if ((v = e[i].v) != f) {
			dfs1(v, u);
			siz[u] += siz[v];
			if (siz[maxp] < siz[v]) maxp = v;
		}
	son[u] = maxp;
}
void dfs2(int u, int f) {
	top[u] = f, dfn[u] = ++cnt, rdfn[cnt] = u;
	if (!son[u]) return;
	dfs2(son[u], f);
	for (int i = head[u], v; i != 0; i = e[i].nxt)
		if ((v = e[i].v) != fa[u] && v != son[u])
			dfs2(v, v);
}
void upd(int u, int v, int col) {
	while (top[u] != top[v]) {
		if (d[top[u]] < d[top[v]]) swap(u, v);
		tr.update(1, 1, n, dfn[top[u]], dfn[u], col);
		u = fa[top[u]];
	}
	if (d[u] < d[v]) swap(u, v);
	tr.update(1, 1, n, dfn[v], dfn[u], col);
}
int getlca(int u, int v) {
	while (top[u] != top[v]) {
		if (d[top[u]] < d[top[v]]) swap(u, v);
		u = fa[top[u]];
	}
	if (d[u] < d[v]) swap(u, v);
	return v;
} 
int qry(int u, int v) {
	int lcl = -1, res = 0, lca = getlca(u, v); node p;
	while (top[u] != top[lca]) {
		p = tr.query(1, 1, n, dfn[top[u]], dfn[u]);
		res += p.s + (p.rc == lcl);
		lcl = p.lc;
		u = fa[top[u]];
	}
	p = tr.query(1, 1, n, dfn[lca], dfn[u]);
	res += p.s + (p.rc == lcl);
	lcl = -1;
	while (top[v] != top[lca]) {
		p = tr.query(1, 1, n, dfn[top[v]], dfn[v]);
		res += p.s + (p.rc == lcl);
		lcl = p.lc;
		v = fa[top[v]];
	}
	p = tr.query(1, 1, n, dfn[lca], dfn[v]);
	res += p.s + (p.rc == lcl);
	return res;
}
void clr() {
	memset(t, 0, sizeof(t));
	memset(head, 0, sizeof(head));
	memset(son, 0, sizeof(son));
	memset(fa, 0, sizeof(fa));
	memset(d, 0, sizeof(d));
	memset(siz, 0, sizeof(siz));
	memset(dfn, 0, sizeof(dfn));
	memset(rdfn, 0, sizeof(rdfn));
	memset(top, 0, sizeof(top));
	memset(e, 0, sizeof(e));
	cnte = cnt = numc = 0;
	unt = (node){0, 0, 0, 0};
}

void solve() {
	clr();
	cin >> n >> m;
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		adde(u, v), adde(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	tr.build(1, 1, n);
	for (int i = 1, opt, u, v; i <= m; i++) {
		cin >> opt >> u >> v;
		if (opt == 1)
			upd(u, v, ++numc);
		else
			cout << qry(u, v) << "\n";
	}
}

int main() {
	ios :: sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int T;
	cin >> T;
	while (T--)
		solve();
	return 0;
}
11 P4689 [Ynoi2016] 这是我自己的发明
Problem

给定一棵 \(n\) 个点的树,初始时的根节点为 \(1\)。要求支持 \(m\) 个操作,种类如下:

  • 1 x,将根节点换为 \(x\)
  • 2 x y,给定两个点 \(x, y\),求从 \(x\) 的子树中选一个点,\(y\) 的子树中选一个点,点权相等的情况数。

\(n\le 10^5\)\(m\le 5\times 10^5\)\(a_i\le 10^9\)

\(1.5s\)\(512MB\)

Sol

首先换根操作肯定是假的,然后这个时候 \(x, y\) 的子树在 dfn 上肯定是连续的一段/两段。分别记这四个区间为 \(a, b, c, d\)\(f([l, r], [l', r'])\) 为从 \([l, r]\)\([l', r']\) 中分别选一个数,权相等的情况数,那么显然有 \(f(a\bigcup b, c\bigcup d) = f(a, c) + f(a, d) + f(b, c) + f(b, d)\)。然后有 \(f([l, r], [l', r']) = f([1, r], [1, r']) - f([1, l - 1], [1, r' - 1]) - f([1, l' - 1], [1, r]) + f([1, l' - 1], [1, r' - 1])\)。所有就只需要考虑前缀了。记 \(g(i, j) = f([1, i], [1, j])\)。若将 \(g\) 看作单点查询,则最多有 \(\mathcal{O}(m)\)次查询,发现这个东西显然可以莫队解决,于是就完了。

实现上可以将前面最开始的 \(4\) 个区间可以通过将维护的区间范围由 \([1, n]\) 变为 \([1, 2n]\) 更加方便。

时间复杂度:\(\mathcal{O}(m\sqrt{n})\)

Code
// Problem:P4689
#include<bits/stdc++.h>
#define pb push_back
#define ios ios::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 2147483647, mod = 1e9 + 7;
const ll INF = 1e18;

const int N = 1e5 + 10, QN = 5e5 + 10;
int n, m, num, rt, cnt, cq, len, queq;
int a[N], b[N], t[N], dfn[N], rdfn[N << 1], siz[N], c1[N], c2[N], fa[N][20], d[N];
ll res, ans[QN];
int head[N], cnte;
struct edge {
	int v, nxt;
} e[N << 1];
void adde(int u, int v) {
	e[++cnte].v = v;
	e[cnte].nxt = head[u];
	head[u] = cnte;
}
struct que {
	int l, r, typ, id;
} q[QN << 2];
bool cmp(que a, que b) {
	int x = a.l / len, y = b.l / len;
	if (x == y) {
		if (x & 1) return a.r < b.r;
		return a.r > b.r;
	}
	return a.l < b.l;
}

void dfs_init(int u, int f) {
	dfn[u] = ++cnt, rdfn[cnt] = u, siz[u] = 1, fa[u][0] = f, d[u] = d[f] + 1;
	for (int i = 1; i <= 18; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for (int i = head[u], v; i != 0; i = e[i].nxt)
		if ((v = e[i].v) != f) {
			dfs_init(v, u);
			siz[u] += siz[v];
		}
}
int getnode(int u, int v) {
	for (int i = 18; i >= 0; i--)
		if (d[fa[u][i]] > d[v])
			u = fa[u][i];
	return u;
}
void add1(int x) { c1[x]++; res += c2[x]; }
void del1(int x) { c1[x]--; res -= c2[x]; }
void add2(int x) { c2[x]++; res += c1[x]; }
void del2(int x) { c2[x]--; res -= c1[x]; }

int main() {
//	freopen("data.in", "r", stdin);
//	freopen("myans.out", "w", stdout);
	ios
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		b[i] = a[i];
	}
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		adde(u, v), adde(v, u);
	}
	dfs_init(rt = 1, 0);
	sort(b + 1, b + n + 1);
	num = unique(b + 1, b + n + 1) - b - 1;
	for (int i = 1; i <= n; i++) {
		a[i] = lower_bound(b + 1, b + num + 1, a[i]) - b;  
		rdfn[n + i] = rdfn[i];
	}
	for (int i = 1, opt, x, y; i <= m; i++) {
		cin >> opt >> x;
		if (opt == 1) rt = x;
		else {
			cin >> y; queq++;
			int a, b, c, d;
			if (rt == x) a = 1, b = n;
			else if (dfn[x] < dfn[rt] && dfn[rt] <= dfn[x] + siz[x] - 1) {
				int pos = getnode(rt, x);
				a = dfn[pos] + siz[pos], b = n + dfn[pos] - 1;
			} else a = dfn[x], b = dfn[x] + siz[x] - 1;
			if (rt == y) c = 1, d = n;
			else if (dfn[y] < dfn[rt] && dfn[rt] <= dfn[y] + siz[y] - 1) {
				int pos = getnode(rt, y);
				c = dfn[pos] + siz[pos], d = n + dfn[pos] - 1;
			} else c = dfn[y], d = dfn[y] + siz[y] - 1;
			if (a > c) swap(a, c), swap(b, d);
			q[++cq] = (que){min(b, d), max(b, d), 1, queq};
			q[++cq] = (que){min(a - 1, c - 1), max(a - 1, c - 1), 1, queq};
			q[++cq] = (que){min(b, c - 1), max(b, c - 1), -1, queq};
			q[++cq] = (que){min(d, a - 1), max(d, a - 1), -1, queq};
		}
	}
	len = 2 * n / sqrt(cq) + 1;
	sort(q + 1, q + cq + 1, cmp);
	for (int i = 1, l = 0, r = 0; i <= cq; i++) {
		while (l < q[i].l) add1(a[rdfn[++l]]);
		while (r < q[i].r) add2(a[rdfn[++r]]);
		while (l > q[i].l) del1(a[rdfn[l--]]);
		while (r > q[i].r) del2(a[rdfn[r--]]);
		ans[q[i].id] += q[i].typ * res;
	}
	for (int i = 1; i <= queq; i++) cout << ans[i] << "\n";
	return 0;
}
posted @ 2023-12-27 10:49  Pengzt  阅读(48)  评论(0编辑  收藏  举报