杂项算法小总结

前言

很多知识不好整理,这篇文章总结一些杂乱的东西,涉及的内容有,( exttt{RMQ}),矩阵乘法(本来应该写在代数里面的,但我的代数知识少得可怜,就写在杂项里面了),高斯消元,( exttt{2-SAT}),扫描线,随机化与模拟退火(还没学,学了会写的)。

RMQ

玲珑杯 Round15 G咸鱼拷问

(RMQ),即区间最值询问,是一类查找区间最值的问题,这里讲一下比较有名的在线算法(ST表)

(ST)表,本质上是一种(dp),设(f[i][j])表示序列中第(i)个数开始向后(2^j)个数这个区间的最值,那么初始状态(f[i][0]=a[i])(dp)方程为:

(f[i][j]=max/min(f[i][j-1],f[i+(1<<(j-1))][j-1]))

从中我们可以看出,从(i)开始后的(2^j)个数,我们把它分成了两段,一段是第(i)个数到第(i+(1<<(j-1))-1)个数,一段是第(i+(1<<(j-1)))个数到第(i+(1<<j)-1)个数,也就是这中间的(2^j)个数。
以上是预处理部分,现在如果我们要考虑查询([l,r])这个区间呢?看图(来自某(C)姓教练)

在此,(x_{max}=log(r-l+1)/log(2))

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],b[100005],maxn[100005][25],minn[100005][25];
void rmq()
{
	for(int j=1;j<20;j++)
		for(int i=1;i<=n;i++)
		if(i+(1<<j)-1<=n)
		maxn[i][j]=max(maxn[i][j-1],maxn[i+(1<<(j-1))][j-1]),minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
}
int ask(int l,int r)
{
	int x=log(r-l+1)/log(2);
	int maxx,minx;
	maxx=max(maxn[l][x],maxn[r-(1<<x)+1][x]);
	minx=min(minn[l][x],minn[r-(1<<x)+1][x]);
	return maxx*minx;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]),maxn[i][0]=minn[i][0]=a[i];
	for(int i=1;i<=n;i++)
	scanf("%lld",&b[i]);
	rmq();
	for(int i=1;i<=n;i++)
	printf("%lld
",ask(i-b[i]+1,i));
	return 0;
}

矩阵乘法

矩阵乘法是代数的中的知识,两个矩阵相乘的结果自然也是矩阵,那么它的运算法则是什么呢?

设一个(m*n)的矩阵为(A),一个(p*m)的矩阵为(B),那么这两个矩阵的乘积矩阵(C)的大小就应该是(m*m)

设有矩阵

[A_{2,3}= egin{pmatrix} a_{1,1} & a_{1,2} & a_{1,3} \ a_{2,1} & a_{2,2} & a_{2,3} end{pmatrix} ]

设有另一个矩阵

[B_{3,2}= egin{pmatrix} b_{1,1} & b_{1,2} \ b_{2,1} & b_{2,2} \ b_{3,1} & b_{3,2} end{pmatrix} ]

那么两者相乘的结果就应该是

[C_{2,2}= egin{pmatrix} a_{1,1}*b_{1,1}+a_{1,2}*b_{2,1}+a_{1,3}*b_{3,1} & a_{1,1}*b_{1,2}+a_{1,2}*b_{2,2}+a_{1,3}*b_{3,2} \ a_{2,1}*b_{1,1}+a_{2,2}*b_{2,1}+a_{2,3}*b_{3,1} & a_{2,1}*b_{1,2}+a_{2,2}*b_{2,2}+a_{2,3}*b_{3,2} end{pmatrix} ]

也就是说,矩阵之间的乘法是第一个矩阵的行乘第二个矩阵的列。
根据矩阵的运算法则,我们不难看出,矩阵是满足乘法分配律和乘法结合律的,但是矩阵不满足乘法交换律,一旦交换,结果矩阵就会产生变化。

矩阵乘法在(OI)中主要运用于优化递推式,有的时候(O(n))的复杂度也许会高达(1e9)甚至(1e12),这个时候如果有矩阵乘法(严格来说是矩阵快速幂)的优化,复杂度就可以降为(O(logn)),可以接受。
只要我们把递推式写了出来,矩阵一般都比较好构造。
矩阵加速

#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
const ll mod=1e9+7;
struct node
{
	ll m[3][3];
//	node(){memset(m,0,sizeof m);}
}u,v;
//u.m[3][3]=
//{
//	{0,1,0},
//	{0,1,0},
//	{0,1,0}
//};
//v.m[3][3]=
//{
//	{1,0,1},
//	{1,0,0},
//	{0,1,0}
//};
void init()
{
	memset(u.m,0,sizeof(u.m));
	memset(v.m,0,sizeof(v.m));
	u.m[0][1]=u.m[1][1]=u.m[2][1]=1;
	v.m[0][0]=v.m[1][0]=v.m[0][2]=v.m[2][1]=1;
}
node mul(node a,node b)
{
	node t;
	for(int i=0;i<3;i++)	
		for(int j=0;j<3;j++)
		{
			t.m[i][j]=0;
			for(int k=0;k<3;k++)
			t.m[i][j]=(t.m[i][j]+a.m[i][k]*b.m[k][j])%mod;
		}
	return t;
}
node pow(ll b)
{
	while(b)
	{
		if(b&1) u=mul(u,v);
		v=mul(v,v);
		b>>=1;
	}
	return u;
}
ll T;
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		init();
		ll p;
		scanf("%lld",&p);
		if(p<=3)
		{
			printf("1
");
			continue;
		}
		node ans=pow(p);
		printf("%lld
",u.m[1][0]);
	}
	return 0;
	
}

矩阵快速幂

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int mod=1e9+7;
ll n,k;
struct node
{
	ll m[105][105];
}a;
node mul(node a,node b)
{
	node t;
//	memset(t,0,sizeof(t));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
		t.m[i][j]=0;
		for(int k=1;k<=n;k++)
		t.m[i][j]=(t.m[i][j]+a.m[i][k]*b.m[k][j])%mod;			
		}
	
	return t;
}
node pow(node a,ll k)
{
	node ans=a,b=a;
	k--;
//	memset(ans,0,sizeof(ans));
	while(k)
	{
		if(k&1) ans=mul(b,ans);
		b=mul(b,b);
		k>>=1;
	}
	return ans;
}
int main()
{
	scanf("%lld%lld",&n,&k);
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=n;j++)
		scanf("%lld",&a.m[i][j]);
	a=pow(a,k);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		printf("%lld ",a.m[i][j]);
		printf("
");		
	}

	return 0;
}
原文地址:https://www.cnblogs.com/valentino/p/11838515.html