7月做题总结

算法(知识点):

二维前缀和
矩阵乘法:一个nm的矩阵乘一个mk的矩阵,得到一个n*k的矩阵,a[i][j]表示第一个矩阵的第i行乘第二个矩阵的第j列,对应位置相乘
矩阵快速幂:多个矩阵乘法用快速幂实现
模板:

struct matrix
{
	int f[105][105];
	matrix(){
		memset(f,0,sizeof(f));
	}
	matrix operator *(matrix &x){
		matrix ret;
		for(int i=0;i<=m+1;++i)
		    for(int j=0;j<=m+1;++j)
				for(int k=0;k<=m+1;++k)
				    ret.f[i][j]=(ret.f[i][j]+f[i][k]*x.f[k][j]%mod)%mod; 
		return ret;  	
	}
}ans,base;

矩阵乘法优化dp:答案不正确时要先检查一下构造的矩阵是否正确
期望dp:一般从后往前dp
状压dp枚举子集:注意枚举子集是指枚举一个集合+它的子集
模板代码:

for(int j=0;j<(1ll<<n);++j)
	for(int k=j;k;k=(k-1)&j)
	{

    }

时间复杂度O(3^n)
珂朵莉树:主要解决含区间推平操作的问题
模板代码(P8512):

struct node
{
	int l,r,t;
	mutable int v;
	bool operator<(const node &a) const{
		return l<a.l;
	}
};
set<node> s;
set<node>::iterator split(int pos)
{
	set<node>::iterator it=s.lower_bound((node){pos,0,0,0});
	if(it!=s.end()&&it->l==pos) return it;
	it--;
	if(it->r<pos) return s.end();
	int dl=it->l,dr=it->r,dt=it->t,dv=it->v;
	s.erase(it);
	s.insert((node){dl,pos-1,dt,dv});
	return s.insert((node){pos,dr,dt,dv}).first;
}
void assign(int l,int r,int t,int v)
{
	set<node>::iterator itr=split(r+1),itl=split(l);
	for(set<node>::iterator it=itl;it!=itr;++it)
	{
		//add(it->t,-((it->r)-(it->l)+1)*(it->v));
		it->v=v;
	}
	s.erase(itl,itr);
	s.insert((node){l,r,t,v});
	//add(t,(r-l+1)*v);
}

时间复杂度O(nlogn)
树上启发式合并:主要解决每个点都需要遍历其整棵子树(空间无法记录所有子树信息,每次需清空数组),做法是每次先遍历其所有轻儿子(要清空),再遍历重儿子(无需清空),再遍历轻儿子加入数组中,由于每个点最多被遍历O(logn)次(到根的链上轻边最多logn条),时间复杂度O(nlogn)
模板代码(P6623)

void dfs(int u)
{
	sz[u]=1;
	for(int i=0;i<v[u].size();++i)
	{
	 	int d=v[u][i];
		dis[d]=dis[u]+1;
		dfs(d);
		sz[u]+=sz[d];
		if(sz[d]>sz[son[u]])
			son[u]=d;	
	}  
}
void cal(int u,int p)
{
	for(int i=0;i<=20;++i)
		t[a[u]%pw[i]][i]+=p;
	for(int i=0;i<v[u].size();++i)
	    cal(v[u][i],p);
}
void getans(int u)
{
	for(int i=0;i<v[u].size();++i)
		ans[u]^=ans[v[u][i]];
	for(int i=0;i<=20;++i)
	    if(t[dis[u]%pw[i]][i]%2)
	        ans[u]^=(1ll<<i);
	ans[u]^=(a[u]-dis[u]);
}
void work(int u,int l)
{
	for(int i=0;i<v[u].size();++i)
		if(v[u][i]!=son[u])
			work(v[u][i],0);
	if(son[u]) work(son[u],1);	
	for(int i=0;i<v[u].size();++i)
		if(v[u][i]!=son[u])
			cal(v[u][i],1);	
	getans(u);
	if(!l)
	{
		for(int i=0;i<v[u].size();++i)	
			cal(v[u][i],-1);	
	}
	else
	{
		for(int i=0;i<=20;++i)		
			t[a[u]%pw[i]][i]++;    
	}
}

二进制分组
Boruvka:一般在完全图中使用,求最小生成树
模板代码(CF888G):

for(int i=1;i<=n;++i) fa[i]=i;
while(upd)
{
	upd=false;
	for(int i=1;i<=n;++i)
	{
		bes[i]=0;
		mn[i]=inf;
	}
	for(int i=1;i<=n;++i)
	{
		int x=getfa(i);
		pll p=query(x,a[i]);
		int y=getfa(p.first);
		if(x!=y)
		{
			if(p.second<mn[x]) 
			{
				mn[x]=p.second;
				bes[x]=y;
			}
		}
	}
	for(int i=1;i<=n;++i)
		if(bes[i]&&getfa(i)!=getfa(bes[i]))  
		{
			int x=getfa(i),y=getfa(bes[i]);
			ans+=mn[i];
			merge(rt[x],rt[y]);
			fa[y]=x;
			upd=true;
		}  
}

需要注意的地方:

1.遇到期望dp算概率的时候(如CF1265E),如果要除以100(或除以某个数),先算出100的逆元,直接将p[i]乘上100的逆元,方便后续处理,不易出错
2.遇到循环移位,可以先把1...n转化为0...n-1,再转化回来,更好计算

题目,技巧:

期望dp

AT_abc282_g [ABC282G] Similar Permutation

设f[i][j][k][l]表示前i个中,有j个满足条件,A的第i个排名第k名,B的第i个排名第l名
这种状态主要适用于只关心相对关系(最好是相邻的),不关心绝对大小的题目,再使用二维前缀和即可

CF1151F Sonya and Informatics

期望dp+矩阵乘法(矩阵加速),注意k很大且dp方程无需判断时,可以考虑矩阵乘法

CF1265E Beautiful Mirrors

期望dp,从后向前推,考虑下一步是什么就从那里转移过来
注意有后效性,把dp方程式整理成关于f[1]的一次函数,从后向前递推可以整理成如下形式:f[1]=a[1]*f[1]+b[1],移项即可算出答案

状压DP

CF1209E2 Rotate Columns (hard version)

状压dp,这种带操作的dp,一般枚举操作的那一维,比如这道题对列操作,可以枚举列,但这道题列的数量很大,但我们发现行的数量很少,且求的是每行的最大值,我们最多只要用12列,发现我们只需要用最大值最大的n列即可
证明:如果还要用另一列,那么显然可以把那一列用到的用前n列替换。
那么我们设w[i][j]表示第i列,当前列使用的状态为j时移位的最大值,然后对于每一列先算出w,再按照状压dp的模板合并之前的状态与当前状态即可

P8189 [USACO22FEB] Redistributing Gifts G

这个题我们看到有多组询问,考虑把奶牛按品种分成两组,每组之间交换,然后相乘,这样可以O(1)解决询问
于是我们要先计算的就是几只奶牛形成几个环,环之间交换的方案数
使用状压dp,设g[i]表示状态i代表的牛之间交换的方案数,这样我们就可以由g计算f了
发现g必然由多个环组成,设h[i][j][k]表示状态i的奶牛,以j为起点,当前点为k的方案数
在h的最后一个点判断一下能不能与起点相连,如果能相连就把状态的数量放到g里面
这样还是超时了,注意到是一个环,所以哪个点做起点都无所谓,钦定标号最小的点为起点即可省掉一维
注意在把f[i]分为f[i^j]和g[j]时,为避免重复,让j必须包含i中编号最大的点即可

最短路

P5304 [GXOI/GZOI2019] 旅行者

我们可以把k个点分成两组,第一组接超级起点,第二组接超级终点,跑最短路,接下来考虑分组,可以随机化分组,也可以使用确定性的二进制分组,因为两个点的二进制不同,所以一定有一次被分到不同组,注意每次分组要把两边分别当起点跑一次

P6961 [NEERC 2017] Journey from Petersburg to Moscow

我们想知道第k大边的长度,所以我们枚举每条边,考虑让它作第k大边,把其他边减去它的长度(小于它的变成0)加上k*它的长度,跑最短路即可,这样做是对的,考虑证明,设实际排名为p
1.p>k,则会多算k+1-p的贡献,不会更优
2.p<j,则会把<k的当成k,不会更优

P2371 [国家集训队] 墨墨的等式

同余最短路模板题

AT_arc084_b [ABC077D] Small Multiple

同余最短路,将路径长度改为0-9即可

最小生成树

CF888G Xor-MST

Brovuka算法+01trie,注意trie树可以合并和相减,合并与线段树合并类似,相减记录sz为子树大小即可

平衡树

P3369 【模板】普通平衡树

P3391 【模板】文艺平衡树

树套树

P3380 【模板】树套树

这个题写树状数组套线段树会好写很多,常数也很小,查询时只需要把所有段的根找到,然后判断是全部往左走还是全部往右走即可

P3157 [CQOI2011] 动态逆序对

非传统题(构造,交互)

CF2068A Condorcet Elections

这个题其实不难,考虑一个排列和把它反过来的排列都加进去其实对答案没有影响
那我们直接让要求的两个数在排列中的相对大小关系不变,剩下的数一个正着,一个倒着即可

AT_arc145_d [ARC145D] Non Arithmetic Progression Set

有一个结论,所有在三进制中不含二的数中一定没有等差数列
然后把这些数全部加一或减一也不会出现等差数列
那我们只需要构造出一组数的和与m在模n意义下同余即可
考虑先放入三进制中不含二且末位为0的数,这样可以任意地对它们的末位加一,所以就一定会有一组数满足我们想要的条件

CF1364E X-OR

很多交互题都与随机化有关
注意到排列中有0,我们可以先找到0的位置,然后对每个数和0或一下即可
那么我们考虑如何快速找到0的位置
先考虑如何快速确定p[x]
其实只需要把它和随机16个位置的数或一下,再把这些值与起来即可
因为如果这样求出来的一个位置是1,那么这些值中的这个位置都是1,有极大的概率原数的这个位置就是1
但是如果只是这样的话,把每个数都扫一遍肯定还是无法快速找到0
我们可以先求出第一个位置,然后扫描这个数组,如果p[i]|p[j]=p[i],说明p[j]在二进制下是p[i]的子集,把p[i]替换为p[j],这样最后保留的位置就是0

斜率优化

P4072 [SDOI2016] 征途

斜率优化模板题

posted @ 2026-08-18 12:54  sunhaoyu403  阅读(7)  评论(0)    收藏  举报
levels of contents