:D <csp2019考前对基础知识的整理>

基本算法

二分/三分法

  • 将求解转化为判定
  • 注意观察答案的单调性来考虑二分
  • 三分法只能用于严格单峰函数的求解极值

[模板]三分法

	while(r-l>=eps)
	{
		lmid=l+(r-l)/3,rmid=r-(r-l)/3;
		if(f(lmid)<f(rmid))l=lmid;
		else r=rmid;
	}

排序

  • 感觉只要会\(sort\)和归并排序就行.
  • 归并排序求逆序对
  • 当要合并两个序列使之有序且已经有一个/两个序列有序的情况下,考虑归并排序合并来优化.前一种情况中可以先\(sort\)无序的那个序列再归并两个序列.
  • \(sort\)的一个应用:离散化.这里讲几个点.
    • 当有给出的坐标和询问的坐标时,记得要放到一起离散化,而不是只离散化给出的坐标.
    • 有时候离散化不用\(unique\)函数会更加方便,如\([YNOI2016\)掉进兔子洞\(]\),这里是为了在\(bitset\)上更方便精准地划分区间.

[模板]归并排序求逆序对

il void sol(Ri l,Ri r)
{
	if(l==r)return;
	Ri mid=(l+r)>>1;
	sol(l,mid);sol(mid+1,r);
	Ri i=l,j=mid+1,ct=l-1;
	while(i<=mid && j<=r)
	{
		if(a[i]<=a[j])b[++ct]=a[i++]; //千万要注意这里是<=
		else as+=mid-i+1,b[++ct]=a[j++];//核心部分
	}
	while(i<=mid)b[++ct]=a[i++];
	while(j<=r)b[++ct]=a[j++];
	go(k,l,r)a[k]=b[k];
}

倍增

  • 求区间最值的\(ST\)算法可以做到\(O(nlog\ n)\)预处理\(O(1)\)询问
  • 考试\(10.27\ T3\)有一个很巧妙的对倍增的运用. \(link\).
    • 问题简述是一棵有边权的树,每次操作要求对一条路径上的所有的边权对给定的\(x\)\(min\).
    • 如果放在序列上,那么线段树可以简单维护.在树上,考虑设\(f_{i,j}\)表示\(i\)向上走\(2^j\)步经过的边都要对\(f_{i,j}\)\(min\).只需要在最后进行类似于线段树\(pushdown\)的操作就行.

贪心

  • 经典模型
    • 需要给出一个顺序,满足某一函数值最小/大.
      • 这里常常是\(sort\),至于关键字是啥,可以考虑先取出其中的两项,那么就只有两种顺序,推下式子得出某一项在前面更优的条件是啥,就可以按照这个\(sort\)了.如:\([NOIp2012\)国王游戏\(]\)\([ZJOI2004\)午餐].
    • 求字典序最小.设\(p_i\)表示在求出的排列中\(i\)的位置,保证所有的\(i\)取值是一个排列.\(a_i\)表示在求出的排列中第\(i\)个位置的数.要求求出的排列满足若干偏序关系\(p_i\leq p_j\).
      • 第一种,最小化\(p\)的字典序,答案是反向图最大拓扑序的\(reverse\).题目有: [菜肴制作] \(and\) 考试题\(11.4\ T3\)
      • 第二种,最小化\(a\)的字典序,直接建图跑最小拓扑序就行.题目有:$[NOIp2008\ \(旅行\)]$.
  • ​ 经典问题
    • \(D:\)若干个区间,选出最多的区间个数使得区间两两不相交 ; \(S:\)按照右端点升序排序,能选就选.
    • \(D:\)若干个区间,每个区间有权值,选出权值和最大区间集合,满足这些区间两两不相交. ; \(S:\)这个要\(dp\),设\(f_i\)为前\(i\)个位置的答案.
    • \(D:\)选最少的区间覆盖整个序列 ; \(S:\)每次选当前能选的里面\(r\)最大的就行.
    • \(D:\)要求将一些点放到序列上,每个点有一个最晚可以放下的位置,问最多能放下多少点 ; \(S:\)按照能放下的最大位置降序排序,每次放点都贪心地靠后放.
    • \(D:\)要求将一些区间放到序列上,每个区间有一个能放下地最大位置,问最多能放下多少区间,放下的区间不能重合 ; \(S:\)按能放下的位置升序排序,每次能放就放,并且把放下的区间长度放到一个大根堆里,并维护一个当前放了的最后一个位置.遇到一个不能放的区间,就看堆顶长度是不是比它大,如果是就\(pop\)掉它换成当前区间. 其实这题是\([JSOI2007\)建筑抢修\(]\).

数据结构

  • 一个序列可以单栈排序的条件是,不存在\(i<j<k\)满足\(a_k<a_i<a_j\).

  • 表达式的计算

    • 后缀表达式: 遇到数就把它放到栈中,遇到运算符就取出栈顶两个数计算再\(push\)进去.
    • 中缀表达式: 分治递归处理,具体见\([NOIp2005\)等价表达式\(]\).
  • 单调栈

单调队列

  • 常见用法是将求解的问题做一些转化之后变成两个部分,然后枚举一个部分,另外一个部分一般要求是区间最大/小,就可以用单调队列维护了.

链表

  • 支持快速删点.对于边加点边询问的问题可以离线下来倒过来做,边删点边记录当前答案.

  • 有一个叫"对顶堆"的东西,可以动态维护中位数(这个也可以链表维护.

并查集

  • 并查集能动态维护许多具有传递性的东西.
  • 有一个和链表相反的就是它支持加点,却不能支持删点.所以如果边删点边询问,可以离线下来,倒着做,边加点边记下答案.
  • 扩展域并查集
  • 边带权并查集,注意\(d[root]\)必须为\(0\).
  • 快速得到后继位置,序列上的:\(BZOJ2054\)疯狂的馒头 ; 树上的:考试$10.27\ T3 $.

[模板]边带权并查集

il int fd(Ri x)
{
	if(x==fa[x])return x;
	Ri rt=fd(fa[x]);
	d[x]+=d[fa[x]];
	return fa[x]=rt;
}

树状数组

  • 支持单点修改和区间求和/前缀和.
  • 将序列差分之后再用树状数组维护可以支持区间修改和单点查询.

线段树

  • 数组要开\(4\)倍大小.
  • 每次更新了\(p<<1\)\(p<<1|1\)后记得更新\(t[p].dat\).

图论

最短路

  • 一些和最短路类似的\(dp\)转移也可以跑\(dijkstra\),如\([JLOI2011\)飞行路线\(]\).
  • \(dij\)不能处理负权,要用\(SPFA\).
  • 别忘了\(Floyed\),看数据范围\(n\leq 200\)\(Floyed\)的暗示吖\(QwQ\).
  • \(01BFS\)

[模板]\(Dijkstra\)

il void dijkstra()
{
	mem(dis,63);dis[s]=0;q.push(mp(0,s));
	while(q.size())
	{
		Ri u=q.top().second,dat=-q.top().first;q.pop();
		if(dat>dis[u])continue;
		e(i,u)
		{
			Ri v=a[i].v,w=a[i].w;
			if(dat+w<dis[v])dis[v]=dis[u]+w,q.push(mp(-dis[v],v));
		}
	}
}

[模板]\(SPFA\)

il void SPFA()
{
	go(i,1,n)dis[i]=inf;
	dis[s]=0;in[s]=1;q.push(s);
	while(q.size())
	{
		Ri u=q.front();in[u]=0;q.pop();
		e(i,u)
		{
			Ri v=a[i].v,w=a[i].w;
			if(dis[u]+w<dis[v]){dis[v]=dis[u]+w;if(!in[v])in[v]=1,q.push(v);}
		}
	}
}

[模板]\(Floyed\)


次短路

  • 可以走重复边+严格次短路.
    • 这个可以用类似树形\(dp\)求最长链的方法解决.其实感觉就是在跑\(dijkstra\)的时候\(dp?\)注意这里有一个易错点就是并不是只把\(d1_i\)放进\(priority\_queue\),应该是\(d1_i,d2_i\)都要\(push\)进去.
  • 不能走重复边+非严格次短路.
    • 一个比较直接简单的办法是先跑个最短路,然后依次删掉最短路上的边,再跑最短路.
    • 另还有一个和第一种情况类似的\(dp\)做法,不过因为这里不能走重边,所以要记录从哪里转移而来,就不能再转移回去了.

负环

  • \(SPFA\).记录\(cnt_i\)表示从\(1\)\(i\)的最短路径包含的边数.当发现\(cnt_j\geq n\)时就存在负环.这种方法效率较高.

最小生成树

  • \(Kruskal\)复杂度\(O(mlog\ m)\),\(Prim\)复杂度\(O(n^2)\).
  • 所以,完全图\(Prim\)更优.

[模板]\(Prim\)

	mem(dis,0x3f);dis[1]=0;
	go(k,1,n)
	{
		Ri u=0;
		go(i,1,n)if(!in[i] && dis[i]<dis[u])u=i;
		in[u]=1;as+=dis[u];
		e(i,u)
		{
			Ri v=a[i].v,w=a[i].w;
			if(!in[v] && w<dis[v])dis[v]=w;//不要思维僵化地写成dis[v]=dis[u]+w辣
		}
	}

树的直径

  • 树形\(dp\)或两次\(dfs\).

\(LCA\)

  • 倍增复杂度\(O((n+q)log\ n)\),\(Tarjan\)复杂度\(O(n+q)\),树剖复杂度\(O(qlog\ n)\).
  • \(Tarjan\)\(LCA\).
    • 将所有的点标记为\(3\)个颜色.白色表示还没访问过,灰色表示访问过但还没回溯,黑色表示访问且回溯完毕.每次标记一个点为灰点时就回答以它为一端且另外一端是黑点的询问,答案应该是对应的黑点向上走到的第一个灰点,这个可以用并查集维护.具体来说,每次标记一个点是黑点的时候就合并一下它与父亲.
  • 树剖求\(LCA\).
    • \(top_u\)表示从\(u\)开始往上走,只走重链可以到达的最高点.

[模板]倍增求\(LCA\)

il void build(Ri u,Ri fa)
{
	dep[u]=dep[fa]+1,f[u][0]=fa;
	go(i,1,18)f[u][i]=f[f[u][i-1]][i-1];
	e(i,u)if(a[i].v!=fa)build(a[i].v,u);
}
il int lca(Ri u,Ri v)
{
	if(dep[u]<dep[v])swap(u,v);
	yes(i,18,0)if(dep[f[u][i]]>=dep[v])u=f[u][i];
	if(u==v)return u;
	yes(i,18,0)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
	return f[u][0];
}

[模板]\(Tarjan\)\(LCA\)

我 不 会.

[模板]树剖求\(LCA\)

il void dfs1(Ri u,Ri ft)
{
	fa[u]=ft;sz[u]=1;dep[u]=dep[ft]+1;
	e(i,u)if(a[i].v!=ft)dfs1(a[i].v,u),sz[u]+=sz[a[i].v];
}
il void dfs2(Ri u)
{
	if(!top[u])top[u]=u;
	Ri t=0;e(i,u)if(a[i].v!=fa[u] && sz[a[i].v]>sz[t])t=a[i].v;
	if(t)top[t]=top[u]; // attention!
	e(i,u)if(a[i].v!=fa[u])dfs2(a[i].v);
}
il int lca(Ri u,Ri v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])return u;;return v;
}

最小环

  • \(Floyed\)
    • 在完成\(k-1\)层循环之后,第\(k\)层循环刚开始时,\(f[i][j]\)表示经过编号不超过\(k\)的结点的最短路.可以更新环内最大结点为\(k\)的环,具体来说,枚举环内\(k\)旁边的两个点\(i,j\),\(f[i][j]+a[j][k]+a[k][i]\)就是该情况下的最小环,更新答案即可.
    • 复杂度\(O(n^3)\).
  • 删边+\(Dijkstra\)
    • 枚举删去边\((u,v)\),然后以\(u\)为起点跑最短路,最后\(dis_v+a_{u,v}\)就是环大小.
    • 复杂度\(O(m(m+n)log\ n)\).

\(Tarjan\)缩点

[模板]\(Tarjan\)缩点

il void tarjan(Ri u)
{
	dfn[u]=low[u]=++times;
	stk[++top]=u;ins[u]=1;
	e(i,u)
	{
		Ri v=a[i].v;
		if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
		else if(ins[v])low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u])
	{
		++tot;
		while(stk[top]!=u)in[stk[top]]=tot,ins[stk[top--]]=0;
		in[stk[top]]=tot,ins[stk[top--]]=0;
	}
}

差分约束系统

  • 对于不等式\(a_i-b_i\leq c_i\),连一条有向边\((b_i,a_i)\),边权为\(c_i\).
  • 对于不等式\(a_i-b_i\geq c_i\),转化成\(b_i-a_i\leq -c_i\),连一条有向边\((a_i,b_i)\),边权为\(-c_i\).
  • 建立一个源点\(0\)向所有点连边,从\(0\)开始跑最短路,若出现负环,则无解.

二分图

  • 二分图判定:直接二分图染色即可,出现冲突就不是.
  • 二分图匹配:我只会匈牙利算法.

[模板]二分图匹配

il bool dfs(Ri u)
{
	e(i,u)
	{
		Ri v=a[i].v;
		if(vis[v])continue;;vis[v]=1;
		if(!mat[v] || dfs(mat[v])){mat[v]=u;return 1;}
	}
	return 0;
}

\(dp\)

  • 时刻都要想\(dp\),时刻都要想\(dp\),时刻都要想\(dp\).

  • 不会就想\(dp\),不会就想\(dp\),不会就想\(dp\).

  • 别忘了背包,别忘了背包,别忘了背包.

背包

  • 是自己特别薄弱的一块,不是不会,就是永远想不到.变形特别多,我总看不出背包的本质.所以要强制自己不会的时候就想下这是不是背包.
  • 注意倒序枚举体积
  • 还要记得树形背包
    • 注意边界,其实和普通的\(01\)背包没有太大差别
    • 当是森林的时候可以建个虚拟根变成一棵树
  • 多重背包的单调队列优化
    • \(f_{i,k\cdot v_i+u}=max\{f_{i-1,k'\cdot v_i+u}+w_i\cdot (k-k')\}\)
    • 建立一个随着\(k\)的减小,\(f_{k'\cdot v_i+u}-w_i\cdot k'\)递减的单调队列

[模板]树形背包

//是[选课]的代码,这里强制树根必须要选
il void dfs(Ri u)
{
	f[u][1]=w[u];
	e(i,u)
	{
		Ri v=a[i].v;dfs(v);
		yes(j,m+1,1)
			go(k,0,j-1)
			f[u][j]=max(f[u][j],f[v][k]+f[u][j-k]);
	}
}

[模板]多重背包的单调队列优化

int calc(Ri i,Ri u,Ri k){return f[k*v[i]+u]-w[i]*k;}
go(i,1,n)
{
	go(u,0,v[i]-1)
	{
		l=1,r=0;Ri mx=(m-u)/v[i];
		yes(k,mx,max(mx-c[i],0)) //注意这里c[i]的限制
		{
			while(l<=r && calc(i,u,q[r])<=calc(i,u,k))--r;
			q[++r]=k;
		}
		yes(k,mx,0)
		{
			if(k-c[i]>=0)
			{
				while(l<=r && calc(i,u,q[r])<=calc(i,u,k-c[i]))--r;
				q[++r]=k-c[i];
			}
			while(l<=r && q[l]>k)++l;
			if(l<=r)f[k*v[i]+u]=max(f[k*v[i]+u],calc(i,u,q[l])+w[i]*k);
		}
	}
}

线性\(dp\)

  • 复读一下[算法竞赛]上比较有启发性的语句.
    • 在实现状态转移方程时,要注意观察决策集合的范围随着状态的变化情况.对于"决策集合中的元素只增多不减少"的情景,可以维护一个变量来记录决策集合的信息,避免重复扫描. 如求\(lcis\).
    • 求解线性\(dp\)问题一般先确定阶段,若阶段不足以表示一个状态,则可以把所需的附加信息也作为状态的维度.
    • 排出冗杂维度.
    • 通过额外的算法确定\(dp\)状态的计算顺序
      • 贪心,如 [拯救小矮人],[\(Cookies\)].
      • 拓扑排序,这个就很常见.
      • \(Dijkstra\),比如刚做不久的[飞行路线]
      • 记搜,这个常常可以代替拓扑排序来解决顺序问题.
  • 找规律压缩状态空间,如 [过河]
  • 可能\(dp\)的答案记的不是一个树值,而是一个二元组.
    • 考试\(11.3\ T2\) : 将两个\(dp\)数组合成一个,重载加号然后树状数组维护,就减少了一维循环.
  • 通过观察决策点的规律可以优化\(dp\).
  • 观察\(dp\)数组数值的规律可以优化\(dp\),如考试\(11.12\ T2\).

区间\(dp\)

  • 小区间转移到大区间,就比较套路
  • 有时候可能要记多个值,比如 [\(Polygon\)]
  • 环形一般倍长+断环为链转化成区间\(dp\)求解

树形\(dp\)

  • 树形背包的复杂度是\(O(n^2)\)
  • 树形\(dp\)记状态时不一定要记子树内的信息(不过一般都是),也可能记到根的信息.
  • 换根\(dp\)
    • 这个真的不是很会\(QwQ\),就大概看了下这题.

状压\(dp\)

  • 用状压\(dp\)来代替小范围的搜索,效率会提高不少.

数位\(dp\)

  • 常见套路是 预处理+试填法
  • 试填法要细心
  • 我觉得不会考

计数\(dp\)

  • 合法\(=\)\(-\)不合法
  • 不合法\(=\)\(-\)合法
  • 运用组合计数与容斥原理

单调队列优化\(dp\)

  • 优化决策取值范围的上,下界均单调变化,每个决策在候选集合中插入或删除至多一次.
  • 在考虑内层循环\(j\)和决策\(k\)时,把外层循环变量\(i\)看作定值.
  • 基本式子是\(f_i=min/max\{f_j+val(i,j)\}\),且\(val(i,j)\)中没有\(i,j\)的乘积项.

斜率优化

  • 与单调队列优化\(dp\)相比,斜率优化可以解决\(val(i,j)\)中含有\(i,j\)乘积项的问题.
  • 因为还是不会所以讲下入门难度的斜率优化
    • 假设现在有\(f_i=min\{f_j-j\cdot a_i\}\)
    • 转化下\(f_j=j\cdot a_i+f_i\)
    • 于是一个\(j\)可以对应成一个坐标系中的点\((j,f_j)\),现在用一条斜率为\(a_i\)的直线从下往上扫描,第一次经过一个点时取的的截距就是\(f_i\).
    • 所以,应该维护一个斜率单调递增的\((j,f_j)\)作为候选集合.

字符串

字符串\(Hash\)

  • 字符串\(S\)\(Hash\)值是\(H(S)\),字符串\(S+T\)\(Hash\)值是\(H(S+T)\),那么字符串\(T\)\(Hash\)值是\(H(S+T)-H(S)\cdot bas^{T.length()}\).
  • 动态维护字符串\(Hash\)值可以用树状数组维护前缀和求解.

\(kmp\)算法

  • \(next_i+1,next_{next_i}+1...\)\(next_{i+1}\)的候选项.

[模板]\(kmp\)算法

Ri j=0;
go(i,2,n)
{
	while(j && a[j+1]!=a[i])j=nxt[j];
	if(a[j+1]==a[i])++j;
	nxt[i]=j;
}
j=0;
go(i,1,m)
{
	while(j && a[j+1]!=b[i])j=nxt[j];
	if(a[j+1]==b[i])++j;
	f[i]=j;if(j==n)printf("%d\n",i-n+1);
}

\(manacher\)算法

[模板]\(manacher\)算法

il void manacher()
{
	Ri id=0,r=0,ct=1;a[1]='7';
	go(i,1,n)a[++ct]=b[i],a[++ct]='7';
	a[0]='5',a[ct+1]='6';
	go(i,1,ct)
	{
		if(i<r)rad[i]=min(r-i+1,rad[(id<<1)-i]);
		else rad[i]=1;
		while(a[i+rad[i]]==a[i-rad[i]])++rad[i];
		if(i+rad[i]-1>r)id=i,r=i+rad[i]-1;
		as=max(as,rad[i]);
	}
}

\(Trie\)

  • \(01Trie\)多用于与异或有关的问题
  • 最大异或对
    • 对这\(n\)个数对应的二进制数建\(01Trie\),求答案是一次类似检索的过程,具体来说每次贪心地选择与当前数当前位不同的数走下去,没有才走相同的.
  • 最大异或路径
    • 记个到根的异或前缀和就将问题转化成了最大异或对.

[模板]\(Trie\)

il void insert()
{
	Ri len=strlen(s+1),p=0;
	go(i,1,len)
	{
		Ri q=s[i]-'a';
		if(trie[p][q])p=trie[p][q];
		else trie[p][q]=++tot,p=tot;
	}
	++ed[p];
}
il void query()
{
	Ri len=strlen(s+1),p=0;
	go(i,1,len)
	{
		Ri q=s[i]-'a';
		if(!trie[p][q])return;
		p=trie[p][q];as+=ed[p];
	}
}

数论

逆元

费马小定理求单个逆元

  • 注意条件是模数是质数
  • 答案是\(a^{p-2}\)

扩欧求单个逆元

  • 要求\(a,p\)互质

线性递推求多个逆元

  • \(inv_i=p-p/i\cdot inv[p\ mod\ i]\)
  • 证明
    • \(a=p/i,b=p\%i\)
    • \(a\cdot i+b\equiv0\ (mod\ p)\)
    • \(-a\cdot i\equiv b\ (mod\ p)\)
    • \(-a\cdot inv_b=inv_i\ (mod\ p)\)
inv[0]=inv[1]=1;go(i,2,n)inv[i]=p-1ll*p/i*inv[p%i]%p; //一定要记得是p-

线性递推求阶乘逆元

  • \(invjc_i=invjc_{i+1}\cdot (i+1)\)
  • 证明
    • \(jc_i=jc_{i+1}/(i+1)\)
    • \(invjc_i\equiv invjc_{i+1}\cdot (i+1)\)

同余方程

[模板]\(exgcd\)

il void exgcd(Ri a,Ri b,Ri &x,Ri &y)
{
	if(!b){x=1,y=0;return;}
	exgcd(b,a%b,y,x);y-=a/b*x;
}
exgcd(a,b,x,y);printf("%d\n",(x%b+b)%b);//别忘了这一步

欧拉函数

  • 定义: \(1\)\(N\)中与\(N\)互质的数叫做欧拉函数,记作\(\phi\).
  • \(\phi(N)=N\cdot \prod_{质数p|N}(1-\frac{1}{p})\)
  • 例题: \([SDOI2008]\)仪仗队.
    • 考虑一个人会被另外一个人挡住是什么情况.设左下角坐标为\((0,0)\),\((x',y')\)会被\((x,y)\)挡住,则一定满足\(\frac{y'}{x'}=\frac{y}{x}\).
    • 也就是说对于所有的\(\frac{y'}{x'}=\frac{y}{x}\)这类点,我们只能选择一个点,不难发现,这个点一定是\(\{(x,y)|gcd(x,y)=1\}\).
    • 考虑对于每个\(x\)求与它互质的\(y\)的个数,由于对称性我们只要求\(y<x\)的情况,最后将答案\(\cdot 2\)就行.

[模板]求欧拉函数

go(i,2,n)phi[i]=i;
go(i,2,n)
{
	if(phi[i]!=i)continue;
	go(j,1,n/i)phi[i*j]=phi[i*j]/i*(i-1);
}

欧拉定理

  • \(a,n\)互质,则\(a^{\phi(n)}\equiv 1\ (mod\ n)\).
  • 推论: 若正整数\(a,n\)互质,则对于任意正整数\(b\),有\(a^b\equiv a^{b\ mod\ \phi(n)}(mod\ n)\).
  • \(a,n\)不一定互质且\(b>\phi(n)\)时,有\(a^b\equiv a^{b\ mod\ \phi(n)+\phi(n)}\ (mod\ n)\).

矩阵乘法

  • \(A\cdot B\),\(A\)的列数必须要与\(B\)的行数相等.设\(A\)\(n\cdot p\)大小,\(B\)\(p\cdot m\)大小,则\(C=A\cdot B\)\(n\cdot m\)大小.

  • \(C_{i,j}=\sum_{k=1}^m A_{i,k}\cdot B_{k,j}\)

  • 矩阵快速幂加速递推

  • 一些小技巧

    • 多次求快速幂可以预处理出矩阵的幂次方,减小转移矩阵乘法的次数以降低复杂度,如考试\(10.24\ T2\).

[模板]矩阵快速幂

struct nd
{
	int mt[N][N];
	il void clear(){mem(mt,0);go(i,1,n)mt[i][i]=1;}
}A,B;
il void inc(Ri &x,Ri y){x+=y;if(x>=mod)x-=mod;}
il nd operator *(nd x,nd y)
{
	nd ret;mem(ret.mt,0);
	go(i,1,n)
		go(j,1,n)
		go(k,1,n)
		inc(ret.mt[i][j],1ll*x.mt[i][k]*y.mt[k][j]%mod);
	return ret;
}
signed main()
{
    n=read();Ri k=read();
	go(i,1,n)go(j,1,n)A.mt[i][j]=read();
	B.clear();
	while(k)
	{
		if(k&1)B=B*A;
		A=A*A;k>>=1;
	}
}

高斯消元

  • 高斯消元是一种求解线性方程组的方法,线性方程组是由\(m\)\(n\)元一次方程共同构成的.
  • 出现\(0=x\)就是无解情况,出现\(0=0\)有多解.

[模板]高斯消元

n=read();go(i,1,n)go(j,1,n+1)a[i][j]=read();
go(i,1,n)
{
	go(j,i+1,n)
	{
		if(a[i][i])break;
		if(!a[j][i])continue;
		go(k,1,n+1)swap(a[i][k],a[j][k]);
	}
	if(!a[i][i]){puts("No Solution");return 0;}
	go(j,1,n)
	{
		if(i==j || !a[j][i])continue;
		db t=a[j][i]/a[i][i];
		go(k,1,n+1)a[j][k]-=t*a[i][k];
	}
}
go(i,1,n)printf("%.2lf\n",a[i][n+1]/a[i][i]);

组合数

  • \(P_n^m=n\cdot (n-1)\cdot (n-2)...\cdot (n-m+1)=\frac{n!}{(n-m)!}\)
  • \(C_n^m=\frac{P_n^m}{m!}=\frac{n!}{m!(n-m)!}\)
  • 组合数性质
    • \(C_n^m=C_{n-1}^{m-1}+C_{n-1}^m\) 这里与杨辉三角紧密相连
    • \(C_n^0+C_n^1+...+C_n^n=2^n\)
  • 组合数的求法
    • 常规求法是预处理出阶乘以及阶乘逆元然后\(O(1)\)求.
    • 但也不要忘了最初始的\(P\)的式子,因为一些原因(可能是逆元不能处理?)可能要朴素求.

\(Lucas\)定理

  • \(p\)是质数,则\(C_n^m\equiv C_{n\ mod\ p}^{m\ mod\ p}\cdot C_{n/p}^{m/p}\ (mod\ p)\).
  • 适用于\(P\)不大但是\(n,m\)较大的计算.

类欧

  • \(O(log\ n)\)时间里求\(f(a,b,c,n)=\sum_{i=0}^n \lfloor \frac{ai+b}{c}\rfloor\).
  • \(m=\lfloor \frac{an+b}{c}\rfloor\).
  • \(f(a,b,c,n)=\frac{n(n+1)}{2}\lfloor \frac{a}{c}\rfloor+(n+1)\lfloor \frac{b}{c}\rfloor+f(a\ mod\ c,b\ mod\ c,c,n)\).
  • \(f(a,b,c,n)=nm-f(c,c-b-1,a,m-1)\).
  • 边界: \(a=0\)时 , 答案为\(b/c\cdot(n+1)\).

结论

  • 树中,到一个点的最远点一定是直径的一个端点.
  • 将两个联通块\(x,y\)连边,新的直径只有两种情况
    • \(max\{len_x,len_y\}\)
    • \(w+far_u+far_v\)
  • \(O(1)\)快速乘:
    • 原理: \(x\cdot y\ mod\ p=x\cdot y-\lfloor\frac{x\cdot y}{p}\rfloor \cdot p\)

[模板]\(O(1)\)快速乘

il ll ksc(ll x,ll y,ll p)
{
	ll z=(ld)x/p*y,ret=(ull)x*y-(ull)z*p;
	return (ret%p+p)%p;
}
posted @ 2019-11-15 22:16  DTTTTTTT  阅读(18)  评论(0编辑  收藏  举报