新学算法练习记录

last upd: 21.8.20

目录:


图论方向

网络流与二分图

太多了,专门开了一个

Hall 定理

1. P3488 - LYZ-Ice Skates

就是把人连向鞋子,看看有没有完美匹配。运用 Hall 定理。一个人连向的是往后 \(d+1\) 个鞋子的区间,那么一个人的集合连向的鞋子集合想象一下就是某些处并起来了,某些处断开,也是若干个区间。考虑对断开出来的若干个连通块,每个都可以把能不超出去的人都给补上得到更强的条件。然后显然每个连通块各自都是要满足的,而又容易发现这是整个满足的充分条件。所以就是要判断是否对所有区间 \([l,r]\) 都满足 \(\sum\limits_{i=l}^ra_i\leq (r-l+1+d)k\)。拆开来得到 \(\sum\limits_{i=l}^r(a_i-k)\leq dk\)。只需维护一个单点修改的全局最大子段和,这是个经典问题,线段树轻松解决。

2. CF338E - Optimize!

直接上 Hall 定理。对于一个集合 \(S\),出点个数显然是 \(cnt(h-\max(S))\),其中 \(cnt(x)\) 表示 \(b\)\(\geq x\) 的个数。我们考虑枚举这个 \(\max(S)\),那么对于每个 \(x\) 都要有 \(cnt(h-x)\geq cnt'(x)\),其中 \(x\)\(a\) 中当前考虑的区间中 \(\leq x\) 的个数。

转化成这样一个 ds 题就很好了。左边是个常量,就离散化一下,把 \(cnt\) 序列算出来。然后两边作差,每次看整个序列中最大值是否 \(\leq 0\)。然后就顺着考虑区间们,每次变化量是常数,就后缀增加一下,线段树 xjb 维护。

3. CF981F - Round Marriage

二分答案,然后判定完美匹配存在性。运用 Hall 定理,对每个新郎,出点都是一个区间。按照类似 P3488 的推导,只需要判定,断环成链后(这题枚举新郎区间需要断一次,对左右端点各需要往左右延申访问,所以要复制三次),是否对于每个新郎区间 \([l,r]\) 都满足 \(r-l+1\leq B(r+x)-B(l-x-1)\)\(l-1-B(l-x-1)\geq r-B(r+x)\),其中 \(B\) 表示 \(|b|=4n\)\(b\)\(\leq x\) 的数量,只需要二分就可以求出。那就扫个描线,对每个 \(r\) 的有效 \(l\) 是距离 \(n\) 以内的,单调队列求最小值即可。复杂度 2log。

4. ARC076F - Exhausted?

就是求 \(n\) 减去最大匹配。直接网络流搞不了,这就需要用到扩展 Hall 定理了。

那么答案显然是求所有 \(l,r\) 中最大的 \(cnt(l,r)-f(l,r)\),其中 \(cnt(l,r)\) 表示 \(a_i\leq l,b_i\geq r\)\(i\) 的数量,\(f(l,r)\) 表示 \(|[1,l]\cup[r,m]|\)。考虑扫描线,实时关于 \(l\) 的 rmq。对前者就装个桶到时候加就可以了,对后者分两种情况,相交的就 \(m\),不相交的就 \(m-r+1+l\),分别求个 rmq 即可,线段树随便搞。

5. P3679 - 二分毯

终于可以枚举集合做 Hall 定理了,爽(

对于只包含一部的点的点集,Hall 定理直接判了。要枚举子集的话,可以高维前缀和,但其实根本不用,因为 | 这东西可重叠。总之二年级学生都会。

对包含两部的,结论是如果两边都满足,则满足(想不到 * 1)。想要证明的话,我们先考虑 \(V_1\) 中某子集 \(S\) 有完美匹配使用 Hall 定理,除了把 \(V_1-S\) 中点删掉的另一种证法:直接反证,考虑 \(S\) 的最大匹配,然后找到一个点,类似 Hall 定理证明推出连到的点一定与异于该点的点匹配。但如果这个点在 \(S\) 外就不能使用条件了,但是可以发现,如果这个点在 \(S\) 外,完全可以删掉这个匹配,让右部点匹配选的点来增加一个匹配,与最大匹配矛盾。然后就可以像 Hall 定理一样证了。

那么考虑该题这个结论的证法(想不到 *2):反证。那么 \(S=S_1\cup S_2\) 在最大匹配中必有至少一个非匹配点,随便考虑一个,不妨设在 \(S_1\) 中。那么该点至少连向一个与其它点匹配的右部点。分成两种情况:在 \(S_2\) 中的话,必须连向 \(S_1\) 内部,不然不是最大匹配(即使变换过之后匹配数没有增加,但是匹配点数增加了 \(1\)),这样就可以无限搞然后归谬;不在 \(S_2\) 中的话,那更必须连向 \(S_1\) 了……于是就证完了。

然后就随便 two-pointers 一下,不是本题的重点。

6. CF1027F - Session in BSU

里面吧,EDR49F。

三 & 四元环计数

1. P6815 - Cakes

太水了,就把模板改一下就好了,毕竟三元环计数算法是可以枚举到每个三元环的(不像四元环)。

2. CF985G - Team Players

hot tea,题解

3. Gym102028L - Connected Subgraphs

nb tea,题解

4. loj6076 - 「2017 山东一轮集训 Day6」三元组

三个互质一口清反演掉(分数线表示整除):

\[\begin{aligned}ans&=\sum_{i=1}^a\sum_{j=1}^b\sum_{k=1}^c\sum_{o\mid i,j}\sum_{p\mid i,k}\sum_{q\mid j,k}\mu(o)\mu(p)\mu(q)\\&=\sum_o\sum_p\sum_q\mu(o)\mu(p)\mu(q)\dfrac a{\mathrm{lcm}(o,p)}\dfrac{b}{\mathrm{lcm}(o,q)}\dfrac c{\mathrm{lcm}(p,q)} \end{aligned} \]

一脸不可以用正常方法维护的样子。

直接枚举是三方的。但是注意到,考虑对 \(o,p\) 的枚举,暴力是平方的,但是有大量的 \(\mathrm{lcm}(o,p)>a\),这样贡献就一定是 \(0\) 不要算了。进一步,\(\mu(o),\mu(p)\) 也要非零。这样的 pair 很少,在 5e5 左右?然后应该就很容易想到三元环计数的模型了吧,复杂度线根(关于 5e5),而三元环计数的常数不是一般的小。而三元环计数只能解决 \(o,p,q\) 不同的情况,对于至少有一对相同的情况就 xjb 讨论就行了,太简单了。

5. hdu6123 - Destroy the cube

首先这个对称性给的目的是避免给做题者解释这六个面到底是按什么方向摆的((

那么 \((x,y,z)\) 被搞掉当且仅当 \((x,y)\)\((y,z)\)\((x,z)\) 是黑的。这种「或」就应该想到容斥,于是 so-called 容斥一波:一个的非常简单,就 \(3\sum\deg_in\),两个的就 \(3\sum\deg_i^2\),三个的考虑一波。

\(x,y,z\) 不同的直接三元环计数,一个算 \(3!\) 遍。有相同的话,若两个相同,枚举出现两遍的那个 \(x\)(前提是 \((x,x)\) 是黑色),贡献就是 \(3(\deg_x-1)\);三个相同直接枚举就好了…………………………

6. Gym100342J - Triatrip

其实不符合这个三元环计数专题(

有向图三元环计数,\(n\) 1e3 \(m\) 1e6。由于是有向图,不能用三元环计数了;就算用了也会 T。

这个 bitset 就比较容易想到了…………就枚举一条边,然后把一个点入点集合和另一个点出点集合 and 起来数一下即可。这样是 \(\mathrm O\!\left(\dfrac{nm}w\right)\),就过掉了(每个三元环算了三次)。

实际上无向图也可以这样子,但是渐进意义上始终没有三元环计数算法优,而且对 1e5 1e5 的数据会 MLE(空间 \(\mathrm O\!\left(\dfrac {n^2}w\right)\)),就尴尬了。而这题设 \(n\) 1e3,能开的下,就比较友善(废话,不然就不可做了)。

7. P4619 - 旧试题

我们之前有个 \(d(ij)\) 的展开式是吧,现在类似那个思想,一个质因子的贡献是三者的该质因子数相加再加一,不难得到

\[d(ijk)=\sum_{o\mid i}\sum_{p\mid j}\sum_{q\mid k}[o\perp p][o\perp q][p\perp q] \]

于是推柿子:

\[ans=\sum_{i=1}^A\sum_{j=1}^B\sum_{k=1}^C\sum_{o\mid i}\sum_{p\mid j}\sum_{q\mid k}[o\perp p][o\perp q][p\perp q] \]

依然是整除不方便,提到前面来搞成被整除(以下分数线全表示整除):

\[ans=\sum_o\sum_p\sum_q[o\perp p][o\perp q][p\perp q]\dfrac Ao\dfrac Bp\dfrac Cq \]

有 loj6076 内味,三个反演开来:

\[ans=\sum_r\sum_s\sum_t\mu(r)\mu(s)\mu(t)f(\mathrm{lcm}(r,s))g(\mathrm{lcm}(r,t))h(\mathrm{lcm}(s,t)) \]

其中 \(f(x)=\sum\limits_{i\in[1,A],x\mid i}\dfrac Ai\)\(g,h\) 对应 \(B,C\)。这玩意可以调和级数预处理,那就跟 loj6076 一样啦,简简单单!

然后被卡常了,卡了一个点。把 vector 改成前向星反而负优化了,为什么呢?因为这里开了 O2,开了 O2 的 vector 是奇快无比的。而去年省选是没开 O2 的………………然后稍微优化了其它地方就 A 掉了。

然后发现我 loj6076 的枚举所有边的 dfs 写法是真的 sb……\(\mu(x)\neq 0\) 满足 \(x\) 每个质因子最多一次,那么就肯定有更简单的方法了……

边双

1. P2860 - Redundant Paths

给一个无向图,计算至少加多少条边变成边双连通。

先边双缩点。然后加一条边相当于把对应路径上的点全部合并了。实质上是个树上的最小可相交路径覆盖。树形 DP 考虑起来好像不会做。数据比较小,尝试网络流?但是这无向图的路径覆盖问题咱也不会啊。

翻题解。答案是叶子节点数量除以二上取整。翻遍了全网也没有证明,即使有也是错的,真他妈诡异。

简单证一下:首先这么多是必要的是显然的吧。。。。下面证明充分性。考虑对规模归纳。\(n\leq 3\) 都是对的。对更大的规模,任选一条长度 \(\geq 3\) 的叶子连叶子的链。这时候这条链像土地一样平放着,剩下来的子树们长在土地上。然后把土地缩成一个点,相当于把地上的树并拢起来。如果没有增加叶子,只减少两个叶子,那归纳成功。

如果没有归纳成功试图补救。如果土地上有两格都种树或者某格的根部有两个儿子,那显然是成功了。否则只有一格种树。如果这棵树是一条直直的链的话,那就 \(\left\lceil\dfrac32\right\rceil=2\) 也是对的。否则地上的树肯定有两个叶子,那重新选择左端与地上任意一个叶子相连作为新的土地,那显然满足有两格种树(画个图就知道了),得证。

2. Contest Hunter #24C - 逃不掉的路

无向图必经边问题。

边双缩点之后,必经边显然是两点之间的割边们(真的很显然!)。LCA 一下就好。

3. CF652E - Pursuit For Artifacts

走过的边不能再走了。考虑要到一条指定的 1 边的话,就是 \(a\to e\to b\) 并且不能经过重复的边。走两条路,边不能重复这个限制就很自然地想到边双。

缩点之后显然只能走直接路径(不然经过往其他方向的割边就完蛋了)。我们感觉从一个 DCC 里进去,到一条指定边,然后是一定可以出来的。考虑证明之:设起点终点分别钦定为 \(a,b\),要经过 \(e\)。那么 \(e\to b\) 至少有两条没有交边的路径,那如果能找到一条 \(a\to e\) 的和这两条路径之中最多一个有交边的路径就好了。这很容易:如果能找到一条点都没交的,那显然边也一定没交;否则在刚交到某一条路径的点时候,既然已经交上了,就直接按它走回 \(e\),它与另一条没有交边,得证。

那就如果一个 DCC(即缩点之后树上的点)内部有 1 边就标记成 1 点。最终就是看 \(a\to b\) 路径上有没有 1 边或 1 点。(其实可以加强到多组询问的啊)

code
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
#define ppb pop_back
const int N=300010,LOG_N=20;
int n,m,qu;
vector<int> nei[N],rv[N];
vector<bool> brg[N],good[N];
int dfn[N],low[N],nowdfn;
void tar(int x=1,int fa=0){//to modify
	dfn[x]=low[x]=++nowdfn;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa){fa=-1;continue;}
		if(!dfn[y]){
			tar(y,x),low[x]=min(low[x],low[y]);
			if(dfn[x]<low[y])brg[x][i]=brg[y][rv[x][i]]=true;
		}
		else low[x]=min(low[x],dfn[y]);
	}
}
int cnt;
int cid[N];
void dfs(int x=1){
	cid[x]=cnt;
	for(int i=0;i<nei[x].size();i++)if(!brg[x][i]){
		int y=nei[x][i];
		if(!cid[y])dfs(y);
	}
}
vector<pair<int,int> > cnei[N];
bool vis[N];
bool gv[N];
int st,ed;
vector<bool> v;
void dfs0(int x=st){
	vis[x]=true;
	v.pb(gv[x]);
	if(x==ed){
		for(int i=0;i<v.size();i++)if(v[i])puts("YES"),exit(0);
		puts("NO"),exit(0);
	}
	for(int i=0;i<cnei[x].size();i++){
		int y=cnei[x][i].X,g=cnei[x][i].Y;
		if(vis[y])continue;
		v.pb(g);
		dfs0(y);
		v.ppb();
	}
	v.ppb();
}
int main(){
	cin>>n>>m;
	while(m--){
		int x,y,v;
		scanf("%d%d%d",&x,&y,&v);
		nei[x].pb(y),nei[y].pb(x);
		rv[x].pb(nei[y].size()-1),rv[y].pb(nei[x].size()-1);
		brg[x].pb(false),brg[y].pb(false);
		good[x].pb(v),good[y].pb(v);
	}
	tar();
	for(int i=1;i<=n;i++)if(!cid[i])cnt++,dfs(i);
	for(int i=1;i<=n;i++)for(int j=0;j<nei[i].size();j++){
		int x=nei[i][j];
		if(cid[i]!=cid[x])cnei[cid[i]].pb(mp(cid[x],good[i][j]));
		else if(good[i][j])gv[cid[i]]=true;
	}
	cin>>st>>ed;
	st=cid[st],ed=cid[ed];
	dfs0();
	return 0;
}

4. CF51F - Caterpillar

洛谷博客

5. CF555E - Case of Computer Network

可能是个 jxd 作业(判断依据:大概是 CF 比赛号 500~600 的一个 E 题、难度 *2800、有 xht 的题解)。然而感觉并不是很难的亚子(然而现在题解区已经封闭了(

首先发现如果只有两个要求,分别是 \(s\to t\)\(t\to s\),那就是看 \(s,t\) 是否边双连通。所以这个题是不弱于边双的。

那就 eDCC 缩点观察一下。那么对要求 \(s\to t\),如果 \(s,t\) 不在同一个连通分量那显然炸了;如果不在同一个边双,那唯一的要求就是 \(s\to t\) 的路径上的边都按照该方向定向。如果在同一个边双里呢?我们甚至能把边双定向变成 scc(无向图中的环(eDCC)对应有向图中的环(SCC))。证明的话,考虑 dfs 树,每条返祖边都把一段直链上的点给 merge 了。那只需要把树边设成往下,返祖边设成往上,依然每条返祖边都把原来 merge 的点集 merge 了,所以是 scc。

最后就是树上差分赋方向,然后看是否有边被要求同时两个方向。

6. P6914 - Tours

nb 题(关于切边等价),题解

点双 / 圆方树

1. P3469 - BLO-Blockade

建出圆方树出来,不难发现,把某个点割掉之后分裂成的若干个连通块,跟圆方树上割裂之后分裂成的若干个连通块是一致的。那就记录一下子树的 size 们(注意只有圆点才作数),然后对每个点枚举邻居随便乘乘就行了,最后还要加上关于它自身的 \(2(n-1)\)

贴个圆方树模板(真的很好写!):
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=200010;
int n,m;
vector<int> nei[N];
int dfn[N],low[N],nowdfn;
int stk[N],top;
int cnt;
vector<int> cnei[N];
void tar(int x=1,int fa=0){
	stk[top++]=x;
	dfn[x]=low[x]=++nowdfn;
	if(!fa&&nei[x].size()==0)return cnt++,cnei[n+cnt].pb(x),cnei[x].pb(n+cnt),void();
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa){fa=-1;continue;}
		if(!dfn[y]){
			tar(y,x),low[x]=min(low[x],low[y]);
			if(dfn[x]<=low[y]){
				cnt++;
				while(true){
					int z=stk[--top];
					cnei[n+cnt].pb(z),cnei[z].pb(n+cnt);
//					cout<<z<<" ";
					if(z==y)break;
				}
				cnei[n+cnt].pb(x),cnei[x].pb(n+cnt);
//				cout<<x<<"!\n";
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
}
int sz[N];
void dfs0(int x=1,int fa=0){
	sz[x]+=x<=n;
	for(int i=0;i<cnei[x].size();i++){
		int y=cnei[x][i];
		if(y==fa)continue;
		dfs0(y,x);
		sz[x]+=sz[y];
	}
}
int main(){
	cin>>n>>m;
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		nei[x].pb(y),nei[y].pb(x);
	}
	tar();
	dfs0();
	for(int i=1;i<=n;i++){
		long long ans=2*(n-1),now=0;
		for(int j=0;j<cnei[i].size();j++){
			int x=cnei[i][j];
			if(sz[x]<sz[i])ans+=2ll*now*sz[x],now+=sz[x];
			else ans+=2ll*now*(n-sz[i]),now+=n-sz[i];
		}
		printf("%lld\n",ans);
	}
	return 0;
}

2. hdu3686 - Traffic Real Time Query System

无向图必经点问题。

每条边恰好属于一个 vDCC。考虑求两个询问边属于哪个 vDCC。那显然圆方树中有边的(也就是在同一个 vDCC 中)两个点距离一定为 2,那分几类讨论出来它们之间的那个方点。

那么从一条询问边走到另一条经过的路径必定是圆方树上两条边所在方点之间的唯一简单路径,数一下里面的圆点(就是割点,肯定要经过)就行了。倍增 LCA,长度除以二。

3. P3225 - 矿场搭建

首先吐槽一下这个输入为什么这么奇怪,孤立点还不算是吧(严格按照题意应该是要选的啊)。那就离散化,然后 \(n\leq 2m\),不存在孤立点。

显然每个连通分量是独立的,第一问相加第二问相乘。对连通块考虑枚举删除的点看是什么情况。那就是对每个点,假装删除它,分裂开来的每个连通块里都要有至少一个选的。那删除每个点的情况的这些限制是可以混在一起的,最终就是个最小集合覆盖问题,NPC 很抱歉。

分裂开的连通块这个东西很有趣,注意到删除一个点之后圆方树和原图的分裂情况一致,于是可以建出来圆方树放上面考虑。然后发现了不得了的事情:集合覆盖问题中如果有包含关系,那大的那个是废的;而圆方树中的子树显然数最下面的作数。以任意一个圆点为根,根据 vDCC 大小至少为 2 的限制,不会有方叶子,所以叶子都是圆点。那就是说,只有儿子全是叶子的方点才作数,对每个这样的方点,它的儿子中必须恰好选一个;而且这些限制没有交,这就傻子都会算了吧。

但是删除一个点不光儿子树们要考虑,还要考虑它这棵子树的补集。当根不是叶子的时候,这玩意依然被至少一个上述限制包含于;如果不是叶子就说不定了。基于根必须选圆点的强迫症,我们希望找到一个非叶子圆点。找到好说;找不到的话当且仅当整个图双连通(否则一定有割点,在圆方树上度至少为 2),这种情况易得答案为 \(2,\dbinom{n}{2}\)

复杂度线性,为啥数据范围给 500?就很迷惑。

4. P4630 - 铁人两项

考虑对 \(s,f\) 什么样的 \(c\) 满足条件。根 cf652E 那题类似,那题是不能经过一条边两遍,想到 eDCC;这题不能经过同一个点两遍,想到 vDCC。如果 \(s,f\) 在同一个点双里,那么这个点双内异于 \(s,f\) 的点都可以当 \(c\),证明跟 652E 基本一样;而外面的点必定不能当 \(c\),因为要经过割点,出去就回不来了。

考虑 \(s,f\) 不在同一个点双的情况。建出圆方树,走点双的路径就是圆方树上唯一的简单路径。中途每个点双,都可以在里面随便选 \(c\);其它点双都不行,走了就回不来了。但是这时候 \(c\) 的数量并不是路径上方点的大小总和,因为一要去掉相邻两方点之间共享的割点,二要根据互异于 \(s,f\) 的限制再去掉 \(2\)。不难发现,把圆点的权值设为 \(-1\),方点设为对应点双大小,\(c\) 的数量恰好就是路径和!

然后考虑枚举 \(s,f\) 统计。统计路径,可以想到枚举 lca(我还不会淀粉质),然后把儿子树扫一遍,dfs 的时候记录一点东西不说了这很 trivial。

5. P4320 - 道路相遇

一开始把题目看错了,以为是每种可能都必经的点数。那就是每种是一条路径,差分一下,最后数必经次数为 \(qu\) 的。

正好样例是一次询问。交上去全 WA 才发现每次都是独立询问。。。。。那不就是裸的必经点了吗,圆方树上路径上的圆点个数,基于圆方树路径的圆方交错性,圆点个数就是链的长度除以 2 上取整。

6. P4606 - 战略游戏

TL 10s 疯掉了啦!

建出圆方树,那么一个点符合条件当且仅当它在 \(S\) 中任意两个点的路径上。也就是符合条件的点的集合是 \(S\) 中任意两点的路径的并。这其实就是 \(S\) 的虚树。

什么?要建虚树?好怕怕哦。其实要求虚树中边权和,只需要用到 P3320 寻宝游戏那题的结论:按照 dfn 排序,然后相邻(环形)对的距离和就是答案的两倍。那就直接做就可以了,要写个 lca。

7. CF487E - Tourists

跟铁人两项的结论一样,要考虑的集合就是圆方树上路径上的方点代表的点双的节点集合。而且这题是求最小值,不需要考虑重复。

那修改操作是修改圆点的所有方点邻居;询问就树剖查询链咯。但是修改操作复杂度是假的,度数稍微大一点的圆点重复几次就炸了(其实好像可以根号分治的说,但常数太大了(小雾))。

这时候发现链上的圆点查询的时候就假装是空的,并没有把它真正利用起来。如果也算它的权值的话,就发现链上除了 lca 以外的其它方点的父亲都在链上。这下好了,也就是说方点只需要记录儿子的最小值,因为爸爸会在查询的时候自动被查询到。也就是说修改圆点的时候只需要修改爸爸一个方点。那复杂度就又保证了。这个方点的儿子存储只需要用 multiset。

8. spoj KNIGHTS - Knights of the Round Table

SWT、结论题一道。lg 博客题解

9. P4082 - Push a Box

较为毒瘤的题。

DS 方向

线段树合并与分裂

nmd 一堆已经用 dsu on tree 写过的题。

1. P4556 - 雨天的尾巴

虽然标着线段树合并模板,但是我不尊它为模板嘿嘿。

这题可调死爷辣……两个错误,一个是树上差分的时候,应该在 LCA 和 LCA 的爸爸处各减一次,而我在 LCA 爸爸处减两次;一个是数组开小。A 掉的时候,热泪盈眶的我说:社义好!


下面讲这题怎么做。

先树上差分,然后就变成了求每个子树内权值加起来最大的一个类型是啥。首先有个显然的 dsu on tree,毕竟这玩意好理解。但是你可能要问了,这题子树信息量不等于子树大小啊,那复杂度分析还有效吗?注意到启发式合并的本质是使得每个点被贡献不超过 log 次,那么如果保证了所有点的信息量之和,那么复杂度分析依然是有效的。也就是说每个元素在复杂度贡献值带权的启发式合并也是有效的。(搁这线段树合并的地方你给我扯 dsu on tree 是吧)

我们考虑怎么用线段树合并来做。那就在每个点的子树上维护一个值域线段树,每个节点维护当前区间权值最大的类型。那么只需要线段树合并就可以了。这玩意是不可以区间跟区间合并的,但幸运的是,线段树合并只会到最底层合并,其他时候都是上传。那么就可以做了。

zszz 学一个算法练题的时候要没事找事变着花样写。本来是可以一遍 dfs 下来,先把子树全处理了并且记录答案,然后采用不保留原树的方式线段树合并,这样省空间。但我写的是保留原树的线段树合并,最终可以 \(1\sim n\) 扫一遍,非常快乐。我们来算一下这样的空间,差分是搞了 \(4m\) 个修改,每个会开 \(\log n\) 的空间,线段树合并会额外开这么多,是 \(8m\log n\) 的,是在 \(500\) 兆以内的。

我们发现线段树合并比 dsu on tree 要灵活,dsu on tree 必须按照特定顺序一口清处理掉,而线段树合并(尤其是保留原树版本)是可以随便搞的。

2. P3224 - 永无乡

u1s1 现在感觉这种封装起来的 DS 是真心好写……这题 1A 了

首先显然可以平衡树启发式合并,这样子是 2log 的。可以用线段树合并做到 1log。就一开始建出 \(n\) 棵树来,然后合并合并,只有合并操作,不会新增节点,所以垃圾回收是没有用的。然后每个点的根号如何维护呢,只需要用并查集维护集合代表元即可。

空间就是一倍的 \(n\log n\),没有任何常数。

3. P3201 - 梦幻布丁

容易发现只需要计算同色相邻对数。那么不难想到对每种颜色统计,就把 pos 序列搞出来排序扫一遍数只相差一的相邻对即可。

那现在要合并两个颜色。考虑启发式合并的话,还要实时维护 pos 序列的有序性,可能是需要 2log 了。这时候线段树合并能有效解决这个问题。考虑每个颜色搞个动态开点线段树,在那些有值的位置打上 1,那么每个节点记录当前区间最左边是否有值、最右边是否有值、内部相邻对数,容易上传。

然后就容易合并了。但是这并不是传统的「合并」,它是把两个集合的结果合并起来赋给其中一个,另一个清空,其中另一个日后有可能再用。于是我们需要额外模拟这个过程:清空的话直接新建空节点即可,并且不需要用新建节点的方式写线段树合并,直接把要赋给的那一个保留下来即可。看起来值域是 \(v\),但由于动态开点,所以空间依然是 \(\mathrm O(n\log v)\)

4. P3521 - ROT-Tree Rotations

对于任意非叶子节点处,它两个子树内部怎么样不是本次交换所能决定的,只能看交换前和交换后的逆序对数哪个少。于是不难想到从底往下合并,每次不仅要合并出该节点处的权值集合,还要在合并过程中顺便统计出若不交换的左右子树逆序对数。

这个是启发式合并难以做到的。我们考虑线段树合并。合并权值集合就有点简单了,统计答案呢,这是个点对问题,考虑对值域 cdq。而 cdq 的每一步的 mid 恰好在线段树结构上能体现,并且只需要调用左儿子和右儿子的信息即可算出。而且在线段树合并该停下的时候(一个节点为空),此时对答案贡献显然一定是 \(0\),也可以相应停下。于是就可以在线段树合并过程中统计出来啦!

5. P3605 - Promotion Counting

就这?还 Pt 组?

就线段树合并 instead of 启发式合并一波,然后在每个点处后缀查询。这是个经经典典的动态开点线段树的合并。

6. CF490F - Treeland Tour

考虑这题的 \(n\leq 10^5\) 加强版。

考虑枚举以每个点 \(x\) 为 lca 的 LIS。那么就设 \(dp1_x\) 表示从 \(x\) 往下的 LDS,\(dp2_x\) 是 LIS,那么贡献有三种:

  1. \(\max(dp1_x,dp2_x)\)
  2. 对两个不同的儿子子树里的节点 \(y,z\),满足 \(a_y<a_x<a_z\)\(dp1_y+1+dp2_z\)
  3. 对两个不同的儿子子树里的节点 \(y,z\),满足 \(a_y<a_z\)\(dp1_y+dp2_z\)

第一种傻逼。第二种我们要搞一个子树内值域在某一范围内的 \(\max\)(相当于是个不可差分的二维数点),DP 的转移也要,第三种则更难。

先考虑这个类二维数点。它是在线、动态的,必须用树套树,由于不可差分还必须要用线段树套线段树。我曾尝试用后序遍历编号,这样可以假装是静态的,可以用主席树处理……吗?它不可差分…………所以树套树是大常数 2log 的,而且还不想写。

对于子树数颜色之类不可数据结构维护的东西,我们很容易想到 dsu on tree。但像这种暧昧的,可以区间 ds 维护但又比较劣的就想不到 dsu on tree。事实上是可以 dsu on tree 的,我们考虑之。就实时维护该子树内,关于值域的一个线段树。那么类二维数点这玩意就搞完了,第三种怎么处理呢?考虑在 dsu on tree 的轻子树合并进来的时候,与实时维护的到目前去掉当前子树的所有子树的 \(\min\max\) 发生关系即可。这样子是小常数 2log,是个经典的 dsu on tree 优化 DP。

那么这个 dsu on tree 还可以改成线段树合并。对于第三种怎么处理呢?合并两个线段树的时候,类似 cdq 分治地在以每个节点为分界的地方更新答案即可。

7. P4577 - 领导集团问题

考虑一个显然的 DP:\(dp_{i,j}\) 表示子树 \(i\) 中,选的集合的最小等级为 \(j\) 时的最大的集合大小。转移的话分两种:选 \(i\) 和不选。不选的话就把各部分给加起来……等等,是这样吗?「恰好等于 \(j\)」容易出错,不妨改设为 \(\geq j\)。那么不选的话就是把每个 \(j\) 在所有儿子处的值加起来;选的话,就用当前 \(a_i\) 处的值 \(+1\) 去 chkmx \(j=1\sim a_x\) 处的值。每个 DP 数组的有效值可以看作 \(\mathrm O(sz)\),所以可以 dsu on tree,那么自然也能线段树合并(而且还不需要考虑有效值量)。

那就不选的操作是个简单的操作为相加的线段树合并,然后取值然后区间 chkmx。懒标记搞不了,就需要用标记永久化。开始写的时候发现不可实现,不可能在操作为相加的线段树合并的时候合并 max 的标记。那怎么办呢?注意到新值是 +1,而这棵线段树一定是单调的显然吧,那么影响到的就是值等于该处的一段区间。那么线段树二分出左端点就可以变 chkmx 为加法了。而要支持二分还要记录区间 min,容易维护。

8. P5298 - Minimax

yet another 1A

这是个挺不常规的线段树合并。

显然不需要维护这个 \(\sum\),只需要维护最终每个值被取到的概率。考虑从底往上合并。这里不考虑 dsu on tree(不过要是考场上我可能会考虑 dsu on tree),而考虑线段树合并。

没儿子的话,直接把值在线段树上设 \(1\),其他都 \(0\)。一个儿子的话直接继承。两个儿子需要考虑考虑。

一个值显然尽可能出现在一个儿子的列表里。那么它的新概率,是多少呢?各选一个的话,前提条件是它自己要被选到,然后要求取最大并且它是最大的或取最小并且它是最小的,就 \(d\gets d(p_xd_l+(1-p_x)d_r)\),其中 \(d_l\) 为另一个儿子中比自己小的数出现的概率之和,\(d_r\) 是比自己大,对应到值域线段树上就是前后缀和。那么这次合并不是点对点的了,而是点对前后缀,就比较棘手。

需要考虑的是,线段树合并时遇到一满一空是否能快速继承(点对点显然是可以的,直接复制即可)。那么考虑修改那个满的,由于另一个是空的,所以这个满的里面所有对应的 \(d_l,d_r\) 都是相等的,也就是它们要乘以相同的东西,那么打乘法标记即可(\(d_l,d_r\) 可以从上往下走的时候实时记录)。而这里有个比较巧妙的地方,普通的线段树合并是不能懒标记的,而这里懒标记下传的时候遇到空节点不需要新建(因为对 \(0\) 作用任何乘法无效),所以可以大力懒标记。

其他地方的合并就很平凡了。最后遍历一遍线段树即可。

9. P2824 - 排序

这题最后只有一个询问,可以对其二分然后转 01 离线 2log 处理。我们这里使用在线 1log 的线段树分裂。

那这题就是个区间排序的板子了。直接做就行了。

放个 code,写了 4.2k 累死了。

10. CF558E - A Simple Task

这题有另一个特点:值域特别小,所以我们可以直接维护区间各字符出现次数,所以难度只有 2300。不过我们还是用线段树分裂来写(

就把上一题代码稍微改一下就可以了((

主席树

1. P1383 - 高级打字机

直接可撤销是做不到 200% 的。不难发现可持久化数组可以轻松解决这个题……对 undo 就回到前 \(x\) 的历史版本即可。

2. P2468 - 粟粟的书架

这二合一合的有点……

显然想到二分。判定就是要求,特定子矩形内后 \(x\) 个大的数的和。

考虑前一部分。二维的没法主席树,只能预处理二维前缀和。但是是四方的,存不下。注意到 \(p_{i,j}\leq 1000\) 的特殊条件,考虑对值二分,对值预处理(前缀和和前缀计数),然后在边界处取个 min 啥的。

第二部分是一维的。考虑主席树维护东西,这样就可以等效于得到 \(l\sim r\) 内所有值搞成的线段树(主席树这样的前缀和思想是最常用的)。那么后 \(x\) 个大的和就直接在里面二分了(u1s1 我获得了线段树前缀查询的循环写法?区间就前缀和减一下?修改类似?)。

3. spoj TTM - To the moon

这题调死爷辣!

是个主席树区间修改模板题。需要用标记永久化。记好了,标记永久化的信息维护的是后代对该子树的贡献和,标记维护的是该节点的贡献和,查询的时候的总贡献等于后代 + 祖先。

然后修改的标记啥的怎么搞,只需要根据主席树(函数式线段树)的本质,线段树怎么写,主席树就相当于把要修改的节点复制一份然后套。(信息可以上传,等于左右儿子的信息加上该点的标记(根据定义显然))

代码贴一份吧

4. P3567 - KUR-Couriers

考虑按照剧本,预处理出 \(1\sim n\) 历史版本的值域主席树。然后考虑在前缀和相减所得线段树里面查询。

直接的想法是找众数,但是 \(\max\)\(\min\) 这玩意没有可减性,做不起来。这题条件特殊,满足条件的显然是最多有一个数。于是在树上二分的时候,两个儿子中最多有一个代表的区间是包含答案的(也可以直接用个数是否严格过半判断出来),于是就在树上往下走一遍,复杂度是对数。

5. CF840D - Destiny

上一题的 \(k\leq 5\) 加强版。这次不能保证每次都只到一个儿子里递归了,但是直觉上感觉复杂度依然是对的。

事实上很好证,就如果往两个儿子里递归的话,那么当前区间个数必定分裂成 \(\dfrac{k-1}k,\dfrac 1k\) 或者更靠拢。显然只能分裂 \(\mathrm O(k)\) 次,以后就不分裂了,每条路是对数的,所以一次复杂度是 \(\mathrm O(k\log n)\)

6. P2633 - Count on a tree

模板题转到了树上。

你当然可以树剖,这样二分就不能搞到树上,就是 3log。

考虑树上前缀和这玩意,这玩意依然是可以轻松的主席树预处理的,一个节点的状态基于它爸爸的状态。然后就随便做了。

7. spoj COT - Count on a tree

双经

8. P6071 - 『MdOI R1』Treequery

从这题学到许多,故作以记之。

9. P3963 - 奖学金

事水题。先考虑二分答案,chk 就需要求某处之前的 \(\dfrac{n-1}2\) 个最小的和,和该处之后的 \(\dfrac{n-1}2\) 个最小的和。进而发现不用二分,直接枚举一遍,递推的时候维护这玩意,有很简单的方法。不过我们考虑用可持久化先预处理(真就没事找事?)。

查询这玩意是平衡树干的事,可以用值域线段树代替,记录区间计数和区间和,树上二分。那就可以前后缀两遍主席树,然后这玩意有可减性,所以可以前缀和差分只预处理一次。一开始看错数据范围了,以为值域是 1e9,事实上如果这样那么不需要离散化,主席树本身就是动态开点,直接写就可以了。

10. P3939 - 数颜色

对询问前个缀和,那么就是维护每个颜色在每个位置的前缀和,可以把每个位置处的前缀和数组用可持久化数组维护。

对修改呢?如果是单纯的修改的话,那么要修改的版本们是一个后缀,就废了。但这个相邻交换恰好是可以常数修改的,就把对应历史版本改一下即可。

需要注意的是,主席树想要修改任意一个历史版本(即使没有被任何其它历史版本所基于),那么不能直接改,而要新建版本。换句话说,函数式数据结构你要想函数式就要函数式到底,不能一会儿函数式一会不,就乱套了。

11. P3755 - 老C的任务

真就二维数点板题呗。好家伙,我直接好家伙!

就离散化一波(其实第二维不用,直接动态开点即可),对第一维主席树前缀一波。

12. P4559 - 列队

按照贪心,如果将 \([l,r]\) 中元素排序得到 \(a_{1\sim r-l+1}\),那么答案就是 \(\sum\limits_{i=1}^{r-l+1}|a_i-(x+i)|\)。考虑去绝对值。

由于保证 \(a_i\) 严格大于 \(a_{i-1}\),所以这个绝对值是正是反是单调的。我们考虑二分出那个 bound,直接二分的话,每次需要主席树找区间第 \(k\) 大,是 2log 的。但是不难发现这个二分可以直接搞到主席树上,是 1log。然后就是个二维数点了,可以用主席树解决。

13. P6655 - 制高

拆贡献,拆到点上。有 \(r_i<i\) 的保证,于是顺着考虑是无后效性的。考虑 \(dp_i\) 表示考虑到 \(i\)\(i\) 是制高点的情况数。转移 \(dp_i=\sum\limits_{j=l_i}^{r_i}[h_j\leq h_i]\dfrac{p_{i-1}}{p_j}dp_j\)。记录 \(\dfrac{dp_j}{p_j}\) 然后就是个二维数点。看上去是动态的,但是可以假装是静态的,因为一个点被加入前永远不会被数到。主席树顺着 add 即可。

14. P6638 - 「JYLOI Round 1」常规

考虑前缀和,算 \(b\) 以内秒的贡献和。那就是 \(\sum[a_i\leq b]\left\lfloor\dfrac{b-a_i}k\right\rfloor\)。把 \(a\) 排个序就是前缀,二分可找。整除拆开:\(\dfrac{b-a_i}k-\dfrac{(b-a_i)\bmod k}k\)。前者随便做,后者 mol 拆开:\(b\bmod k-a_i\bmod k+[a_i\bmod k>b\bmod k]k\)。前两项随便做,后面一个东西就是个二维数点,主席树搞一下就出来了(值域很大,但是可以直接沿用主席树的动态开点)。感觉并不难,不知道为啥 A 的人如此之少?

15. P6592 - 幼儿园

这题感觉挺难的,不知道为啥只是个蓝题?

建反图,\(1\to x\) 单调递增。考虑对一个特定的 \(l\),求出对每个点,合法路径中最小的最后一步。那么回答询问就看这个值是否小于等于 \(r\)

容易想到这样一个求法:\(1\sim m\) 依次加入每条边,更新答案。那么我们考虑 \(l=1\) 开始,然后上升,删边,会导致连锁反应,但是每条边只会被删一次,所以爆搜就可以了。想要获得之后的答案,需要对每个点维护一个候选 set

这样子离线的版本就可以实现了。在线的话需要可持久化维护历史版本一下。但并不是什么都要可持久化的,比如辅助计算的 set。只需要可持久化答案数组,于是写个可持久化数组(主席树)即可。

16. P4094 - 字符串

并不难的一题,只是码农。

显然我们只需要考虑 \(s_{a\sim b}\) 的后缀们。后缀 \(i\) 的答案是 \(\min(b-a+1,d-c+1,lcp(s_{i\sim n},s_{c,n}))\)。对 \(i\) 讨论的时候,可以把 \(d-c+1\) 提出来。但是还是有个 min,很烦。考虑二分答案,那么对 \(b-a+1\) 这项得到一个关于 \(i\) 的范围,是个区间。那么只需要考虑这个区间内的最大 lcp 即可。那就没有什么顾虑了,就看 \(c\) 在 SA 中在该区间内相邻的两项。也就是区间前驱后继。那就用 Treequery 那题的做法做掉就可以了。

SA + 主席树 + ST 表模板三合一,你值得拥有!虽然我是贴板子的

17. P2839 - middle

几天前还觉得很难,现在看起来也不算什么……

考虑二分,chk 就是存在 \(l,r\) 满足 \(cnt(l,r,x-1)\leq\dfrac{r-l+1}2\),其中 \(cnt(l,r,x)\) 表示 \([l,r]\) 中小于等于 \(x\) 的数的个数。前缀和拆开:\(2cnt(1,r,x)-r\leq 2cnt(1,l-1,x)-(l-1)\)。由于 \(b<c\),所以只要求对应区间的 min / max。我们考虑把下标这一维主席树前缀和预处理掉,但这样就意味着要在历史版本序列中区间查询,这是噩梦。但非常容易想到转个置,对值搞主席树,这样就是在单个历史版本里区间查询了。预处理需要区间修改,要标记永久化。

需要注意的是,下标由于 \(l-1\) 可能到 \(0\)

值得一题的是:维护标记永久化的时候,这个 \(mn/mx\) 一路下来无法直接改,只能上传。

18. P2048 - 超级钢琴

首先有个很容易想到的套路方案:显然是要找出前 \(k\) 大的超级和弦,如果把所有超级和弦分成若干类的话,就用个优先队列实时维护每类的最大值,然后取了再更成次大值。考虑按照长度分类,但是 \(f(x,x+c)\) 这种玩意一般很不好维护。考虑按照左端点分类,那么就是选择右端点处前缀和最大的,这个好做。就每次区间 xth,主席树轻松解决。

19. CF226E - Noble Knight's Path

并不是很难的一题,考场上遇到这种题就幸甚至哉了。但是有点难调,调死爷辣!

就把重链一个个搞下来,一个个消,直到确定在哪个重链里。这个可以主席树维护历史版本间区间个数 2log 完成。

找到是哪条重链之后,可以写个二分,也可以树上二分做到 1log(就是先找到是哪个节点,然后在里面二分)。

人老了,什么都能写错。

20. P3168 - 任务查询系统

不带强制在线的话是个挺简单的数据结构题。就差分处理掉,一路扫,维护一个关于优先级的线段树。查询就直接线段树二分。

强制在线的话就套个主席树即可。

21. CF916D - Jamie and To-do List

这是个 2200 的 D2D……我跟不上时代了………………

不 undo undo 操作的话直接可撤销即可。要 undo undo 的话就是个典型的可持久化。

分析一下发现要维护的东西可以拆成两部分:维护字符串对应优先级;维护优先级集合以及查询前缀计数。后者只需要写个主席树即可。前者呢?如果不可持久化,那肯定 map,但是 map 是平衡树不好可持久化。这里有两个方案,一个是维护 trie,但是字符串 trie 每个节点有 26 个儿子,空间可能开不下(u1s1 感觉字符串 trie 不太好用)?第二个就是把字符串映射到整数然后可持久化数组(或者说维护整数的平衡树可以用动态开点线段树代替),有两种方法,一是哈希,而是维护一个 map 每发现一个新串就赋一个新 id。

22. P3248 - 树

不难想到这样一个思路:将新加入的子树们压缩,让根作为它的代表元。那么大树中一个节点可以表示为,压缩后的哪个节点下编号第多少的点。这两种表示方法的互相映射,需要求子树 xth 和子树 rk,而子树就是区间,所以可以主席树搞。

那么答案就是 \(dis(x)+dis(y)-dis(\mathrm{LCA}(x,y))\),其中 \(dis\) 是到根的距离。于是我们只要求大树中的 LCA 和前缀和问题。

对前缀和问题:容易想到拆成它到它虚拟根的距离和虚拟根到根的距离之和。虚拟根的数量能接受,于是直接预处理即可(两虚拟根之间的边权为它所接的点到父亲虚拟根的距离)。前者就映射回模板树求一下。

LCA 问题比较难。先求出两者的虚拟根,然后求虚拟根的 LCA。分成三种情况:同一个虚拟根,那么就映射回模板树求 LCA 再映射回来;一个虚拟根是另一个的祖先,那么就先让下面的那个往上跑跑到祖先虚拟根管理的子树下(分成两步,一步是求一条链的第二浅节点,这个无法 \(\mathrm O(1)\) 求,因为儿子不唯一,但是可以轻松倍增;然后就是再往上一格,这个只需要在构造的时候记录下来即可),然后映回去求完映回来;否则就两个都往上跑,然后映回去映回来。

说的云淡风轻,实际上太难写了。大概是平生第一次写 180+ 行的程序吧。

23. P4216 - 情报传递

一个点被加入的时候,设当时的时间是 \(x\),那么对第 \(i\) 个询问它被算当且仅当 \(i-x>C\)\(x<i-C\)。如果树剖的话,这看起来像个动态加点二维数点。但是有特殊性,它加的点随时间递增。那么就容易许多了,那么只需要访问在时间 \(i-C-1\) 或以前加进去的。这就维护一个主席树,可以树剖做到 2log。但是这个不是链改链查,是单点改,所以可以退化成树上前缀和,就维护每个点的树上前缀和,修改的时候修改子树,查询单点。然而这是个主席树,写区修可能会有点麻烦,所以可以用差分将查询和修改的范围反一下。

可持久化 Trie

1. P5283 - 异或粽子

1A,就他妈爽!

先前缀异或和,那么问题就变成了求异或和前 \(k\) 大的点对。

按照超级钢琴那题的套路,考虑对每个位置求最大,然后用堆维护,实时查询次大、次次大。众所周知,我们可以用一个 01trie 来解决任意一个值与其中的所有数的最大异或和。那么考虑对每个数建个 trie,范围是 \(0\sim i-1\)。这个可以用可持久化 trie 从前往后推一遍预处理出。

对查询次大等等,可以把当前最大从 trie 里删掉,为了不影响其它历史版本,也需要新建历史版本。

2. P4735 - 最大异或和

前缀异或和掉,询问相当于找 \(l-1\sim r-1\) 内与 \(x\oplus a_n\) 异或最大的。自然想到 01trie。

但是要求一个区间内的集合构成的 01trie,怎么办呢?要是我们能快速得到或等效于得到(能快速在上面访问)这个 01trie 就好了。这就又要用到可持久化数据结构常用的前缀和思想:对每处搞个表示前缀和的 trie,区间的话就相减即可。只需要对每个节点记录被数经过的次数,是可减的。

末尾添加就直接搞啦。。。

3. P4098 - ALO

考虑用次大值来贡献。对每个值,考虑哪些区间使得它是次大值。

首先考虑极大的使得它是最大值的区间。然后往两端任意一端扩展。于是我们只要求对每个位置,往左 / 右第一 / 二个大于它的位置。这个随便用什么维护就能求了。

于是现在问题就是求区间与特定值最大异或和。这个跟上一题一样,大概是可持久化 Trie 的经典应用吧。

4. P4592 - 异或

对子树询问,就用 dfn 拍成序列然后套上上题的那个经典应用。

树链查询呢?可以强行树剖 2log,但这题没有修改有点浪费。考虑模仿 COT 那样树上前缀和,可持久化 trie 依然是能做的。

5. P3293 - 美味

如果没有 \(x_i\) 的存在就是可持久化 trie 的板子了。有了它就不能 trie 了。

我们在这儿考虑一下 01trie 的本质。

xht 曾说过:异或的题不是拆位,就不是 01trie,就是线性基。那么你可能会问,按位贪心去哪儿了?事实上 01trie 是使用了按位贪心的思想的:就从高往低依次确定,每次 chk 的时候相当于看以当前前缀为前缀的数存不存在。而这是一个区间,也就是一个数点,而幸运的是,由于是二进制下的这种问题,配上 01trie(本质是线段树)的结构,这样的区间恰好是节点,可以 \(\mathrm O(1)\) 查询。

而每个节点记录的恰是当成线段树的时候,该区间内的数的个数。那么插入一个数其实就是对应线段树的单点加。

那么本题也依旧使用按位贪心:每次考虑特定二进制区间 \([l,r]\) 内存不存在 \(a_j+x_i\)。也就是 \([l-x_i,r-x_i]\) 内存不存在 \(a_j\)。这不是个二进制区间了,不能直接通过某个节点 \(\mathrm O(1)\) 获得了,只能强行到树上区间查询了。于是我们被迫把经过简化的 trie 给还原成线段树,写个主席树复杂度 2log。

6. P5795 - 异或运算

一开始看到矩形求异或吓死了。然后发现 \(nq\) 是 5e5 级别的,那肯定就是枚举行搞。

如果要求的是最大的话,那就对 \(m\) 这一维建可持久化 trie 然后每个查最大取 max。但这里求第 \(k\) 大,就不能转化成每一行独立的问题。

我们考虑按位贪心(有点像二分,或者树上二分)。那么问题变成了,每次要求各行在 trie 中的某区间和的和,而该区间是节点。那么每次可以跑一下,1log 求,加上按位贪心是 2log。实际上可以在按位贪心一路下来的过程中实时记录每行当前节点是哪儿,那每次就往下走一格就可以了。复杂度是 1log。

7. bzoj2741 - 【FOTILE模拟赛】L

终究还是遇到二维最大异或和问题了……该来的还是会来的。首先前缀和一下。

但是 01trie 这玩意没啥扩展性,只能做一维的。所以考虑直接枚举一维,是 \(\mathrm O(qn\log )\) 的,都比较小,\(qn\) 是 1e8 左右,只需要再减少一点点就可以了。别的数据结构看起来都铁定做不了,考虑分块。

把边角处理一下,问题就变成了 \([l,r]\)\([l,r]\) 内选两个数异或和最大。分块的话,分成三种:零碎和零碎、零碎和整块、整块和整块。直接枚举零碎的可以把前两者都做了(配合可持久化 trie),根对。对后者考虑预处理,整块 * 整块是线性,每个 log,就是线对。然后一个区间内的整块要两两枚举,那就是线性,比暴力枚举一维可持久化 trie 少一个 log,但还是不行。考虑对每个块预处理 rmq,其实根本不用 st 表,直接记录下所有区间。。。那样是 \(S^3\) 也就是线根的。总复杂度线根对。

根号分治

RT,虽说不算「新学算法」,但也勉强丢这儿了,亿些杂题。

1. P3396 - 哈希冲突

这种同余系的位置集合无法用什么正常的 ds 维护。这时候基本上就是根号分治来平衡暴力了。

\(p>\sqrt n\) 的询问直接跑是根号的。\(p\leq\sqrt n\) 只能想办法维护了,幸运的是这样的 \(p\) 的种类比较少,作为一个突破口。那么先预处理出来了所有的 \((p,x)\) 的答案,看修改能不能搞。

修改的话,为了 \(p>\sqrt n\) 的查询需要实时改原数组,这个刚学 OI 都会。想要维护那东西,遍历一下 \(p\leq\sqrt n\),每个显然只有一个 \(x\) 被改,ok 完毕。

2. CF797E - Array Queries

又是阴间操作,果断根分。对 \(k\) 小的可以预处理(对每个 \(k\) 递推一遍即可),对 \(k\) 大的直接暴力。阈值取根号复杂度线根。

3. CF1039D - You Are Given a Tree

先考虑对于单个 \(k\) 如何求。不难想到一个树形 dp:\(dp_{i}\) 表示子树 \(i\) 最大能放得下多少个链。转移就把儿子的 dp 值加起来,但这时候可能能加一条穿过 \(i\) 的链。如何判断是否能加呢?我们还需要记录每个子树在链取得最多的时候,顶上剩的最长的直链。那么就很容易转移了。

直接搞是平方的。不难一个性质:\(k\) 的答案不超过 \(\dfrac nk\)。想到根分:前 \(S\) 暴力 dp,后面的答案有限,考虑对所有答案求那些答案等于当前答案的 \(k\) 们。而它显然是单调的,于是二分。\(S=\sqrt{n\log n}\) 时复杂度最优为 \(\mathrm O\!\left(n\sqrt{n\log n}\right)\)

然后这题卡常,把邻接表换成链式前向星(又想起了去年省选因为懒,没有做这一步卡常,而白白丢掉 60pts 的悲惨回忆),把递归 dfs 变成从后往前枚举 dfn,就可以过了。

4. CF1270F - Awesome Substrings

感觉要是知道了是根分就不是很难了吧。。。总之遇到这种 \(i\) 对复杂度贡献与 \(\dfrac ni\) 相关的题就要敏感,大概率不是调和级数就是根分。

我们设 \(a\)\(1\) 的个数,\(c=r-l+1\)\(b=\dfrac ca\)。由于 \(c\leq n\),所以 \(a,b\) 必有一个 \(\leq\sqrt n\) 的,就可以根分了。

\(a\leq\sqrt n\):就枚举 \(a\),遍历一下字符串,对每个位置作为 \(l\) 算出合法的 \(r\) 的区间,容易算出区间内到 \(l\) 距离为 \(a\) 倍数的个数。

\(b\leq\sqrt n,a>\sqrt n\):那肯定是枚举 \(b\),设 \(S\) 为前缀和,那么 \([l,r]\) 合法需要满足 \(r-l+1=b(S_r-S_{l-1})\),即 \(l-1-bS_{l-1}=r-bS_r\)。那扫一遍装个桶,但是并不是实时查询的(因为要满足 \(a>\sqrt n\) 呀),所以可以用一个桶装下所有询问。注意空间不要爆。

5. CF506D - Mr. Kitayuta's Colorful Graph

这题和下方的题都是很久以前 hb 讲过的,当年认为是好题但是没有及时写题解。现在来整理一下。

这题想到根号分治有两个原因:一个是这是图上问题,通常没什么 polylog 做法;一个是「若干种东西,总和复杂度能接受」这种东西其实就是个很经典的根分模型(很常见的对图进行大点小点分类就是这种)。

于是容易想到对颜色分类,边数超过阈值 \(S=\sqrt n\) 的称为大颜色,否则称为小颜色,分别处理。大颜色不多,对每个暴力建个并查集然后询问的时候暴力枚举并查集们查即可。对小颜色处理稍微复杂,虽然边数小但不容易想到什么卵用。这就要有个 OI 见地了:根分里有个结论就是对小颜色,每个贡献个平方的复杂度,是线根的(证明很容易,根据贪心,要割开一条带子使得平方和尽量大显然是尽可能不割,而由于颜色的限制,最大就是 \(\sqrt n\)\(\sqrt n\),总复杂度就是 \(\sqrt n(\sqrt n)^2=n\sqrt n\))。那为什么不常见呢?因为常见的线根都是让小颜色在询问的时候暴力,这样不能保证每个块只访问一次;而预处理就可以。

那就很简单了,就对每种小颜色搞出所有连通的点对,容易知道对复杂度贡献是边数平方。

6. P3645 - 雅加达的摩天楼

zrm 当时说 doge 是狗

首先可以明确的是,一个 doge 最优决策下不会接手两遍。那么就很容易建出最短路,就是每个 doge 初始位置是节点,连向所有它能到达的位置,边权为跳的步数。

但是显然会 T。一个 doge 对边数复杂度的贡献是 \(\mathrm O\!\left(\dfrac n{P_i}\right)\),那么不难想到根分。当 \(P_i\geq\sqrt n\) 时直接暴力建,否则弱点是 \(P_i\) 的种类特别少。考虑对每种能不能用线性的时间建边?是可以的,对每个剩余系,每个 doge 连的边不可能跨 doge,那样不可能是最优,所以扫一遍该同余系下的有序 doge 序列就可以线性建边了。最终边数是线根,带上 dijkstra 复杂度是线根对。

cdq 分治

1. P4755 - Beautiful Pair

数数对,cdq 分治走起。然后处理的时候,就枚举 max 在哪一边的哪个位置(为了去重,左等右不等),可以 two-pointers 搞出另一边的合法位置范围。那么要数的就是另一边该范围内的 \(\leq\left\lfloor\dfrac {mx}{a_i}\right\rfloor\) 的个数,用离散化 + BIT 即可动态增删(配合 two-pointers)(当然你动态开点线段树我也不拦你),带上 cdq 总复杂度 2log。

2. P3810 - 【模板】三维偏序(陌上花开)

昨天上午调了一早上,下午去模拟,晚上颓掉了,现在才写题解,我是鸽王/cy

这里使用的是 cdq 套 BIT。如果直接在原序列上 cdq 分治,会有相等元素互相处理的情况,构不成 DAG,比较麻烦。所以对于偏序问题,最好把每个点拆成一个加点一个询问然后 cdq 就直接做了。还有,在给操作询问混合序列排序的时候,需要满足值相等的操作和询问中操作在前面,这就需要排序算法稳定,STL 里有个 stable_sort,不仅稳定似乎还比 sort 快(?)

3. P3157 - 动态逆序对

稍微转化一下就是个动态二维数点。考虑用 cdq 套 BIT。

大锅乱炖修改询问,静态二维数点的部分也丢进去一起算,那叫一个爽!

丢个代码吧,因为动态二维数点比三维偏序强。code

4. P4169 - 天使玩偶/SJY摆棋子

拆成四个象限之后就是以 max 为操作的动态二维数点,每种分开来计算,就坐标变换一波然后 CV 即可。每种就直接 cdq?由于 max 不可撤销,BIT 的清空就直接赋 inf 吧。。虽说 4 个 3e5 要有 1e6 的,但是 cdq 和 BIT 这两个玩意常数都比较小,开个 O2 还是能跑进 3s 的。

5. P4390 - Mokia 摩基亚

动态二维数点板子题。。。对非前缀矩形容斥一波即可。

6. P3658 - Why Did the Cow Cross the Road III

对每个 \(b\) 统计答案,那么在 \(i,a^{-1}_{i},b^{-1}_i\) 这三个维度上面各有限制,那就拆成若干个前缀就是个裸的静态三维数点。就排个序变成动态二维数点然后直接上 cdq 套 BIT。注意数点问题的排序一定要稳定!!!

7. CF1045G - AI robots

其实这题可以无视 \(k\leq 20\)。。。

就看 \(i,j\) 被数需要满足:

  • \(q_j\in[q_i-k,q_i+k]\)
  • \(x_j\in[x_i-r_i,x_i+r_i]\)
  • \(x_i\in[x_j-r_j,x_j+r_j]\)

前两个差分开来,后一个搞两位,这样是静态四维数点。考虑把最后一个条件也给差分开来,那么就是两遍三位数点。大常数 2log,竟然还 1.8s 过了。

8. CF848C - Goodbye Souvenir

拆成两种,负贡献和正贡献,两种类似就算两遍,现在只讲前面。那么有贡献当且仅当 \(i\in[l,r],prv_i\in[1,l-1]\)。那么如果实时维护前驱后继的话就是个动态二维数点直接做了。每次前驱后继的变化量显然是常数,只需要用 set 维护。

9. hdu5126 - stars

本来想找个四维偏序板子的,结果找到这个是动态三维数点。不过是等价的。

就 cdq 一波,变成静态三位数点。按第一维排序,变成动态二维数点。cdq 第二波,变成静态二位数点。按第二维排序,变成动态一维数点。然后就可以 BIT 了(要对第三维离散化)。

对询问要拆成八个,3log,不知道是怎么 A 的,挺神奇。cdq 和 BIT 常数都非常小。

10. P4093 - 序列

考虑一个序列满足条件的条件。首先本身要是不降的。然后对于每个可改变的地方,要满足改变之后左右两个都满足不降。综合一下,设 \(mn_i,mx_i\) 分别表示 \(i\) 能变成的(包括初始)最小值,最大值,那么在每处都要满足 \(mx_i\leq a_{i+1},a_i\leq mn_{i+1}\)

子序列,DP。\(dp_i=\max\limits_{j\in[1,i),a_j\leq mn_i,mx_j\leq a_i}\{dp_j\}+1\)。这个转移条件就是个动态二维数点。DP 这东西嘛,你得知道它的值才能更新后面的,所以看起来是强制在线的。回想 LIS,是个在线动态一维数点,用的是 BIT,没错对吧。那这里要二维线段树了吗?不想写。

仔细想一下,DP 其实不完全是强制在线的(你要看做强制在线,二进制分组 + 主席树我也不拦你)。毕竟加密版本的强制在线,你算完 DP 也不能更新很后面的,但是这里可以。我们依然尝试使用离线分治算法,试图转动态为静态。

我们现在对一个区间搞 cdq 分治,目标是准备回溯的时候这个区间的 DP 都已经计算完毕。然后一般的 cdq 是先分治两边,然后算左边对右边的贡献。但是到这个「半强制在线」的情景下就有点问题了:第二步分治右边,这时候左边还没贡献到右边呢,无论如何结束的时候也不可能得到右半部分的正确 DP 值,这样就达不到目标了。那只需要将第二步和第三步调换位置即可。这就是套路的「cdq 优化 DP」。每次递归到单点的时候,说明此时已经接收了前面所有的贡献,只需要整理一下子即可。

对于这题来说,就只跟普通的离线动态二维数点有个交换二三步的区别。

11. P3364 - Cool loves touli

破烂题面,交了个 RE / WA 程序上去发现 \(a_i\) 没有重复。

那就是个二维数点 DP 啊,跟上一题一样,直接抄代码。

12. P2487 - 拦截导弹

首先要是计算 LIS,那就是个普通的二维数点 DP。

但我们同时还要记录对每个 DP,能达到当前这个最大 DP 值的方案数。这似乎有点棘手了:要查询在二维限制下,值等于某个特定值的个数?不好维护。但是如果想到线段树的话,容易想到线段树每个节点维护当前区间最大值和最大值出现次数(带权)。那这个东西 BIT 也可以维护,修改、查询以及更新 DP 的时候都用这个合并方式。有点意思,第一次遇到在 BIT 上合并复杂的信息。

然后就是正反两遍(维护 4 个 DP 数组),以算出对于每个位置,包含它的最大拦截数以及达到这个最大拦截数的包含它的方案数。

但仔细想一想结果可能很大很大,是指数级的。试了一波 ll 交上去果然 WA 了 3 个点。改成 double 就过了(double 在值很大的情况下会适当损失精度 instead of 爆炸)。

13. P3769 - TATT

是四维数点 DP 板子题。但是比三维的情况有意思。

套两层 cdq 然后 3log 对吧(话说 5e4 3log 我只跑了 250ms,这说明 cdq、BIT 这两者常数是真的小(在数点问题不差分拆开的情况下,因为这题的数点就是四维前缀))。外层还是对 DP 的处理,内层就完全是对普通的修改与询问的处理了,不需要再交换 cdq 的二三部分,而且需要区分「修改」与「询问」(不像 DP 既是修改又是询问,只能取左边的修改和右边的询问)。但是记录贡献不需要像普通 ds 题一样真的记录答案,只需要贡献 DP 即可。

放个板子吧
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
int lowbit(int x){return x&-x;}
const int N=500000;
int n;
struct type{
	int x,y,z,w;
	friend bool operator<(type x,type y){
		if(x.x!=y.x)return x.x<y.x;
		if(x.y!=y.y)return x.y<y.y;
		if(x.z!=y.z)return x.z<y.z;
		return x.w<y.w;
	}
}a[N];
vector<int> n3;
void discrete(){
	sort(n3.begin(),n3.end());n3.resize(unique(n3.begin(),n3.end())-n3.begin());
	for(int i=1;i<=n;i++)a[i].w=lower_bound(n3.begin(),n3.end(),a[i].w)-n3.begin()+1;
}
int dp[N];
struct query{
	int y,z,w,id,op;
	friend bool operator<(query x,query y){return x.y<y.y;}
};
vector<query> v;
struct query0{
	int z,w,id,op;
	friend bool operator<(query0 x,query0 y){return x.z<y.z;}
};
struct bitree{
	int mx[N];
	bitree(){memset(mx,0,sizeof(mx));}
	void insert(int x,int v){
		while(x<=n3.size())mx[x]=max(mx[x],v),x+=lowbit(x);
	}
	void clear(int x){
		while(x<=n3.size())mx[x]=0,x+=lowbit(x);
	}
	int Mx(int x){
		int res=0;
		while(x)res=max(res,mx[x]),x-=lowbit(x);
		return res;
	}
}bit;
void cdq0(int l=0,int r=v.size()-1){
	if(l==r)return;
	int mid=l+r>>1;
	cdq0(l,mid),cdq0(mid+1,r);
	vector<query0> v0;
	for(int i=l;i<=mid;i++)if(v[i].op==1)v0.pb(query0({v[i].z,v[i].w,v[i].id,v[i].op}));
	for(int i=mid+1;i<=r;i++)if(v[i].op==0)v0.pb(query0({v[i].z,v[i].w,v[i].id,v[i].op}));
	stable_sort(v0.begin(),v0.end());
	for(int i=0;i<v0.size();i++){
		int w=v0[i].w,id=v0[i].id,op=v0[i].op;
		if(op)bit.insert(w,dp[id]);
		else dp[id]=max(dp[id],bit.Mx(w));
	}
	for(int i=0;i<v0.size();i++){
		int w=v0[i].w,id=v0[i].id,op=v0[i].op;
		if(op)bit.clear(w);
	}
}
int ans;
void cdq(int l=1,int r=n){
	if(l==r)return dp[l]++,ans=max(ans,dp[l]),void();
	int mid=l+r>>1;
	cdq(l,mid);
	v.clear();
	for(int i=l;i<=mid;i++)v.pb(query({a[i].y,a[i].z,a[i].w,i,1}));
	for(int i=mid+1;i<=r;i++)v.pb(query({a[i].y,a[i].z,a[i].w,i,0}));
	stable_sort(v.begin(),v.end());
	cdq0();
	cdq(mid+1,r);
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld%lld%lld%lld",&a[i].x,&a[i].y,&a[i].z,&a[i].w);
	for(int i=1;i<=n;i++)n3.pb(a[i].w);
	discrete();
	sort(a+1,a+n+1);
	cdq();
	cout<<ans;
	return 0;
}

14. P5621 - 德丽莎世界第一可爱

求助,快要笑死了怎么办。跟上一题基本一样,只是这题带权了。改几个字母就能过。

但奇怪的是,数据范围一样,为什么这题就要开 O2 才能过呢?可能是因为这题值域到了 ll 吧,虽然上一题开了 ll,但没达到 ll,可能常数不变?

15. P4849 - 寻找宝藏

这题就是四维情况下的 P2487 嘛。就用那题的套路用 BIT 维护最大值以及最大值出现次数就可以了。

16. P4690 - 镜中的昆虫

hot ds tea,但是放在 Yn 里略显水了,题解

树论方向

dsu on tree

reference

1. CF600E - Lomsat gelral

近乎模板的题。dsu on tree 板子套上之后,任务就变成了如何快速维护当前主导地位的颜色和。这个很自然,就维护一个计数数组和当前最大出现次数和主导地位颜色和。合并进去的话就,对应位置 ++,然后分三类更新另外两个值。撤销直接赋 \(0\) 就好(dsu on tree 只要求可以在关于子树大小复杂度内全局清空,而非撤销,这是好的)。

2. CF570D - Tree Requests

这个就要像莫队那样把询问离线下来然后安放在节点处了。

考虑 dsu on tree 然后维护的是在每个深度在当前子树内的所有字母出现次数的奇偶性(可以用 bitmask 优化)。然后就照常做就好了。

然后这个每个点的信息量是和子树深度同阶的,是要低于子数大小的,所以 dsu on tree 依然有效。

3. CF246E - Blood Cousins Return

(都是水题,可能有更优秀的方法,不管了,只顾 dsu on tree)

也没啥,跟上一题差不多,需要 set 维护。

但是发现按照 dep 选重儿子的复杂度是假的。还疑神疑鬼改了几发 unordered_map(捂脸

4. CF208E - Blood Cousins

上一题的弱化。

至于如何求 \(k\) 级祖先,这个有很多方法,复杂度最优的应该是长剖,但我还不会。我使用的是倍增。

5. CF1009F - Dominant Indices

dsu on tree 咋这么多关于深度的题啊,是因为这个的暴力比较直观吗。大概暴力复杂度只关于子树大小就可以直接 dsu on tree 了吧。

就跟上一题差不多处理,然后实时维护一个最优决策即可。

6. CF375D - Tree and Queries

2400 你再给我 2400。

维护 cnt 的同时,维护一个关于颜色 cnt 值的后缀计数。然后每次 \(\pm1\),游走量是常数,直接做即可。

7. CF741D - Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths

hot tea,题解

长链剖分

其实没几个题。

1. CF504E - Misha and LCP on Tree

是个 2020 jxd 作业。很早就想 A 掉了,可惜不会长剖。

其实是很无脑的题。考虑朴素的二分 + 哈希求 lcp。那问题转化成了如何快速算某条链上前若干个字符构成的字符串的哈希值。

显然哈希值是有可逆性和可合并性的,所以可以在 lca 断开,两边用树上差分。还需要快速知道这条链上第若干个位置是哪儿。显然等价于树上 \(k\) 级祖先,可以长剖做到常数,这样算上二分是 1log 的。注意要搞向上向下两种哈希,本质是不同的。

这么无脑,*3000 的主要原因应该是难写 + 卡常。卡常的话,换一波快读,存几个多次被计算的值,lca 从倍增改到欧拉序 + ST 表,取模优化,开个 O3 就过了。

code

2. CF1009F - Dominant Indices

学 dsu on tree 时候刷的几个只关于深度的大水题的一个代表。现在发现这类可以用长剖做到线性,因为每个点处的信息量是只与子树深度相关的。

把原来代码稍微改一下,然后在统计答案的时候开个 vector<vector> 存一下浅儿子信息就好。不过也没快多少,毕竟 1e6 的图遍历常数在那儿呢。

3. P5904 - HOT-Hotels 加强版

妹妹题,题解

4. P3899 - 谈笑风生

u1s1 这题改题面之前是真的 excited

对于一个询问,显然只可能是 \(a,b,c\) 或者 \(b,a,c\) 这样排座次,分成两类。后者比较简单,\(c\)\(sz_a-1\)\(b\)\(\min(dep_a-1,k)\),乘一下就好了;前者的话,答案是 \(a\) 子树中深度在 \(dep_a+1\sim dep_a+x\) 之间的点的 \(sz-1\) 的和。这恰好是一个容易长剖优化的东西。唯一有点变化的东西是这个要求的是一个类似前缀和的东西,但如果在 \(a\) 处更新的话,所有深度处的前缀和都要受牵连,难免用线段树或者其他东西维护。但是后缀和是不用顾虑的,于是就做完了。又柔和又容易理解,相比于 P5904 是个 yyq 题,一点都不性感。

5. P4292 - 重建计划

题解

6. bzoj3252 - 攻略

考虑把点权搞到边上。显然每次一定是选一个叶子到根的路径……

首先有一些其他做法,比如线段树在 dfn 上区修。这里讨论最优秀的长剖(带边权)优化贪心。

长链跟叶子显然是形成双射的。那么结论就是,答案是选出前 \(k\) 大的长链。

这玩意不太好理解。我们归纳地考虑,考虑任意一步,已经有一些点被选了。那么这一步选的叶子肯定是满足它的长链是恰好「抵到」被选的点的(即链顶的父亲已经被选 / 没有父亲),因为如果不是的话,那么它的链顶的兄弟中肯定有更优的。那么满足这些条件的候选叶子们的贡献显然就是长链长度,因为上面的都被选了。那么每次选没被选的最长长链就是正确的了。

你可能会说,这不也要排序吗,不也是线对吗,比线段树优在哪里呢?长剖吼啊。注意到线段树终究是一系列操作,还不够本质;而长剖已经把每一步都当作数据给析出来了,自然要比线段树灵活得多。不过这玩意可能没啥可扩展性,大概就是长剖的一个偏门应用吧,就当作结论记好了。

7. CF526G - Spiders Evil Plan

神仙题,题解

8. P5384 - 雪松果树

做线段树合并做到这题了。我一看!这不是长剖板子吗?

这题显然可以分成求树上 \(k\) 级祖先和树上 \(k\) 级后代个数两部分。前者虽然长剖查询是常数复杂度,但是预处理带 log……但这题可以离线,所以可以 dfs 过程中维护递归栈来线性。后者就真的是长剖板子呀……总复杂度线性。

然后我开了若干个 vector(个人习惯不好),被卡空间了……这题空间太紧了……只好改成邻接表 A 掉了(又想起来去年省选因为懒而没有 vector 改邻接表而丢了 60pts)。

虚树

虚树这东西就是个简简单单的套路,没啥好做题的……

1. P2495 - 消耗战

1A,爽

考虑朴素版本的树形 DP。\(dp_i\) 表示子树 \(i\) 解决相同的子问题的答案。当 \(i\) 是资源丰富的岛屿时,问题无效,答案是 \(+\infty\)。否则显然每个儿子子树是独立的,考虑一个一个割。要么是割当前这条边,要么是用 \(dp_{son}\) 来割,选较小的那个,然后对于所有儿子把它们加起来就行了。

建了虚树(包含根节点,不包含就是错的)之后,稍微有一个变化,就是边权不再是单边了,而是一条链的 \(\min\),倍增维护一下就行了。

2. P4103 - 大工程

没有 1A,tbgxl(其实只是第二三问输出反了)

我们考虑以两端点的 LCA 来贡献。对某个 LCA \(x\),如果知道了它所有儿子子树内被选出的点的个数、深度和、最小深度、最大深度,那么在它自身和儿子序列上扫一遍就求出来了。具体怎么扫就不说了,太简单了,dsu on tree 和长剖的时候有比这个难一万倍的。

然后显然只有 LCA 有效,所以建虚树是没错的。(这次可能选出 \(1\),要在加入虚树的时候判一下)

u1s1 这题虚树其实不带 \(1\) 也行?但是带了也没关系。

3. CF613D - Kingdom and its Cities

u1s1 为啥所有虚树题的输入格式都长一样啊……

这题稍有一些难度,但主要在树形 DP,而不在虚树。

我们考虑对某个子树回答相同的子问题。需要分几个小类:

  1. 根节点是一个 city。那么所有儿子子树不仅内部要解决,而且必须满足断点把这个儿子子树给「覆盖」住了。此时本子树显然是一定不被覆盖住的;
  2. 根节点不是 city。那么儿子子树内部都要解决是肯定的,然后再分出几个小类:
    1. 儿子子树全被覆盖了。那就没有其它要担心的了,此时本子树是被覆盖的;
    2. 儿子子树恰有一个没被覆盖。那也不用添加其它点(添加的话是下面那类),此时本子树没被覆盖;
    3. 普适地:在根节点上断掉,这样本子树是被覆盖了的。

于是对每个 \(x\) 记录 \(dp0_x,dp1_x\) 分别表示必须覆盖 / 不必覆盖。答案是 \(dp0_1\)。在虚树上 DP 的时候有个小变化:第一类中如果在原树中当前节点和儿子不是相邻关系,那么可以在中间那条路径上断的,此时要加一,并且不要求儿子子树被覆盖。

4. P6572 - Railway

首先,对一个点集,作用的边集显然是它们的虚树(不能包含 \(1\),要主动把 \(1\) ban 掉)。

一开始想的是,这根本就不用虚树啊!直接找出它们所有人的 LCA,然后把每个点向 LCA 差分一波就完事了。但这样会算重。如果用线段树啥的区间 or 呢?那样不仅要树剖,而且树剖了也做不了,因为不同市长之间是加,不可能一会儿维护 or 一会儿维护加的。

那就只能硬着头皮建出虚树,然后在每对父子在原树中对应的路径上差分了。这样是不重不漏的。

5. P5680 - 共享单车

傻逼题一题。

  1. 题面不是给人看的。
  2. 强行上图。
  3. 数据范围这么小,像个傻逼一样。

tmd 傻逼一点就傻逼一点,题目还这么板。随便建最短路生成树,然后虚树随便 DP,路径边权和用树上前缀和,tmd 写了 3.5k tmd。

6. P3320 - 寻宝游戏

就是要求虚树上边权和的两倍。

一开始还分了好多类希望动态维护虚树的,但是太烦了,最后放弃了。看到题解区粉兔就蒙蔽了。

有这样一个结论:把宝物所在点按 dfn 排序之后,答案就是相邻(环形)两个的距离之和。证明是非常显然的。那么直接做就行了。

数论方向

CRT & ExCRT

非常无聊。

1. P3868 - 猜数字

哇塞,CRT 板子题!

用的 ExCRT 写的,互质的特性使得部分地方可以简写,比如不用求 lcm。但是解扩大倍数还是要扩大的,因为 exgcd 求出来 gcd 可能是 -1。。。还要龟速乘。还有个小坑,就是 \(n=1\) 是可能发生的,而它给出的并非模意义下。

2. P4774 - 屠龙勇士

数论基础小杂烩?算是 NOI 里较柔和的一题了吧。

首先用 multiset 模拟出每个巨龙对应的剑,然后可以列出一个 \(x\geq ?\) 的不等式和 \(a-tx\equiv 0\pmod p\) 的同余方程。对前者显然所有的随便合并得到一个下界,对后者用 exgcd 解出来得到 \(x\equiv ?\pmod ?\) 的形式,然后 ExCRT 合并一下。最后找到大于等于下界的最小解,这个随便做。

又是一个需要龟速乘的题,有被恶心到……

BSGS & ExBSGS

1. P5345 - 【XR-1】快乐肥宅

小粉兔!!!11

AC 20pts 可还行 yet another 数论小杂烩 可还行

先担心一个问题,就是这题的取模是 \(1\sim p\) 的……然后发现不用担心(

然后就是个离散对数方程组。每个 ExBSGS 解一下,三种情况:无解直接走;唯一解就遍历一遍所有方程看是否合法;两解的话就表示成 \(x\geq ?\)\(x\equiv ?\pmod ?\)(刚说通解形式不会考的呢,真香)。对前者所有的显然直接合并,对后者 ExCRT 合并,最后找最小解就随便做。

但是有个难点,ExCRT 合并出来的模数是 lcm,会很大怎么办?不难想到一个巧妙的解决方式:因为单个的 \(p\) 是 1e7,所以一定存在一个时刻使得合并起来的 \(p>10^9\) 但又没爆 ll。那么此时可以发现,如果把当前的解增加一个 \(p\),那也是不行的,遍历判一下即可。

莫比乌斯反演

1. P2522 - Problem b

莫反最基础的题。

先差分容斥,使得下界都为 \(1\)

\[ans=\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=s] \]

注意到,对这样一组 \(i,j\),可以同除以 \(s\) 得到一组互质的数,一组互质的数也可以逆过来得到一组 \(i,j\),易证它们一一对应。于是令 \(n\gets\left\lfloor\dfrac ns\right\rfloor,m\gets\left\lfloor\dfrac ms\right\rfloor\)

\[ans=\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=1] \]

这个 \([?=1]\) 是个 \(\epsilon\),可以用 \(\mu*1\) 替掉(这个还不是严格意义上的莫反,只是个狄卷的小应用),这是个套路。

\[\begin{aligned}ans&=\sum_{i=1}^n\sum_{j=1}^m\sum_{k\mid \gcd(i,j)}\mu(k)\\&=\sum_{i=1}^n\sum_{j=1}^m\sum_{k\mid i,k\mid j}\mu(k)\end{aligned} \]

(上一步体现了狄卷来推数论式子的优势)

换个求和号顺序(本质上是换贡献,让 \(\mu(k)\) 来贡献,这样对于特定 \(k\) 的贡献次数就可以直接按照意义整除来算)。

\[ans=\sum_k\mu(k)\left\lfloor\dfrac nk\right\rfloor\left\lfloor\dfrac mk\right\rfloor \]

这就是个二维整除分块。预处理的时候跑个 xxs,把 \(\mu\) 的前缀和给求出来,那么每块就可以直接算。复杂度 \(\mathrm O(n+T\sqrt n)\)

2. spoj LCMSUM - LCM Sum

题解

3. P3455 - ZAP-Queries

草了 跟 P2522 是一毛一样的(

还记得 2019.7 的时候我交错题了,交了个 dfs 序 + 线段树的代码上去,爆了零,然后持续了 1.5 years 这道题旁边有个红色的 0

4. P2257 - YY的GCD

\[ans=\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)\in \mathbb P] \]

按照剧本,考虑让所有 gcd 的值一个一个贡献。

\[ans=\sum_{k\in\mathbb P}\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=k] \]

这就直接套上面推好的结论了。题解里一句话说的太真实了:我一看到那种要设函数的莫反就头疼,所以我决定再也不用它!

\[ans=\sum_{k\in\mathbb P}\sum_o\mu(o)\left\lfloor\dfrac n{ko}\right\rfloor\left\lfloor\dfrac m{ko}\right\rfloor \]

这样可以枚举 \(k\),然后二维整除分块。但这样太慢了。想要极大发挥整除分块的优势的话,就直接从整除的值入手,让整除的值来贡献。

\[\begin{aligned}ans&=\sum_{p}\left\lfloor\dfrac np\right\rfloor\left\lfloor\dfrac mp\right\rfloor\sum_{k\in\mathbb P}\sum_{o}[ko=p]\mu(o)\\&=\sum_p\left\lfloor\dfrac np\right\rfloor\left\lfloor\dfrac mp\right\rfloor\sum_{k\in\mathbb P,k\mid p}\mu\!\left(\dfrac pk\right)\end{aligned} \]

惊喜的发现后面这个和式是个关于 \(p\) 的函数,可以直接预处理然后求前缀和!直接让所有质数贡献的话,复杂度是和埃氏筛一样的 \(\mathrm O(n\log\log n)\)。事实上它可以被线性筛筛出来。质数的时候显然;最小质因子是一次的时候,考虑取掉这个质因子,它的函数值显然是对当前函数值贡献个相反数的,然后再加上去掉这个质因子后得到的数的莫比乌斯函数值;最小质因子是多次的时候,显然除了去掉这个质因子后得到的数的莫比乌斯函数值,其他的贡献都为 0。于是复杂度 \(\mathrm O(n+T\sqrt n)\)

5. P2568 - GCD

比上一题弱,嘿嘿嘿嘿这事闹的。

6. P1829 - Crash的数字表格

第一道自己做出来的莫反题祭!

\[ans=\sum_{i=1}^n\sum_{j=1}^m\dfrac{ij}{\gcd(i,j)} \]

老套路,让 gcd 值来贡献。

\[\begin{aligned}ans&=\sum_{k}\dfrac1k\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=k]ij\\&=\sum_k\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}\sum_{j=1}^{\left\lfloor\frac mk\right\rfloor}[\gcd(i,j)=1]ijk\\&=\sum_kk\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}i\sum_{j=1}^{\left\lfloor\frac mk\right\rfloor}j\sum_{o\mid i,o\mid j}\mu(o)\\&=\sum_kk\sum_o\mu(o)\dfrac{\left(1+\left\lfloor\dfrac n{ko}\right\rfloor\right)\left\lfloor\dfrac n{ko}\right\rfloor\left(1+\left\lfloor\dfrac m{ko}\right\rfloor\right)\left\lfloor\dfrac m{ko}\right\rfloor}4o^2\end{aligned} \]

直接枚举 \(ko\)

\[ans=\sum_p\dfrac{???}4\sum_{k\mid p}k\mu\!\left(\dfrac pk\right)\!\left(\dfrac pk\right)^2 \]

对前面的 \(p\) 整除分块,后面那个东西相当于 \((\mathrm{id}*(\mu\times\mathrm{id}_2))(p)\),是个积性函数,可以线性筛出来然后求前缀和。至于质数的幂处的值怎么快速算,可以利用 \(\mu\!\left(p^k\right)=0(k\geq 2)\) 列个 \(\mathrm O(1)\) 的式子直接算

7. AGC038C - LCMs

自己做出来的第二道莫反题祭

跟上一题大差不差。只不过要记录一个每个值出现的次数 \(a_i\),并且乘进去。推到第三步的时候:

\[ans=\sum_kk\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}ia_{ik}\sum_{j=1}^{\left\lfloor\frac nk\right\rfloor}ja_{jk}\sum_{o\mid i,o\mid j}\mu(o) \]

再下一步,发现倍数和不能用一个简洁的式子来表达了,因为有 \(a\) 混进去。考虑设 \(S(k,o)\) 表示 \(1\sim\left\lfloor\dfrac nk\right\rfloor\)\(o\) 的倍数 \(i\)\(ia_{ik}\) 的和,那么

\[ans=\sum_{k}k\sum_o\mu(o)S^2(k,o) \]

之前看到一篇题解里面说,推柿子推到一定程度就可以了。我们看看现在能不能做。考虑固定住 \(k\),那么对 \(ok>n\),贡献显然是为 0 的,于是直接枚举 \(o,k\) 是调和级数复杂度!再看看 \(S\) 能不能算出来,似乎并不是那么好算,但是容易想到将 \(S\) 的数论意义稍微补的优美一下,算 \(ika_{ik}\) 的和,那么 \(S(k,o)\) 显然就表示 \(n\) 以内 \(ok\) 的倍数 \(i\)\(ia_i\) 之和,就只和 \(ik\) 有关系了,就可以降维了!这样子 \(S\) 就直接调和级数爆算,然后算答案的时候需要乘个 \(\dfrac1{k^2}\)

8. P3911 - 最小公倍数之和

跟上一题一样哈哈哈哈哈

9. P4450 - 双亲数

wdnmd 真就又和 P2522 是重题呗。好耶,又水了一个紫题。

10. P6156 - 简单题

套路题一道。枚举 gcd 的值,让 gcd 贡献。

\[\begin{aligned} ans&=\sum_k\mu^2(k)k\sum_{i=1}^n\sum_{j=1}^n(i+j)^m[\gcd(i,j)=k]\\ &=\sum_{k}\mu^2(k)k^{m+1}\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}\sum_{j=1}^{\left\lfloor\frac nk\right\rfloor}(i+j)^m[\gcd(i,j)=1]\\ &=\sum_k\mu^2(k)k^{m+1}\sum_o\mu(o)o^mS\!\left(\left\lfloor\dfrac n{ko}\right\rfloor\right) \end{aligned} \]

其中 \(S(x)=\sum\limits_{i=1}^n\sum\limits_{j=1}^n(i+j)^m\)。下面还是老套路。

\[ans=\sum_pS\!\left(\left\lfloor\frac np\right\rfloor\right)\sum_{k\mid p}\mu^2(k)k^{m+1}\mu\!\left(\dfrac pk\right)\!\left(\dfrac pk\right)^m \]

后面那个狄卷就是 \(\!\left(\mu^2\times \mathrm{id}_{m+1}\right)*(\mu\times\mathrm{id}_m)\),可以线筛。

需要一个线性算法。考虑 \(S\) 如何求?稍微列一下每个 \(i+j\) 值出现个次数,发现预处理出 \(\mathrm{id}_m\) 的前缀和就随便算了。那么如何线性预处理 \(\mathrm{id}_m\) 呢?由于它是完全积性函数,只需要算质数处的值(快速幂),其他的线筛。如果 \(\pi(n)=\mathrm O\!\left(\dfrac n{\log n}\right)\),那么乘以一个 log 恰好是线性(似乎发现了新的线性求逆元方法?)

11. P6222 - 「P6156 简单题」加强版

12. loj6053 - 简单的函数

算是 min25 筛板子题?(逐渐脱离莫反主题)

显然对奇质数 \(p\)\(f(p)=p-1\)。那就直接 min25 筛就可以了,第二部分的时候需要特判一下 \(p=2\) 是否被贡献。

13. P3327 - 约数个数和

首先有个 sb 公式:

\[d(xy)=\sum_{i\mid x}\sum_{j\mid y}[i\perp j] \]

证明的话就对一个特定的质因数,设在 \(x,y\) 中的次数分别为 \(\alpha,\beta\),那么贡献是 \(1+\alpha+\beta\),恰好和选两个互质的因数的方案数一样。不是什么可推的东西,就当结论背吧。

然后就套路了:

\[\begin{aligned} ans&=\sum_{i=1}^n\sum_{j=1}^m\sum_{k\mid i}\sum_{o\mid j}[k\perp o]\\ &=\sum_{i=1}^n\sum_{j=1}^m\sum_{k\mid i}\sum_{o|j}\sum_{p|k,p|o}\mu(p)\\&=\sum_{i=1}^n\sum_{j=1}^m\sum_{p|i,p|j}\mu(p)\mathrm d\!\left(\dfrac ip\right)\!\mathrm d\!\left(\dfrac jp\right)\\&=\sum_p\mu(p)\sum_{i=1}^n[p\mid i]\mathrm d\!\left(\dfrac ip\right)\!\sum_{j=1}^m[p\mid j]\mathrm d\!\left(\dfrac jp\right) \end{aligned} \]

然后你看 \(\sum\limits_{i=1}^n[p\mid i]\mathrm d\!\left(\dfrac ip\right)\) 这玩意不直接算吗,于是

\[ans=\sum_p\mu(p)\sum_{i=1}^{\left\lfloor\frac np\right\rfloor}\mathrm d(i)\sum_{j=1}^{\left\lfloor\frac mp\right\rfloor}\mathrm d(j) \]

然后整除分块,需要线筛出 \(\mu\)\(\mathrm d\) 的前缀和。

14. spoj DIVCNTK - Counting Divisors (general)

容易发现要求前缀和的函数是 \(\mathrm d\) 和幂函数的复合,依然是积性函数,于是 min25 直接上!\(f\!\left(p^x\right)=xk+1\),在质数处事多项式。

15. spoj DIVCNT3 - Counting Divisors (cube)

上一题 \(k\) 改成 \(3\)

16. P3768 - 简单的数学题

前面的推柿子实在是太套路了,就贴个最后一句吧。

\[ans=\sum_p\!\left(\dfrac{\left\lfloor\dfrac np\right\rfloor\!\left(\left\lfloor\dfrac np\right\rfloor+1\right)}2\right)^2\sum_{k\mid p}k^3\mu\!\left(\dfrac pk\right)\!\left(\dfrac pk\right)^2 \]

整除分块,需要快速求 \(\mathrm{id}_3*(\mu\times\mathrm{id}_2)\) 关键点处的前缀和。考虑杜教筛,这个函数恰好是个狄卷的形式……于是 \(\mathrm{id}_3\) 显然可杜教筛(不需要筛),\(\mu\times\mathrm{id}_2\) 是个经典的东西,\((\mu\times\mathrm{id}_2)*\mathrm{id_2}=\epsilon\)。就可了。需要跑两遍杜教筛,还要取好多模,常数异常大,卡了好久。最后关键一招是把两次杜教筛的循环放一起写,然后把常数多平衡到了线筛上面一点(筛到了 \(n^{0.69030899869919435856412166841735}\))。

17. P2158 - 仪仗队

\[ans=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}[i\perp j] \]

用不着莫反。把 0 拎出来,\(n=1\) 判一手。

\[ans=2+2\sum_{i=1}^{n-1}\varphi(i)-1 \]

线筛走起。

18. P3312 - 数表

前面都很套路,见过一百遍了:

\[\begin{aligned} ans&=\sum_{i=1}^n\sum_{j=1}^m\sigma(\gcd(i,j))\\ &=\sum_{\sigma(k)\leq a}\sigma(k)\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}\sum_{j=1}^{\left\lfloor\frac mk\right\rfloor}\sum_{o\mid i,o\mid j}\mu(o)\\ &=\sum_{\sigma(k)\leq a}\sigma(k)\sum_o\mu(o)\left\lfloor\frac n{ko}\right\rfloor\left\lfloor\frac m{ko}\right\rfloor\\ &=\sum_p\left\lfloor\frac np\right\rfloor\left\lfloor\frac mp\right\rfloor\sum_{\sigma(k)\leq a,k\mid p}\sigma(k)\mu\!\left(\left\lfloor\frac pk\right\rfloor\right) \end{aligned} \]

如果没有 \(a\) 的限制就直接做了,就后面是个狄卷,是积性,线筛,前面整除分块。

\(a\) 的话一种办法是每次直接枚举因数,这样根据杜教筛复杂度分析是 3/4 方的,计算量大概是 1e8,可能能过去?或者线筛前 2/3 方,但是这玩意好像不可线筛?

考虑线对地计算狄卷的过程,那么这次就是枚举因数只枚举那些 \(\sigma(k)\leq a\)\(k\),单次最坏依然是线对,不行啊。但是这个方案很容易把所有询问合并,这是个见得多的套路了。就离线按照 \(a\) 排序,然后类似个 two-pointers 把 \(k\) 们按照 \(\sigma(k)\) 排序,实时维护狄卷函数值,每个值只被加入一次。那这样更新的次数是线对。整除分块里需要求区间和,搞个 BIT 维护一下,复杂度 \(\mathrm O\!\left(q\sqrt n\log n+n\log^2 n\right)\)。(u1s1 第一次见到数学和 ds 合并的题,而且还不是缝合怪,合并的非常巧妙)

19. P5221 - Product

\(\prod\) 比较优美,可以直接拆~

\[ans=\prod_{i=1}^ni^{2n}\prod_k\left(\dfrac 1{k^2}\right)^{f\left(\left\lfloor\frac nk\right\rfloor\right)} \]

其中 \(f\) 是仪仗队那题的答案,欧拉函数跑一跑。

卡时卡空,一点营养都没有,无力吐槽。对前者是 \(\prod\) 起来一起幂,线性。对后者可以整除分块,就随便做了。但是技术难点在求 \(\mathrm{id}_{-2}\) 的区间和,线性求逆的话要多开一个数组,M。然后快速幂竟然能过?!也没谁了。线筛的时候还被迫省掉了表示每个数除尽最小质因子得到的数的数组,只能临场除,复杂度不会算,不高于线对。

20. P3704 - 数字表格

just this? 黑题?小学二年级也能做(

第一眼感觉要用 \(f_{\gcd(x,y)}=\gcd(f_x,f_y)\) 这个结论,结果发现不用(

\(\prod\) 的推柿子不太熟练,根据意义就可以了。

\[\begin{aligned} ans&=\prod_{i=1}^n\prod_{j=1}^mf_{\gcd(i,j)}\\ &=\prod_kf_k^{\sum\limits_o\mu(o)\left\lfloor\frac n{ko}\right\rfloor\left\lfloor\frac m{ko}\right\rfloor}\\ &=\prod_p\prod_{k\mid p}f_k^{\mu\left(\frac pk\right)\left\lfloor\frac np\right\rfloor\left\lfloor\frac mp\right\rfloor}\\ &=\prod_p\!\left(\prod_{k\mid p}f_k^{\mu\left(\frac pk\right)}\right)^{\left\lfloor\frac np\right\rfloor\left\lfloor\frac mp\right\rfloor} \end{aligned} \]

括号里一坨可以预处理,但这个是「狄利克雷卷幂」,不太好搞。考虑直接枚举因数 -> 枚举倍数线对算。斐列逆元要预处理,不然多一个 log。询问整除分块。复杂度 \(\mathrm O(n\log{}+q\sqrt n\log)\)

21. P2303 - Longge 的问题

\[\begin{aligned} ans&=\sum_{j\mid n}j\sum_{i=1}^n[\gcd(i,n)=j]\\ &=\sum_{j\mid n}j\varphi\!\left(\dfrac nj\right) \end{aligned} \]

甚至不需要反演。。。。

直接枚举因数暴力分解质因数算 phi 的话,是 \(\mathrm O(\mathrm d(n)\sqrt n)\)。看上去因数个数上界是根号,但是根据 SF 主页的表格,1e9 的时候 d 大概是 1e3 左右。然后分解质因数本身就跑不满,对 \(\dfrac nj\) 更是跑不满。所以是能过去的。

但是又很容易快速求 phi。只需要把 \(n\) 分解质因数之后,dfs 出来所有因数,一路维护值和 phi 就做到了 \(\mathrm O(\sqrt n)\)

22. UVA10214 - Trees in a Wood.

\[ans=4\sum_{i=1}^n\sum_{j=1}^m[i\perp j]+4 \]

\(n\neq m\),不能欧拉函数了,只能老老实实反演。那些递归的做法都是野鸡吧(小声)

\[\sum_k\mu(k)\!\left\lfloor\dfrac nk\right\rfloor\!\left\lfloor\dfrac mk\right\rfloor \]

整除分块、线筛走起。

23. P4318 - 完全平方数

先二分答案,然后相当于要求 \(\mu^2\) 的前缀和。

min25 筛不知道能不能过,杜教筛又找不到合适的 \(g\)。求这玩意有个很巧妙的方法。

考虑数论意义,就是 \(n\) 以内不是完全平方数的倍数的数量。正难则反,考虑求是的的数量。考虑对 \(1^2,2^2,\cdots,n^2\) 的倍数容斥?这样做不了。不难发现对所有质数容斥,正确性是存在的。并且满足:\(2^{pcnt}-1\) 个质数集合的元素积互不相同。不难发现,除 \(1\) 外,每个数的贡献为 \(-\mu(x)\left\lfloor\dfrac n{x^2}\right\rfloor\)。那么 \(\mu^2\) 的前缀和就是 \(n-\sum\limits_{i=2}^n-\mu(i)\left\lfloor\dfrac n{i^2}\right\rfloor\),恰好等于 \(\sum\limits_{i=1}^n\mu(i)\left\lfloor\dfrac n{i^2}\right\rfloor\)。只需要枚举到 \(\sqrt n\),复杂度根号。

\(\mu^2\) 前缀和这玩意算是个定式,记住就行了。另外这是个 \(\mu\) 作为容斥系数的例子。

24. UVA11424 - GCD - Extreme (I)

。。。。。。又是个裸题

考虑算 \(1\sim n,1\sim n\),然后减去 \(1\sim n\) 的和然后减半。

\[ans=\sum_{i=1}^n\sum_{j=1}^n\gcd(i,j)=\sum_kk\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=k] \]

注意到后面上界相等,直接欧拉 phi。

\[ans=\sum_kk\!\left(2\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}\varphi(i)-1\right) \]

然后线筛出 phi 前缀和,整除分块,做完了。

25. spoj VLATTICE - Visible Lattice Points

三维的依然可做。把坐标含 0 的处理掉,就是 3 加上二维情况的 3 倍。一个数整除三个数的 gcd 依然可以等价转化为这个数整除这三个数。

\[ans=\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^n[\gcd(i,j,k)=1]=\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^n\sum_{o\mid i,\mid j,o\mid k}\mu(o) \]

移到前面来后面就是个整除的三方,整除分块板子。

26. P4449 - 于神之怒加强版

。。。。。。。。。。。又是个板板。。。。。。。。。

\[\begin{aligned} ans&=\sum_oo^k\sum_p\mu(p)\left\lfloor\dfrac n{op}\right\rfloor\left\lfloor\dfrac m{op}\right\rfloor\\ &=\sum_q\left\lfloor\dfrac nq\right\rfloor\left\lfloor\dfrac mq\right\rfloor(\mathrm{id}_k*\mu)(q) \end{aligned} \]

那个狄卷线筛,然后整除分块。

27. P6810 - 「MCOI-02」Convex Hull 凸包

莫反部分太套路了,做烂掉了都。放个最终结果:

\[\sum_qf(q)g(q)(\mathrm d*\mu)(q) \]

其中 \(f\)\(\mathrm d\)\(n\) 为上界的狄利克雷后缀和,\(g\)\(m\)。狄卷线筛,前两者 \(\mathrm O(n\log\log n)\)

狄利克雷后缀和的写法与前缀和稍有本质不同,后缀和是枚举乘以质数不越界的数(前缀和是枚举质数的倍数),而且从大到小(前缀和是从小到大)。

28. P6860 - 象棋与马

题解

29. P1390 - 公约数的和

裸题没意思 于神那题改一下就过了

30. P3172 - 选数

这题还挺有意思,不是正常的反演。

\[ans=\sum_a[\gcd(a)=k] \]

枚举的 \(a\) 是选择的序列。考虑令 \(L\gets\left\lceil\dfrac Lk\right\rceil,H\gets\left\lfloor\dfrac Hk\right\rfloor\),就把 gcd 变成了 1。

\[ans=\sum_a[\gcd(a)=1] \]

反演。

\[\begin{aligned}ans&=\sum_a\sum_{o\mid a_i}\mu(o)\\&=\sum_o\mu(o)\!\left(\left\lfloor\dfrac Ro\right\rfloor-\left\lfloor\dfrac{L-1}o\right\rfloor\right)^n\end{aligned} \]

然后整除分块就可以做了。需要杜教筛,注意到这是个二维整除分块,于是要对 \(R,L-1\) 的所有关键点都求出前缀和。这个只需要跑两遍杜教筛然后存起来即可。

31. P1447 - 能量采集

容易发现,对 \((x,y)\)\(k=\gcd(x,y)-1\)

于是

\[ans=\sum_{i=1}^n\sum_{j=1}^m(2\gcd(i,j)-1) \]

那不就是裸题了吗?

32. P1587 - 循环之美

提供一种较简洁的做法。

根据小学奥数,\(\dfrac xy\) 符合要求当且仅当 \(x\perp y,y\perp k\)

\[ans=\sum_{i=1}^n\sum_{j=1}^m[i\perp j][j\perp K] \]

该提前的贡献都提前了:

\[ans=\sum_{i=1}^m[i\perp K]\sum_{j=1}^n[i\perp j] \]

两个互质,都可以莫反展开。但是对这种一看就不是套路的莫反题,还是小心为妙,直接莽会死的。考虑先把后面的展开(事实上通往了正确的道路)。

\[\begin{aligned}ans&=\sum_{i=1}^m[i\perp K]\sum_{k\mid i}\mu(k)\!\left\lfloor\dfrac nk\right\rfloor\\&=\sum_k\mu(k)\!\left\lfloor\dfrac nk\right\rfloor\![k\perp K]\sum_{i=1}^{\left\lfloor\frac mk\right\rfloor}[i\perp K] \end{aligned} \]

到这里已经可以做了。考虑对 \(k\) 整除分块,那么最后一个 \(\sum\) 就是关于 \(K\) 的互质函数的前缀和,是有周期性的,预处理前 \(k\) 项即可 \(\mathrm O(1)\) 回答前缀和。现在要求的是 \(\mu\) 乘以互质函数的前缀和。幸运的是,互质函数是完全积性的(那为什么你一开始不 \([i\perp j][i\perp K]=[i\perp jK]\) 呢?因为一般函数里套乘积的式子都很不好搞。。。gcd 可以枚举然后莫反,乘积呢?),于是 \((\mu\times P)*P=\epsilon\)(其中 \(P\) 是互质函数)即可杜教筛(对 \(n,m\) 各算一遍)。复杂度 \(\mathrm O\!\left(n^{\frac 23}+K\log K\right)\)

33. spoj DIVCNT2 - Counting Divisors (square)

要求的是 \(\mathrm d\circ \mathrm{id}_2\) 的前缀和。这是个积性函数,min25 筛不知道能不能卡过。我们尝试用杜教筛。

用一下 \(\mathrm d(xy)\) 的展开式,推一波式子:

\[\begin{aligned}ans&=\sum_{i=1}^n\sum_{j\mid i}\sum_{k\mid i}[j\perp k]\\ &=\sum_j\sum_k[j\perp k]\!\left\lfloor\dfrac n{jk}\right\rfloor \end{aligned} \]

(上一步是因为 \(j\perp k\),这就很高兴,\(\mathrm{lcm}(j,k)=jk\)

整除分块。

\[ans=\sum_o\left\lfloor\dfrac no\right\rfloor\sum_{j\mid o}\left[j\perp \dfrac oj\right] \]

我们考虑后面那坨 \(\sum\) 的数论意义。对 \(o\) 的每种质因数,要么全部给 \(j\),要么全部给 \(\dfrac oj\),所以它就等于 \(2^\omega(o)\)

这个帖子里的渐变色大佬让我意识到了 \(\sum\limits_{i}\left\lfloor\dfrac ni\right\rfloor\!f(i)\) 其实就是 \(f*1\) 的前缀和。那么 \(ans\) 其实就是 \(2^\omega*1\) 的前缀和。前缀和相等则原函数相等,于是得到一个狄卷恒等式 \(\mathrm d\circ\mathrm{id}_2=2^\omega*1\)(虽说知道这个并没什么用,毕竟已经推出来了前缀和的转化式……)。

于是只要 \(2^\omega\) 可杜教筛,则 \(\mathrm d\circ\mathrm{id}_2\) 也可杜教筛(对于这题只要求 \(n\) 处的前缀和,而这个函数又是处于狄卷式的 LHS,于是只要根号时间复杂度)。而有一个比较众所周知的狄卷恒等式 \(\mu^2*1=2^\omega\),而 \(\mu^2\) 是可杜教筛的(只需要用容斥式 \(\sum\limits_{i=1}^n\mu^2(i)=\sum\limits_i\mu(i)\!\left\lfloor\dfrac n{i^2}\right\rfloor\),这虽然不是杜教筛,但是时间复杂度分析是和杜教筛一样的),于是这题就做完了。

预处理前 \(n^{\frac 23}\) 的话复杂度是 \(\mathrm O\!\left(n^{\frac 23}\right)\) 的。比较卡常,需要尽可能节省空间,把线筛数组逼近 1e8。

34. P5518 - 幽灵乐团 / 莫比乌斯反演基础练习题

恶心题。题解

35. P2714 - 四元组统计

水题啊。

\[ans=\sum_{i,j,k,o}\sum_{p\mid a_i,a_j,a_k,a_o}\mu(p)=\sum_p\mu(p)\dbinom{f(p)}4 \]

其中 \(f(p)\)\(a\)\(p\) 的倍数个数。那就调和级数预处理一下即可了。

36. loj6686 - Stupid GCD

nb tea, tj

37. CF585E - Present for Vitalik the Philatelist

比较水的一个 jxdzy。*2900 可能是恶评出来的?

就 so-called 容斥一下,变成 \(\gcd(S)\perp x\) 的数量减去 \(\gcd(S)=1\) 的数量。(\(x\in S\) 的情况不可能,所以不必考虑)

推柿子比较水就直接写一下了:前者是 \(\sum\limits_i\mu(i)\left(2^{f(i)}-1\right)f(i)\),后者是 \(n\sum\limits_i\mu(i)\left(2^{f(i)}-1\right)\)\(f(i)\) 表示 \(i\) 的倍数数量,这个就狄后一下就可以了,复杂度 \(\mathrm O\!\left(10^7\log\log10^7\right)\)

线代方向

高斯消元

1. P2455 - 线性方程组

跟高消的模板基本一样,还是方程数等于变量数,没劲。

只是区分了无解和无限解。那就先判断最后一列有没有主元位置,然后判断有没有自由变量。

2. P4035 - 球形空间产生器

之前用模拟退火做的。

\(n+1\) 个点到圆心距离相等,那就对相邻对列出 \(n\) 个方程。

考虑前一个点坐标为 \(a\),后一个点坐标为 \(b\),可列出方程 \(\sum(a_i-x_i)^2=\sum(b_i-x_i)^2\)。二次项都消掉了,剩下来移个项转化为线性方程的标准形式即可。\(n\) 个方程 \(n\) 个变量,直接消元。题目保证有解,也就是不需要担心出现无解或无穷解的情况。

3. P2011 - 计算电压

基尔霍夫方程组。。。。。被迫接受物理知识(其实跟网络流的流量守恒基本一样?)

首先一个事实是,设 \(f(a,b)\) 表示 \(a,b\) 间的电压,则 \(f(a,b)+f(b,c)=f(a,c)\)。于是有 \(f(a,0)-f(b,0)=f(a,b)\),所以只需要知道每个点到负极的电压即可算出两两之间的电压。

基尔霍夫方程组是说每个点处各路流来的电流的代数和等于 \(0\)。那么对每个点可以列方程,根据欧姆定律 \(I=\dfrac UR\),每条邻边 \((u,v)\) 贡献是 \(\dfrac{x_u-x_v}R\),那总贡献显然是变量们的线性组合,就可以列方程了。特殊地,对正极只需要列 \(x=c\) 形式的方程。

\(n\) 个变量 \(n\) 个方程,应该是必定唯一解的,毕竟这是物理事实,虽然我不知道怎么证。

4. P3164 - 和谐矩阵

为什么题能出成这样……一开始还以为有什么比较妙的构造方法。

那就对 \(nm\) 个变量列 \(nm\) 个方程暴力解咯,是模 \(2\) 意义下的。然后这里 \(nm\) 是 1e3 级别,就引出了一个 bitset 优化模 \(2\) 意义下的高消的 trick。

就拿 bitset 当行呗,初等行变换就 \(/w\) 了,再合理利用 _Find_first() 函数,复杂度就是 \(\mathrm O\!\left(\dfrac{(nm)^3}w\right)\)

这玩意可能会有自由变量,那就令自由变量等于 \(1\),这样一定能满足要求。

5. CF832E - Vasya and Shifts

就列方程啊。这个 shift 的程度是模 \(5\) 的,然后变量的取值也正好是 \([0,4]\),这使得变量每种取值对应的情况数都是一样的。这数据就是给你凑好的。

然后如果每个询问都跑一遍高消是四方的。注意到每次系数矩阵一样,可以类似求逆矩阵的原理那样的 trick,把所有增广向量放一起,一起消元,然后对每个询问分别搞,这样每个询问就是平方的。

甚至最后一列都不需要处理,直接看有没有主元,有的话直接 exit。否则数主元个数,得到自由变量个数 \(c\),答案就是 \(5^c\)

6. CF193C - Hamming Distance

二分然后对每一位开个变量?抱歉,1e5。注意到每一位只有最多 \(2^4=16\) 种情况,对每种情况计数设成变量即可。然后发现,有一半是对称的,还剩 8 种,然后还有一种贡献全是 \(0\),还剩 7 种。列出一个 7 元 6 个方程的方程组。哟吼,大概率是行满秩的,那就只有一个自由变量,1e5 枚举即可。

但终究逃不掉浮点运算。幸亏最多就是 0.5 这样子,不用考虑精度误差。然后枚举自由变量的时候怎么 chk 呢,就其它变量必须都是非负整数。然后取 min 输出就可以了。

7. P2447 - 外星千足虫

洛谷里的题解

8. P3265 - 装备购买

最大权基问题。里面的 T2。

9. P4570 - 元素

就是上一题实数高消变成 \(\bmod 2\)​​ 高消,直接抄代码了,bitset 不想用。不知道这种题为什么会有线性基标签,我也不懂题解区里面一些人非要用线性基强行解释这题有什么好处。

10. P4301 - 新Nim游戏

传统 nim 游戏结论是 \(\bigoplus\limits_i a_i=0\) 则先手 lose,否则 win。现在如果先手拿完若干堆,剩下来这些堆如果有任何一个非空子集异或和为 0 的话,后手就可以拿这个子集的补集,他就赢了;如果没有异或和为 0 的非空子集,那先手就赢了。所以先手的目的就是拿掉一些使得剩下没有异或和为 0 的非空子集。

这其实就是说线性无关。那肯定要只剩下一组基,因为它们是极大线性无关组。要想拿的最少,剩下的基就要越大,那就归约成了 P4570,直接抄代码。

11. CF1466F - Euclid's nightmare

当年我不会做这个绿题……不过 tbh 我感觉这题绿偏低了吧,蓝差不多?

模 2 意义下,每个向量最多两个 1 元素,求字典序最小的基。这不就是高斯消元干的事情吗?考虑用特殊性模拟高斯消元。

首先我们只需要求阶梯型,不需要简化。那从左往右枚举列,如果有两个元素(一个元素更弱,不讨论),就选上面那个元素所在行跟第一行交换,然后下面那个元素所在行异或上该行,然后扔掉第一行(以后不再考虑)。5e5 不能 bitset。可以注意到,向量时刻保持最多有两个 1 元素,因为只可能减少(1 异或 1),不可能增加(会把最上面一列删掉)。那其实可以记录现在的每行是原来哪些行的异或(显然原来的每行只会出现一次),然后对对换变换再记录置换结果。到每一列要实时查询某个原来的行现在跑哪儿去了,如果重合了相当于没有了,变成 0。这个确实可以用上述思路维护。

但是要交换很麻烦啊。其实这个高消不一定要搞成阶梯型,不做对换变换效果是一样的。那就不用记录置换了,并查集维护一下就行了。

(自己也看不懂。这题只可意会不可言传)

12. TC13145 - PerfectSquare

就对每行、每列、每个质因子列异或方程组,然后数自由变量个数,比较简单。但是稍微有一点点的难写。

13. TC8744 - KnightsOut

题解

线性基

1. P3857 - 彩灯

话说 Peter 不就是我吗,那为啥我没有女朋友呢?

每个灯就是个 01 串,求生成空间大小。求出线性基 \(\mathcal B\),那么线性基的每个线性组合显然都不同,答案就是 \(2^{\mathcal B}\)

2. P4839 - P哥的桶

这个 P 哥不会又是 Peter 吧(

线段树维护区间线性基经典题。

合并两个线性基是 2log 的,朴素实现的话查询修改都是 3log 的,而这题数据范围 5e4,我们不得不使用一些卡常技巧。

首先我上一题以及模板题的线性基写法都是复杂度跑满的,而有一个更快的更好写的跑不满的版本,比我自己 yy 的 nb 多了。这个会在学习笔记里体现出来。

然后修改的话,由于是单点修改,并不需要每次都上传,而一路 insert 下来就可以了,这样是 2log 的。虽说这对总复杂度没有影响,因为查询不可能低于 3log,但是至少能减少一些常数。

对于查询,可以不每次返回一个 node,这样每次都要赋值结构体,比较慢。可以统一合并到外面的一个专门存储答案的线性基,这样不用受赋值之苦。

这样能不开 O2 跑过。

3. CF959F - Mahmoud and Ehab and yet another xor task

这名字真长

就是对某个前缀解个矩阵方程,如果有解的话那答案就是 2 的自由变量个数次方,也就是 \(2^{x-\mathrm{rank}}\)。这个 rank 也就是线性基的大小,可以预处理前缀线性基,每插入一个是 log 的,所以复杂度线对。

那如何判断是否有解呢?按高斯消元的思路考虑的话几乎是不可实现的,因为预处理出来的线性基是把原矩阵转置过来消元的。实际上线性基已经高度概括了生成空间的情况,直接按线性基判断是否有解即可。那容易从高位往低位贪心判断是否有解,因为线性基其实是阶梯型矩阵(但并不是简化阶梯型,不过无妨),这个阶梯型的性质使得线性基能使用按位贪心做很多事。

4. CF895C - Square Subsets

每个质因子数量要是偶数。那就是一个异或方程组的解的数量。先预处理出来每个数对应的 bitmask 也就是 01 向量,那么求出线性基大小,用 \(n\) 减一下得到零空间维数,答案就是 \(2^{\dim\mathrm{Nul}}-1\)

5. P3292 - 幸运数字

这是个树上 st 表的小 trick。首先树上倍增会吧,预处理和查询都是 3log 的。但是线性基合并这东西可重复,那就像 st 表那样,先搞出 lca,然后在两条直链上查询,直链上就可以像 st 表一样查 \(\mathrm O(1)\) 次,所以单次询问复杂度 2log。所以这个 \(n\)\(q\) 差 10 倍是有道理的。

我不会告诉你我一开始心里想的是 st 表写成了倍增还没发现结果还 A 了

6. P4869 - albus就是要第一个出场

设线性基为 \(\mathcal B\),那么自由变量有 \(n-|\mathcal B|\) 个,所以对生成空间内每个值都会出现 \(2^{n-|\mathcal B|}\) 次。现在只需要算生成空间中小于 \(m\) 的有多少种数。这个可以使用数位 dp 统计答案的方法。现在需要考虑的是,生成空间中前缀等于某个确定的 01 串的有多少。那么主元位置在这个前缀以内的基的选与不选显然是被确定了的,后面的随便选还是不选反正影响不到前面(因为线性基是阶梯型!)。那就把 \(2\)​ 的后面的基的个数次方累加起来即可。

7. P5556 - 圣剑护符

就是求树上一段是否线性相关。维数只有 30,如果链上点的个数大于 30 那直接肯定线性相关了。不超过 30 的话直接暴力就好了。

lca 写一下,暴力的时候暴力往上跳,链修的话树上差分的一下用 bit 维护。复杂度瓶颈还是在线性基,30 个数 30 维,一次询问是 2log 的。最后判是否满秩。

8. P5607 - 无力回天 NOI2017

nb 的 Ynoi。

首先这个东西不弱于单点修改区间查询,那个是 3log 的,所以这个也不可能低于 3log,5e4 的范围给的很对,应当就是 3log。

懒标记不太会打。考虑差分转化。那么某个位置的真实值其实就是前缀和。考虑一个区间内的前缀和的线性基长什么样。如果选偶数个前缀和的话,那就是 \([1,l]\) 全部抵消,\([l+1,r]\) 内若干个区间,其实就是任意子集都可以。如果选奇数个的话,前面剩个 \([1,l]\) 全选,\([l+1,r]\) 依然随便选。所以这个线性基其实就是 \(l\) 处真实值以及 \((l,r]\) 内差分值的线性基。那就归约成了单点修改区间查询了。

注意这里单点修改是真的修改了,不是插入了,只能一路 sprup,不能像 P 哥那题 2log 了。但不影响啦,总复杂度本来就是 3log。为了维护真实值,还需要用个 bit 稍微维护一下。(Yn 题竟然不开 O2 能过)

9. CF587E - Duff as a Queen

和上面的 Yn 基本一样。不过这题 \(n,q\) 不同阶,\(n\)​ 大一点,刚好符合预处理 2log 操作 3log。

10. P4151 - 最大XOR和路径 / CF845G - Shortest Path Problem?

经典的图上路径异或问题(?)

写的太长了,看洛谷题解吧。

11. CF724G - Xor-matic Number of the Graph

还算有那么点意思。

考虑固定 \(u,v\)​​ 答案是啥(首先 \(u,v\)​​ 要连通)。做过 P4151 就知道,异或值集合是 dfs 树上所有返祖边对应的简单环的边权异或和生成的空间,再平移上 \(u\to v\)​​ 树上路径的边权异或和。考虑按位拆开,考虑这个集合中每一位有多少个为 1。如果是第一位,那么线性基的后面对应位置对第一位都没有影响,就看第一位有没有东西了。如果有,那么可以选或不选,有 1/2 的元素该位为 1​​(不论平移量是啥);如果没有,那么生成空间里没有该位为 1 的,这时候就看平移量这一维是啥,如果是 0 就没有,如果是 1 就全部。再看不是第一位的:那其实这些位没有高低贵贱之分,可以把它的地位跟第一位交换一下重构线性基。但其实不需要真的重构,因为我们只需要知道那一位对应位置上有没有东西,这很简单,如果线性基中所有元素的那一位都是 0 那就废了,否则肯定有。

由于 \(u,v\) 要连通,所以不同连通块内是独立的。考虑一个连通块内,求出 dfs 树上的各点前缀异或和 \(xsm\),那么平移量就是 \(xsm_u\oplus xsm_v\)。还是按位统计:对每个平移量,它对应的线性空间的线性基都是一样的,所以可以统一处理。如果这一位有,那贡献就是位权乘上 2 的线性基大小次方的 1/2 再乘以连通块大小选 2;如果没有,那就只能选 \(xsm_u,xsm_v\) 这一位一个 0 一个 1 的 \((u,v)\)​​​ 对,数一下乘一下即可,注意不用乘以 1/2 了。

12. CF1100F - Ivan and Burgers

正解貌似是前缀线性基,但我不喜欢那玩意,不想学。自己 yy 了一个复杂度相同的做法,但是不能在线。题解

13. CF251D - Two Sets

orz tzc 去年就 dd 我了。题解

14. loj114 - k 大异或和

求出线性基后逐位决定。如果当前位决定不了就不管,易证正确性。比较坑的地方是他要求非空子集,不满秩的时候没什么事,满秩的话 \(0\) 表示不出来,需要询问 \(x+1\)

矩阵树定理

1. spoj HIGH - Highways

板子题。没有模数,可以用实数高消。但是为了避免精度误差,也可以辗转相除消元。

2. P4111 - 小 Z 的房间

近乎板子题。相邻的格子连边,* 不算点,套板子即可。这次真的是模合数了,必须要辗转相除了。

3. P4455 - 社交网络

又是板到不能再板的题。求的是外向树,所以度数矩阵是入度矩阵。

4. P4336 - 黑暗前的幻想乡

看到 \(n\leq 17\) 没反应过来,以为是 \(\mathrm O\!\left(n^6\right)\) 的复杂度(因为高消一般数据给 300 左右对吧),然后怎么想也没想出来,就看题解去了。

实际上这个「每个条件都要满足」如果不好做的话,那其实是个正难则反 + 容斥的套路,这样一般「同时禁止满足若干个条件」比较好做,那就用「不禁止任何条件」减去「禁止至少一个条件」,后者用容斥做。复杂度 \(\mathrm O\!\left(2^{n-1}(n-1)^3\right)\),2e8 左右,但还是不开 O2 过了。

5. P3317 - 重建

\(tot=\prod\limits_{e\in E}(1-p_e)\),则生成树 \(T\) 的贡献显然是 \(tot\prod\limits_{i=1}^{n-1}\dfrac{p_{T_i}}{1-p_{T_i}}\),直接套矩阵树模板就好了。

但是如果有 \(p_i=1\) 这个式子就挂了。这个问题比较棘手,一个方法是令这些 \(p_i=1-\epsilon\)。这难免面临一些精度问题,一方面要让含 \(0\) 的生成树确实为 \(0\)\(\epsilon\)​ 要设地比较小,至少小于 1e-4 吧;一方面不能丢失精度,所以要尽量大。最后设 1e-7 过掉了。

6. uoj75 - 智商锁

immortal tea %%%%%%%%%%%%%

7. P6624 - 作业题

tijie1

字符串方向

SA

reference,o,orz ymx,永远真包含我。
CF666E P5284 P4770

1. P2408 - 不同子串个数

答案是 \(\dfrac {n(n+1)}2-\sum hi_i\)

2. CF432D - Prefixes and Suffixes

从这个开始下面四个都是 lyp 的例题。

傻逼 D2D,傻逼 *2000。

第一步判哪些前缀等于等长后缀不用说了吧。如何算每个前缀出现了多少遍?我们考虑用左端点来贡献,显然贡献了一个 \(1\sim x\),这个 \(x=z_l\)。然后差分一下即可。

本来是个傻逼 Z 或者 KMP 的,非得用 SA 写(tie ban zi)。那就是 \(rk_1\sim ?\) 或者 \(?\sim rk_1\)\(hi\) 的 RMQ,预处理一下即可,就往两端扩展。

3. CF123D - String

这个题感觉还算有点意思。是个远古场的 2300 D1D,感觉无论如何现在评的话也得有 2400 吧。

看到关于要区分本质不同字符串的,想到 SA,在排好序的后缀数组上遍历搞事情。

我们考虑枚举贡献子串的长度。那么显然 \(hi\) 数组上连续 \(\geq len\) 表示连续一段相等的这么长的子串。于是可以在每个位置将它放进要割裂这个位置的那个 vector 里,从小到大枚举 \(len\),用一个 set 维护相等段同时维护总贡献即可。注意把 \(n-len+2\sim n\) 这些无效的减掉。

4. poj3581 - Sequence

老 PKU 的 OJ,我输给你了。

本来是个很有意思的题。考虑先把它整个倒过来,那么最终答案的形式就是,把一个后缀移到前面去,然后把原来的一个前缀移到后面去。

我们考虑按字典序贪心,先看将要移到前面的后缀。显然希望它最小,而又有 \(a_1\) 的存在,不需要考虑严格非严格的问题。于是后缀排序一下。然后考虑剩下来的一段,就是要从中间砍一刀,然后交换左右。这个的话可以将数组倍长,然后后缀排序,因为 SA 数组满足后缀们的等长前缀也是有序的。值得一提的是,这题竟然只要后缀排序,没有要 \(hi\) 数组。

本来可以愉愉快快地 AC 的,但奈何这 poj 题意不清,没说每段非空。改了之后还是 WA 了好久,后来发现是我改变了 \(n\),但是值域大小并未因此改变。已列入 sb-mistake list。

5. poj2217 - Secretary

芜湖,lyp 的例题终于做完了。

我们考虑让两个串的每个后缀贡献一下,答案就是 \(\max\max lcp(i,j)\)。但这样显然是平方的。

我们考虑后缀数组,将它们两个串用 \(\texttt!\) 连起来。那么不难得出结论,答案就是相邻异串的 \(hi\)\(\max\)


哎,不会 DS 什么都做不了………………过几天开始把 DS 好好补一下……


6. CF204E - Little Elephant and Strings

wdnmd hb 又给了两个神仙题。这个还可以看题解勉强做做,另一个就太神仙了………………

题解

7. P2743 - 乐曲主题

又是 usaco 远古题库……

注意到转调后相同当且仅当该段差分数组相同。于是搞出差分数组,然后 SA,然后枚举后缀随便算算就可以了。

但我 WA 了一个点,差点以为是我的 SA 板子锅了。实际上也确实是板子锅了,因为这题里面值是可能等于 0 的,与 \(a_0\) 相等了,会出 bug。已加入 sb-mistake list。

8. P2852 - Milk Patterns

SA 求出来之后显然的滑动窗口。

9. P5546 - 公共串

还是一个水题。

SA 该如何应对多个串的情况呢?拿两两不同的分隔符连起来,然后枚举,再往左和右各扫一遍求 min 的 max。复杂度平方。

10. UVA11107 - Life Forms

比上一题强一万倍……

考虑将第二层枚举改成二分,这样复杂度就是 \(\mathrm O(n^2m\log)\) 的,时限 6.67s(我也不知道为啥这么奇怪),能过去。然后最后统计答案的复杂度我也不会证,反正 A 了就是了,我也不想管那么多了,做 UVA 题完全没了心情。

空行少打或者多打都要 WA……

11. P4248 - 差异

虽说这可能是一个弱省省选的签到题,而且年代也比较久远,但能独立 A 掉一个省选题还是真心爽的(

就是求 \(\sum lcp(i,j)\)

我们考虑从左往右枚举 \(j\),然后就是求对于每个 \(i\) 的 RMQ 的和。考虑用线段树维护,每次更新就找到第一个 \(\geq h_j\) 的地方(线段树二分即可),然后区间赋值。查询求和即可。复杂度线对。

但是 ymx 总有比我复杂度优的算法。我们可以让 \(hi_i\) 来贡献,那么单调栈可以轻松解决。

还需要判断 \(n=1\) 的情况。搞得我差点以为评测机又随机 RE 了(

12. P3181 - 找相同字符

跟上一题几乎一样……

考虑把两个串接起来之后,求那些来自两个不同串的后缀的 lcp 和。直接维护也是可以的,只不过有些麻烦,线段树或者单调栈都可以。但是有一个抄上一题代码的方法():so-called 容斥一下,CV 三遍就可以了(

13. P2463 - Sandy的卡片

ymx:有什么意义吗……

我觉得挺有意义的,因为这题用我之前的几个类似的题的水算法过不去,必须写正解(

我们可以二分。二分之后的 check 就显得异常简单了,搞出若干个极大区间,看里面是否包含来自所有串的后缀。这个用 set 的话是 2log 的,用桶可以 1log。

有的时候二分真的很优秀啊……而且这还是一个有二分典型标志的——最小值最大,我怎么也想不到呢………………

然后这题和 204E 有类似之处,也可以 two-pointers 找出所有包含所有串的区间,然后单调队列判断。

14. P4051 - 字符加密

倍长之后求 SA,那么很容易证明长度为 \(n\) 的子串也是排好序的了。那就从小往大输出一下就可以了,好蠢啊/yun

15. P6640 - 封印

又成功想出了最劣解/cy

好久没碰 SA 了,都忘光了。不过 u1s1 作为一个 BJOI 的加试题这题也够一眼的。

先用 SA 求出 \(s\) 的每处与 \(t\) 的 LCP(只需要分隔符相连,然后对 \(s\) 的每处在 SA 里找左右最近的 \(t\) 的后缀然后 RMinQ 即可(我一开始把 LCP Lemma 背成 RMaxQ 了(捂脸))),然后对每个询问就是求 \(\max\limits_{i=l}^r\{\min(r-i+1,lcp_i)\}\)。考虑把 \(\min\) 拆开两个部分分别维护。不难想到按 \(r\) 排序后,每个点只会从前一类变到后一类一次。于是对两类各维护一个线段树,该变的时候就变(前一类还要持续 +1),然后区间查询取 max。常数巨大。

16. P6793 - 字符串

一个串改成另一个串的代价显然就是 \(k\) 减去 lcp。那么我们就是要找到一个最大权完美匹配,边权为 lcp。

这是个完全二分图。我们考虑贪心(可能就是传说中的模拟费用流吧),每次选最大的一条边,然后删点,根据 dinic 的过程不难分析出是对的。于是不难想到用堆维护每个 \(A\) 中元素的最大连出边,每次找最大的,然后把删除之后影响到的常数个点改一下。

那么若两个后缀的 lcp 为 \(x\),那么真正的 lcp 是 \(\min(k,x)\),完全需要最大化 \(x\)。这就搞出 SA 然后 ST 表即可(感觉 SA 越来越成为求 lcp 的工具人了)。那就找两边最靠近的 \(B\) 中的点,这个可以 BIT 倍增轻松搞(还要支持添加删除)。然后删除的时候只会影响到最多两个。

轻微卡常,把一些作死的写法改掉之后开 O2 A 了。

感觉 SA 不贴板子的话还真是挺难写,我已经决定试机的时候就写它了(

17. P5028 - Annihilate

思路比较裸。就连接一下求个 SA,然后对于每个位置,对于每个串,求最接近的属于该串的位置更新一下答案即可。ST 表的话复杂度是 \(\mathrm O(n\sum len)\)

但是内存不允许 ST 表……但是依然容易想到解决方法,毕竟是简单 ds 题。就从前往后跑的时候实时维护每个串最近到当前位置的 min,每个位置重置其串。反过来一样。

18. P7409 - SvT

这题就很水了。按 rk 排序,搞出相邻两点之间的 lcp,那么某两个的 lcp 就是 rmq。考虑统计每个位置作为最小值贡献多少次,为了去重,前大于等于,后大于,都是套路做烂掉了都。那么搞出最左端点和最右端点,单调栈轻松解决。

19. P6095 - 串分割

\(s=\left\lceil\dfrac nk\right\rceil\)。那么一定是有 \(cb\)\(s\)\(cl\)\(s-1\),可以解二元一次方程组解出来。

然后考虑枚举是哪个最大的 \(s\),这样一共有 \(n\) 个,枚举之后自动断成链了。考虑如何 chk 是否可行,想到一个贪心,从开头开始,优先看能否 \(s\),如果能就搞,否则 \(s-1\)。但这个贪心遇上字典序,正确性不太显然的样子。但如果去试图证明的话还是容易发现它是正确的的。证明贪心常用的思路是如果不这样是否不会更优。那我们考虑如果能 \(s\)\(s-1\) 了,得到一个方案,那么一定能把后面一个 \(s\) 变成 \(s-1\) 然后把当前改成 \(s\);而反过来就不保证行。

这样一次 chk 复杂度是 \(\mathrm O(k)\) 的,\(\mathrm O(nk)\) 不可接受。然后就是我想不到的了。如果二分这个最大的 \(s\) 的话,它是没有单调性的……然后就看了题解。事实上,答案 \(\leq x\) 这玩意是有单调性的,而 chk 照样 chk。于是二分 \(x\) 即可,又不难得出最终得到的答案一定是某个切实际的后缀(即长度够),那么直接输出即可。

但是这样并没有自动断成链,需要枚举开头来贪心,而开头是 \(\mathrm O(s)\) 个,于是一遍 chk 就是 \(\mathrm O(sk)=\mathrm O(n)\)。总复杂度线对。

20. P3649 - 回文串

回文子串问题经典

题解

21. P3804 - 【模板】后缀自动机 (SAM)

这是 SAM 板子题,但是 SAM 板子题怎么能用 SAM 做呢?

我们考虑 SA。显然答案就是后缀排序后,对所有 \(l<r\)\((r-l+1)\min\limits_{i=l+1}^r\{hi_i\}\) 的最大值。这是个点对问题,用 cdq 套 two-pointers 轻松线对解决。

22. P4081 - Standing Out from the Herd

这个 Pt 组的题也不是很难。

考虑像不同子串个数那样遍历 SA:对每个位置数既没被前面算到过,也不存在于其它串中的子串们。前者就一样做,后者就把所有串串起来搞 SA,然后找到最近的两个其它串的位置求 lcp,把这三者取个 max 就是要排除掉的。

状压方向

高维前缀和

reference
https://www.hackerrank.com/contests/countercode/challenges/subset/problem

1. CF449D - Jzzhu and Numbers

容斥 + 高维前缀和一般题(ymx 附体)

选的一个集合满足条件,显然当且仅当每一位都有人是 \(0\)。于是我们考虑每个数能够满足哪些位(就是取反)。

考虑反面考虑,求至少有一位没有被满足的方案数。这样就可以很舒服的容斥了。于是现在只要求对于任意 bitmask \(x\)\(x\) 内的位不被满足,其他位随便的方案数。这显然就是只有 \(x\) 的补 bitmask 的子集才能选,而且任意一种选的方案都是可以的,那么设 \(x\) 的补 bitmask 的子集中有 \(cnt\) 个给定的,那么它对答案的贡献就是 \(2^{cnt}\) 乘以容斥系数。

那么问题就成了求对于所有 bitmask,求出他的被给定的子集数。那这不就是个高维前缀和板子嘛?

code

2. CF76C - Mutation

题解

3. ARC100C - Or Plus Max

考虑对于每个 \(K\) 求出下标为 \(K\) 子集的最大 \(a_i+a_j\)。那么显然一个是最大值,一个是次大值,这个是高维前缀和(前缀最值、次值)可以轻松计算的。

但这样子是假的,因为 \(\leq K\) 不等于是 \(K\) 的子集。但很显然是 \(\leq K\) 的所有数的子集的并。于是按原先算法算出来答案后取个前缀 \(\max\) 即可。

4. CF1208F - Bits And Pieces

题解

5. CF165E - Compatible Numbers

这他妈的就属于超级无敌大水题了吧。。。。。

\(x\operatorname{and}y=0\) 当且仅当 \(y\) 包含于 \(x\) 的补。那就看 \(x\) 的补的子集里面有没有东西呗……这个甚至不需要高维前缀和,一个状压 DP 就搞定,转移的时候枚举缺失的位。为什么能这样呢?因为操作重复没关系,不像加法。

6. CF383E - Vowels

不是我说,这不是一个有手就行的高维前缀和板子题?不知为啥 2700 & 一堆复杂的还要容斥的题解?(装起来了装起来了)

考虑对于某个元音字母集合的答案是多少。考虑求反面,不包含元音字母的单词个数,这个就非常简单了。显然就是字符集合包含于当前元音字母集合的补集的单词个数。那就处理出所有单词的字符集,然后高维前缀和一下就可以了。

这样子复杂度是 \(\mathrm O\!\left(2^dd\right)\) 的。以 CF 机子是可以跑过的。但是这个集合们的大小最多为 \(3\),感觉一脸可以把 \(d\) 去掉的样子?但是逛了一圈发现没有。

7. CC COVERING - Covering Sets

题解。

8. CC AMR14F - Jersey Number

显然两个区间有公共子串等价于有相同字符。

两个区间有相同字符当且仅当什么啊。当且仅当它们的字符集有交啊……

那我们考虑算出每个 bitmask 的区间数,然后高维前缀和之后一通乱算。

那怎么搞出这个关于 bitmask 的 \(cnt\) 数组呢。直接平方枚举区间显然是不行的。考虑对于某个左端点,右端点上升的过程中 bitmask 只会变化 \(16\) 次,找出每个字符第一次出现排个序每段贡献一下即可。

就这?你管这叫 hard?hard?这不比上一题简单 & 套路多了?只不过难写一点。

讲个笑话,我翻到最下面看到 50000B 差点以为是 ML(

9. CC STR_FUNC - Strange Functions

感觉最近要做好多 CC 啊……


注意到这个题显然是强于求 \(f:f_i=a_i+\sum\limits_{j\subsetneq i}f_j\) 的。但我连这个都不会做/kk

这个东西很有分治 fft 内味,看上去不能静态。很自然的想到动态高维前缀和。昨天我说什么来着,动态高维前缀和不可做……但实际上是可做的(好香好香)。在学习笔记里更了。

那这题不就是板子题了吗。。动态维护 \(f_i^2\)\(g_i\) 的高维前缀和即可。

突然想放一下代码

10. CF772D - Varying Kibibits

这是一个神仙题。

这是一个非二进制高维前缀和 & 差分 与 二项式定理维护和的低次方的套路 的缝合怪。

题解

11. CC BEAUTY - Beautiful Sandwich

芜湖,最后一道 CC……

先考虑对每个连通块算贡献,以第一个位置为代表元。然后发现需要 ban 掉使得与前面连起来的 mask 们,有点麻烦。于是我们把平方拆成 \(1+3+5+\cdots\),摊到每个位置头上,这样就不用 ban 前面了。

剩下来就跟 76C 差不多了,基本抄的代码

btw,我发现我好像就没在 CC 上 WA 过?

posted @ 2020-12-09 18:22  ycx060617  阅读(1864)  评论(14编辑  收藏  举报