2024.12 做题记录

这个月可能没做啥特别大的思维题。但学的东西比较多。基本没记录网络流作业里面的那些题。太水的且没有任何启发性的我也略过了。

网络流

网络流问题是一类解决从源点 s 到汇点 t,边有容量限制的情况下使得流最大的一类问题。

最基本的思想是一类贪心,考虑从源点 s 到汇点 t 任意选择一条路径,使得流的最小值非 0,也就是找到一条合法增广路。然后减去增广路流量,继续这个过程,直到找不到合法增广路为止。但很不幸这个贪心是假的。

我们考虑给贪心加上反悔操作。当我们减去一条边的流量时,给它的反边加上这个流量,也就是我们允许退流操作。这样再跑这种增广路算法,正确性就有保证了,这种方法统称为 Ford-Fulkerson 算法,而这种单路增广的算法被称为 Edmond-Karp (EK) 算法。但这种方法的效率并不高。

于是我们考虑我们能不能在正确性有保证的情况下进行 多路增广。怎样增广其实非常显然,我们只要阻止这一次增广造成的退流即可,于是我们考虑一个有向无环图。图上最经典的 DAG 图应该就是最短路图了,于是我们考虑每次增广完都跑最短路建出最短路图,在这个图上用 DFS 跑多路增广即可。这就是著名的 Dinic 算法。

当然还需要一个保证复杂度的优化,当然我也不知道为什么能优化。就是 DFS 的时候每条边最多流一次,流完下次再来就不流了。就是对每个点记个 cur 数组就行了。这就是当前弧优化。总时间复杂度为 O(n2m),但我不会证,但其实随意了。

001. P3376 【模板】网络最大流 - Dinic 版本

难度:提高+ / 省选-

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 205;
const LL MAXM = 10005;
const LL INF = 1e18 + 5;
LL n, m, s, t, tot = 1, V[MAXM], W[MAXM], head[MAXN], nxt[MAXM], cur[MAXN], ans = 0, Dist[MAXN];
void AddEdge(LL u, LL v, LL w)
{
	tot ++;
	V[tot] = v; W[tot] = w;
	nxt[tot] = head[u]; head[u] = tot;
	return;
}
bool BFS()
{
	for(LL i = 1; i <= n; i ++) Dist[i] = 0;
	queue<LL> Q; Q.push(s); Dist[s] = 1;
	while(!Q.empty())
	{
		LL u = Q.front(); Q.pop();
		for(LL i = head[u]; i; i = nxt[i])
		{
			LL v = V[i], w = W[i];
			if(!w || Dist[v]) continue;
			Q.push(v); Dist[v] = Dist[u] + 1;
			if(v == t) return true;
		}
	}
	return false;
}
LL DFS(LL u, LL Lim)
{
	if(u == t) return Lim;
	LL ress = Lim;
	for(LL i = cur[u]; i && ress; i = nxt[i])
	{
		cur[u] = i;
		LL v = V[i], w = W[i];
		if(!w || Dist[v] != Dist[u] + 1) continue;
		LL k = DFS(v, min(ress, w)); 
		if(!k) Dist[v] = 0;
		W[i] -= k; W[i ^ 1] += k;
		ress -= k;
	}
	return Lim - ress;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m >> s >> t;
	for(LL i = 1; i <= m; i ++)
	{
		LL u, v, w;
		cin >> u >> v >> w;
		AddEdge(u, v, w); AddEdge(v, u, 0);
	}
	LL nowflow = 0;
	while(BFS())
	{
		for(LL i = 1; i <= n; i ++) cur[i] = head[i];
		while(nowflow = DFS(s, INF)) ans += nowflow; 
	}
	cout << ans << '\n';
	return 0;
}

002. *P4311 士兵占领

难度:省选 / NOI-

Key Observation 1 正难则反:先把所有格子都放满骑士,我们考虑最多可以去掉多少种骑士。骑士有两种,一种是满足一种贡献的骑士,一种是满足两种贡献的骑士。显然最后只满足一种贡献的骑士是不可忽略的。所以我们 考虑怎么去掉最多的满足两种贡献的骑士

Key Observation 2 网络流建模,最大流建模:我们考虑怎样构造二贡献骑士。考虑如果第 i 行最多可以贡献多少个可去掉的二贡献骑士,其实就是 这一行格子总数 - 被 Ban 掉的格子总数 - 需要的骑士总数,特殊的,如果这个值小于 0,那么说明构造不出任何方案使得限制成立。列是与行同理的。那么我们考虑由源点 s 向每一个行编号连一条容量为 p 的边,p 代表第 i 行最多可以贡献多少个可去掉的二贡献骑士个数。同理,我们由每一个列编号连向汇点 t 一条容量为 p 的边,定义是同理的。考虑行与列之间的连边,对于一个点 (i,j),如果没被 Ban,那么由行编号为 i 的点向列编号为 j 的点连一条容量为 1 的边,表示可以构造出一个二贡献骑士。

于是答案就是没被 Ban 的格子总数减去最大流。

003. P10930 异象石

难度:省选 / NOI-

维护树上动态路径并的题目。

Key Observation 1 既然动态加点,我们动态的考虑问题。考虑加一个点后会对总共的有什么贡献。其实结论我认为不难猜,就是 dist(u1,i)+dist(i,u2)dist(u1,u2),其中 u1 代表第一个 dfn 小于 udfn 的选择点,u2 则是第一个 dfn 大于 udfn 的选择点。

考虑简单证明 Key Observation 1,假设以 1 为根,分类讨论:

  1. u1u2i 在同一条链上,其实就是根本没有增加贡献,其实就是一加一减,把贡献全消了。
  2. u1i 在同一条链上,u2 不在,那么与第一种情况是同理的,也是把贡献全消除了。
  3. u2i 在同一条链上,u1 不在,与情况二对称。
  4. u1u2i 在不同子树,那么就是容斥,两个贡献然后把算重的两段合成一段删去就是了。
  5. u1u2 在同一条链上,i 不在,这种情况显然不存在。

这个过程可以用 set 维护,其实维护方式我认为没啥难的,注意处理不存在 u1,u2 的情况就行了。

004. P4318 完全平方数

难度:省选 / NOI-

这个做法应该完全没有这个难度。性质题。

Key Observation 1 正难则反,考虑是完全平方数正整数倍的数。

Key Observation 2 答案满足可二分性,应该二分后判断。

Key Observation 3 满足条件的基础是 k2|d,此时 k 就很小。且有用的只有质数,先预处理出来然后暴力 DFS 容斥。

Key Observation 4 考虑你容斥的过程,因为你都是质数,所以你的 LCM 等价于乘积。乘积的增长速度巨快,完全容斥不满,于是就跑的飞快。

Key Observation 5 考虑你验证的质数还是非常多,于是你考虑把质数的平方从大到小排序。每次二分寻找可行上界即可。

005. P4451 [国家集训队] 整数的lqp拆分

难度:省选 / NOI-

Key Observation 1 较为显然但重要的一个式子,就是设分拆 i 的答案为 gi,那么 gi=fi×gni

发现这个式子很像卡特兰数的一种递推式子,可以考虑生成函数。但是我不会,于是打表找规律。

Key Observation 2 发现,fi=2×fi1+fi2。于是你可以矩阵加速递推过掉前面的点。

Key Observation 3 注意到矩阵的幂满足数论中的费马小定理。所以边读入边取模,然后就做完了。

二项式反演

就是一个式子:

g(i)=x=in(xi)f(x)f(i)=x=in(1)xi(xi)g(x)

二项式反演主要用于刻画一类,至少选 k 个和恰好选 k 个的问题。具体的,这里的 g(i) 一般对应至少 k 个,而 f(i) 一般对应恰好 k 个。

006. *P4859 已经没有什么好害怕的了

难度:省选 / NOI-

我们首先把 k 变为 n+k2

恰好 k 个的限制过于严格,我们考虑放宽变为至少 k 个,然后二项式反演求出恰好 k 个的答案。

gi 代表至少选 i 个的方案数,fi 代表恰好选 i 个的方案数。

我们先把 ab 都从小到大排序,方便我们求解 gi

Key Observation 1 考虑动态规划,定义 Fi,j 代表 a 数组中的前 i 个位置,对应了 j 组的方案数,其他对不对应我不管。

那么有显然的转移,Fi,j=Fi1,j+Fi1,j1×(ti(j1))ti 代表 b 数组中有多少个小于等于 ai 的数。

然后 gi=Fn,i×(ni)!,表示其他 (ni) 个对随意组合,不管行不行。然后二项式反演求出 fi 就做完了。

动态 DP

动态 DP 是一种思想,将 DP 中的简单转移写作矩阵乘法的形式或 (max/min,+) 矩阵乘法的形式。然后修改使用线段树维护矩阵即可。

最后所需要的值一般是所有矩阵的乘积,也就是线段树根节点维护的值。

007. [ABC246Ex] 01? Queries

难度:省选 / NOI-

fi,0/1 为以 0/1 结尾的本质不同子序列数。则有转移:

s[i] = '0'

  1. fi,0=fi1,0
  2. fi,1=fi1,0+fi1,1+1

s[i] = '1'

  1. fi,0=fi1,0+fi1,1+1
  2. fi,1=fi1,1

s[i] = '?'

  1. fi,0=fi1,0
  2. fi,1=fi1,0+fi1,1+1

将初始矩阵设为 3×1 的,分别记录有多少个 fi,0,fi,11,注意需要记录 1 是因为转移方程里面有 1

三种方程的转移矩阵都是显然的。把它们放到线段树上。然后你要做的就是单点修改即可了。

008. P1453 城市环路

难度:提高+ / 省选-

主要不是想记题咋做。是记一类基环树的判环 Trick。很简单,就是用并查集维护要断的环的其中两个相邻节点。然后强制不选 S 跑一遍树形 DP,再强制不选 T 跑一遍树形 DP。跟环装 DP 有异曲同工之妙。

*009. P8867 [NOIP2022] 建造军营

难度:省选 / NOI-

见我的 2022-2023 赛季选讲 PPT

010. P8820 [CSP-S 2022] 数据传输

难度:省选 / NOI-

见我的 2022-2023 赛季选讲 PPT

011. P8820 [CSP-S 2022] 星战

难度:省选 / NOI-

见我的 2022-2023 赛季选讲 PPT

012. P6190 [NOI Online #1 入门组] 魔法

难度:省选 / NOI-

简单题。考虑经典 Trick —— 将图转化为邻接矩阵的形式。设 fk,i,j 代表使用 k 次魔法,从 ij 的最短路。但是注意到 k 很大,所以我们考虑用矩阵乘法优化转移。

转移矩阵就是将一条边免费。矩阵应为 (min,+) 矩阵。

(min,+) 矩阵乘法

因为 min+ 具有分配率。且其转移形式又类似矩阵乘法,所以可以定义广义的矩阵乘法,即 (min,+) 矩阵乘法。

Matrix friend operator * (Matrix P, Matrix Q)
{
	Matrix R; R.Clear();
	for(LL k = 1; k <= n; k ++)
		for(LL i = 1; i <= n; i ++)
			for(LL j = 1; j <= n; j ++)
				R.A[i][j] = min(R.A[i][j], P.A[i][k] + Q.A[k][j]);
	return R;
}

然后你暴力转移就好了。没啥难的。

013. P3203 [HNOI2010] 弹飞绵羊

难度:省选 / NOI-

Key Observation 1 根号平衡题。定义 fi 代表从 i 跳出 i 所在块所需要的步数。ti 表示 i 跳出所在块所到达的位置。然后对序列按照 n 分块,暴力跳,暴力处理。每次询问最多跳 n 次。预处理也是 O(nn)。(弹飞绵羊 Trick)

014. P3396 哈希冲突

难度:提高+ / 省选 -

Key Observation 1 如果模数 p>n,则暴力计算复杂度是正确的。

Key Observation 2 如果模数 p<n,则可以预处理 fi,j 表示模数为 i,余数为 j 的权值和,然后直接查就可以了。

*015. P1989 无向图三元环计数

难度:提高+ / 省选-

很难想到。图上三元环计数板子题。

三元环计数

考虑给每条边定向。度数小的向度数大的连边。你统计的就是 u to vv to wu to w 的个数。

这样你直接暴力枚举复杂度就是正确的。

首先不难发现这个图是一个有向无环图。但其实也挺难发现。比如原图就是一个三元环,那么连出来的图也是三元环。为了保证原图的偏序性质,如果度数相等,那么比较编号大小即可。

接下来证明:

  1. 如果 u 点在原图上的度 m,那复杂度显然是对的。
  2. 反之,那么由于边只有 m 条,所以它往出连的点最多只有 m 个。然后复杂度依然是对的。然后做完了。

016. HH 的项链

难度:提高+ / 省选-

Bonus:这题用莫队做会简单到爆,但是貌似被卡了?不知道,没试过。

这题挺典的。考虑离线扫描线,把询问挂右端点上。

思考:一个点有贡献意味着什么?答案是与这个点同色的最靠近他的左边的点小于询问区间左端点。

这是好维护的,我们记每个位置的 las 即可。每次加入一个新点,把其 las 贡献消去,加上自己的贡献。然后回答当前节点为右端点的全部询问,就是区间 [L,R] 有贡献的点数。这个用树状数组维护即可。

Bonus2:这题相当于把贡献挂自己身上,还有一个题是把询问挂在自己 las 身上。总之怎么好做怎么做。

2 - SAT 问题

挺精妙的一类问题。巧妙地将变量间的适应性问题转化为了图论相关问题。2 - SAT 一般有两种条件。一个是与,也就是合取,那么对于这类条件我们只需要给相应的变量直接赋值就可以了。而真正的难点在于或,也就是析取,那么我们考虑建图连边。

边的意思:如果 uv 连了一条有向边,那么条件为 uv。又考虑到每个点有两种取值,那么我们可以考虑显然的拆点,即 u 点代表 u 取真的情况。而 u+n 则代表 u 取假的情况。

举个例子,如果题目中说 u 真或 v 真。那么其实等价于,若 u 假,则 v 真,若 v 假,则 u 真,那么从 u+n 连向 v,从 v+n 连向 u 即可。

再讨论一种。比如如果条件为 u 真或 v 假。那么就等价于,若 u 假则 v 假,若 v 真则 u 真,那么从 u+n 连向 v+n,从 v 连向 u 即可。

对于不合法的情况,很简单,就是若 uu+n 在同一个强联通分量里面,也就是出现了 若 u 真,则 u 假这样的矛盾行为,则就是无解。

对于构造解这种行为。那么其实我们有一种非常显然的贪心,就是我们要让这个值影响的越少越好。于是我们考虑刻画“越少越好”这一想法。其实就是在缩点后的拓扑序上,编号越大的越好。但是由于你用了 Tarjan 缩点,所以你先缩的强联通分量必然是靠近叶子的。于是你得到的恰好就接近于拓扑逆序。

于是我们只关注强连通分量的编号就好了。具体的,如果 u 的编号比 u+n 大,那么 u1,否则取 0。做完了。

017. 【模板】2-SAT

代码实现:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
vector<int> Adj[MAXN];
int n, m, dfn[MAXN], SCCNO[MAXN], SCCcnt = 0, sizSCC[MAXN], low[MAXN], Stk[MAXN], Top = 0, dfsTime = 0;
void Tarjan(int u)
{
	Stk[++ Top] = u; dfn[u] = low[u] = ++ dfsTime;
	for(int i = 0; i < Adj[u].size(); i ++)
	{
		int v = Adj[u][i];
		if(!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!SCCNO[v]) low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u])
	{
		sizSCC[++ SCCcnt] = 1; SCCNO[u] = SCCcnt;
		while(Stk[Top] != u)
		{
			SCCNO[Stk[Top]] = SCCcnt;
			sizSCC[SCCcnt] ++;
			Top --;
		}
		Top --;
	}
	return;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= m; i ++)
	{
		int u, a, v, b;
		cin >> u >> a >> v >> b;
		Adj[u + (a ^ 1) * n].push_back(v + b * n);
		Adj[v + (b ^ 1) * n].push_back(u + a * n);
	}
	for(int i = 1; i <= (n << 1); i ++)
	{
		if(dfn[i]) continue;
		dfsTime = 0; Tarjan(i);
	}
	for(int i = 1; i <= n; i ++)
	{
		if(SCCNO[i] == SCCNO[i + n])
		{
			cout << "IMPOSSIBLE" << '\n';
			return 0;
		}
	}
	cout << "POSSIBLE" << '\n';
	for(int i = 1; i <= n; i ++) cout << (SCCNO[i] > SCCNO[i + n]) << " ";
	cout << '\n';
	return 0;
}

莫队

莫队是一种优雅的暴力。真就是暴力。

考虑离线所有询问,进行一定排序后暴力从一个询问按格转移到下一个区间。如从 [2,3] 转移到 [3,4],那么就加上 4 的贡献,变为 [2,4],然后删去 2 的贡献,变为 [3,4],就是这样的。

当然纯暴力是不行的。我们需要找一种让扩展次数尽量少的方法。其实是曼哈顿距离最小生成树。但由于求最小生成树的复杂度过高。莫队给了我们一种非常神奇的区间排序方法。

考虑将序列分块,每次比较两个区间的时候,优先比较他们左端点的所在块,如果块不同,那么块小的排在前面,如果相等,那么右端点大的排在前面。

证明你可以把一个区间 [L,R] 映射为二维平面坐标系的一个点 (l,r),这是类扫描线算法的一个基础思想。然后你尝试跑一遍就可以很容易的看出来复杂度是 O(nn)(假设 nm 同阶)。

使用莫队算法的前提是你可以快速地扩展一个端点,可以快速的删除一个点。如果不能快速做到其中之一,那么可能需要一种莫队的变种——回滚莫队来解决这类问题。

019. P2709 小B的询问

难度:提高+ / 省选-

算是莫队的模板题了。

考虑扩展一个点:就是开一个桶统计数的出现次数。答案计算可以拆完全平方式:(ci+1)2ci2=2×ci+1

考虑删除一个点:也是统计出现次数。答案计算也可以拆完全平方式:ci2(ci1)2=2×ci1

代码实现:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
int n, m, k, A[MAXN], LenBlock, Blo[MAXN], cnt[MAXN], ans = 0, Ans[MAXN];
struct Qry
{
	int L, R, id;
} Q[MAXN];
bool Cmp(Qry u, Qry v)
{
	if(Blo[u.L] == Blo[v.L]) return u.R < v.R;
	return Blo[u.L] < Blo[v.L];
}
void Add(int x)
{
	cnt[A[x]] ++;
	ans += 2 * cnt[A[x]] - 1;
	return;
}
void Del(int x)
{
	cnt[A[x]] --;
	ans -= 2 * cnt[A[x]] + 1;
	return;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m >> k; LenBlock = sqrt(n);
	for(int i = 1; i <= n; i ++) Blo[i] = (i - 1) / LenBlock + 1;
	for(int i = 1; i <= n; i ++) cin >> A[i];
	for(int i = 1; i <= m; i ++) { cin >> Q[i].L >> Q[i].R; Q[i].id = i; }
	sort(Q + 1, Q + m + 1, Cmp);
	int nowL = 1, nowR = 0;
	for(int i = 1; i <= m; i ++)
	{
		while(nowL > Q[i].L) Add(-- nowL);
		while(nowR < Q[i].R) Add(++ nowR);
		while(nowL < Q[i].L) Del(nowL ++);
		while(nowR > Q[i].R) Del(nowR --);
		Ans[Q[i].id] = ans;
	}
	for(int i = 1; i <= m; i ++) cout << Ans[i] << '\n';
	return 0;
}

019. P1494 [国家集训队] 小 Z 的袜子

难度:提高+ / 省选-

也是板子。先考虑概率转计数。每次选的总数是固定的,就是 (cnt2)

维护方面也是维护一个桶统计出现次数。每种颜色的贡献是 (scnt2)。随便维护一下这个即可。

020. P4462 [CQOI2018] 异或序列

难度:提高+ / 省选-

板子,继续开桶维护。考虑到异或可以前缀和,于是维护区间异或等价于维护前缀异或。然后做完了。

*021. P4137 Rmq Problem / mex

难度:省选 / NOI-

莫队+值域分块科技题。考虑修改,非常简单就是开桶维护。

考察 mex 的性质。就是从 0 开始找,找到的第一个 i 满足 cnti=0 的就是区间 mex。暴力找的询问肯定是复杂度假的。于是我们考虑对值域分块。

考虑记另一个桶,记每一块有多少个数出现。询问时,如果这个块有的数个数等于块长,那么不可能存在 mex,直接跳过。否则块内暴力查找。

这样最多查 n 个块。最多块内查 n 次。复杂度为 O(nn+nV),可以通过。这个复杂度分析可以沿用到重链上二分上面。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 200005;
LL n, m, A[MAXN], LenBlock, B = 500, Blo[MAXN], cntBlo[MAXN], cnt[MAXN], ans[MAXN];
struct Qry
{
	LL L, R, id;
} Q[MAXN];
bool Cmp(Qry x, Qry y)
{
	if(Blo[x.L] != Blo[y.L]) return Blo[x.L] < Blo[y.L];
	else return x.R < y.R;
}
void Add(LL x)
{
	cnt[A[x]] ++;
	if(cnt[A[x]] == 1) cntBlo[A[x] / B] ++;
	return;
}
void Del(LL x)
{
	cnt[A[x]] --;
	if(cnt[A[x]] == 0) cntBlo[A[x] / B] --;
	return;
}
LL Query() // 值域分块
{
	LL res = MAXN;
	for(LL i = 1; i <= B; i ++)
	{
		if(cntBlo[i - 1] == B) continue;
		for(LL j = (i - 1) * B; j < i * B; j ++)
			if(!cnt[j]) { res = j; return res; }
	}
	return res;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m; LenBlock = sqrt(n); 
	for(LL i = 1; i <= n; i ++) Blo[i] = (i - 1) / LenBlock + 1;
	for(LL i = 1; i <= n; i ++) { cin >> A[i]; }
	for(LL i = 1; i <= m; i ++) { cin >> Q[i].L >> Q[i].R; Q[i].id = i; }
	sort(Q + 1, Q + m + 1, Cmp);
	LL nowL = 1, nowR = 0;
	for(LL i = 1; i <= m; i ++)
	{
		while(nowL > Q[i].L) Add(-- nowL);
		while(nowR < Q[i].R) Add(++ nowR);
		while(nowL < Q[i].L) Del(nowL ++);
		while(nowR > Q[i].R) Del(nowR --);
		ans[Q[i].id] = Query();
	}
	for(LL i = 1; i <= m; i ++) cout << ans[i] << '\n';
	return 0;
}

022. P4396 [AHOI2013] 作业

难度:省选 / NOI-

非常简单的题。也是莫队+加值域分块科技。唯一需要注意的就是询问区间长度小于 n 的时候需要暴力查找,因为它可能不会触发任何一条查找操作。

023. P3730 曼哈顿交易

难度:省选 / NOI-

另类值域分块。这个值域分块考虑维护热度。扩展和删除的时候稍显麻烦,不过也很套路就是了。

void Add(int x)
{
	nowcnt ++;
	-- cnt2[cnt[A[x]]];
    -- cntBlo[cnt[A[x]] / B];
    ++ cnt2[++ cnt[A[x]]];
    ++ cntBlo[cnt[A[x]] / B];  
	return;
}
void Del(int x)
{
	-- cnt2[cnt[A[x]]];
    -- cntBlo[cnt[A[x]] / B];
    ++ cnt2[-- cnt[A[x]]];
    ++ cntBlo[cnt[A[x]] / B];  
	return;
}

支持修改的莫队(带修莫队)

就是高维莫队。考虑在每个询问上记录这个询问前面已经有多少次修改了。

然后在扩展左右端点的基础上,再加一个扩展修改位置即可。注意修改的时候判断修改点在询问区间里面才能修改。

024. P1903 [国家集训队] 数颜色 / 维护队列

难度:提高+ / 省选-

板子题:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
int n, m, A[MAXN], LenBlock, Blo[MAXN], cnt[MAXN], cntQ = 0, cntC = 0, anss = 0, ans[MAXN];
inline int read()
{
	int x = 0, f = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
	return x * f;
}
struct Qry
{
	int L, R, tim, id;
} Q[MAXN];
struct Updat
{
	int Pos, col;
} C[MAXN];
bool Cmp(Qry x, Qry y)
{
	if(Blo[x.L] != Blo[y.L]) return Blo[x.L] < Blo[y.L];
	if(Blo[x.R] != Blo[y.R]) return Blo[x.R] < Blo[y.R];
	return x.tim < y.tim;
}
void Add(int x)
{
	cnt[A[x]] ++;
	if(cnt[A[x]] == 1) anss ++;
	return;
}
void Del(int x)
{
	cnt[A[x]] --;
	if(cnt[A[x]] == 0) anss --;
	return;
}
void Solve(int x, int i)
{
	if(C[x].Pos >= Q[i].L && C[x].Pos <= Q[i].R)
	{
	    cnt[A[C[x].Pos]] --; if(cnt[A[C[x].Pos]] == 0) anss --;
	    cnt[C[x].col] ++; if(cnt[C[x].col] == 1) anss ++;
	}
	swap(C[x].col, A[C[x].Pos]);
	return;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m; 
	for(int i = 1; i <= n; i ++) cin >> A[i];
	for(int i = 1; i <= m; i ++) 
	{
		char Opt; cin >> Opt;
		if(Opt == 'Q')
		{
			cntQ ++;
			cin >> Q[cntQ].L >> Q[cntQ].R; Q[cntQ].id = cntQ; Q[cntQ].tim = cntC;
		} 
		else
		{
			cntC ++;
			cin >> C[cntC].Pos >> C[cntC].col;
		}
	}
	LenBlock = pow(n, 0.66);
	for(int i = 1; i <= n; i ++) Blo[i] = (i - 1) / LenBlock + 1;
	sort(Q + 1, Q + cntQ + 1, Cmp);
	int nowL = 1, nowR = 0, tt = 0;	
	for(int i = 1; i <= cntQ; i ++)
	{
		while(nowL > Q[i].L) Add(-- nowL);
		while(nowR < Q[i].R) Add(++ nowR);
		while(nowL < Q[i].L) Del(nowL ++);
		while(nowR > Q[i].R) Del(nowR --);
		while(tt < Q[i].tim) Solve(++ tt, i);
		while(tt > Q[i].tim) Solve(tt --, i);
		ans[Q[i].id] = anss;
	}
	for(int i = 1; i <= cntQ; i ++) cout << ans[i] << '\n';
	return 0;
}

025. P2464 [SDOI2008] 郁闷的小 J

难度:提高+ / 省选-

一样的板子题,注意先离散化一下就好。

026. P4151 [WC2011] 最大XOR和路径

难度:省选 / NOI-

一个非常重要有启发性的结论是:图上路径可以拆成一棵 DFS 树套上很多环的形式。

然后我们从 1 开始,任意遍历出一棵 DFS 树,然后暴力把遇到的环插入异或线性基里面。

最后我们的答案就是 DFS 树的答案异或上所有环的异或最大值。所以线性基求一下异或最大即可。然后做完了。

posted @   DataEraserQ  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示