学习笔记——根号分治

根号分治

根号分治与其说是一个算法,更不如说是一种思想

例题1

给出一个正整数\(x\),和\(n\)个整数\(a_i\),求\(x^{a_i} \bmod p\)

对于\(100\%\)的数据,\(1\leq n\leq 10^6,1\leq x,a_i<p,p=99824435\color{red}2\)

这很显然可以用二分快速幂来解决,时间复杂度\(O(nlogn)\),但如果时限为\(0.2s\)呢,二分快速幂就有一点乏力了。

因为发现底数\(x\)不变,考虑打表,将\(x^k\)全部预处理出来,最后直接查询。预处理,时间复杂度\(O(k)\),空间复杂度\(O(k)\),查询,时间复杂度\(O(1)\),这也不行。

但将两种方法结合一下,根号分治,这道题就可以做了:

因为\(x^k = x^{k\%p + p*\lfloor k/p \rfloor} = x^{k\%p} * x^{p*\lfloor k/p \rfloor}\)

那么\(p\)的取值就至关重要,根号分治其实就是\(p=\sqrt n\),然后分类讨论

对于\(k <= \sqrt n\)时,我们可以\(O(\sqrt n)\)预处理\([1,\sqrt n]\)\(x^k\)

对于\(k>\sqrt n\)

可以先\(O(\sqrt n)\)求出\((1 <= i <= \sqrt n)\)\(x^{i * \lfloor k/p \rfloor}\)

然后通过\(x^k = x^{k\%p + p*\lfloor k/p \rfloor} = x^{k\%p} * x^{p*\lfloor k/p \rfloor}\)求得最后答案

预处理\(O(\sqrt n)\),查询\(O(1)\)

\(code:\)

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ri register int
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x)
{
    x=0;register char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
static char cc[10000];
template<typename item>
inline void print(register item x)
{ 
	register long long len=0;
	while(x)cc[len++]=x%10+'0',x/=10;
	while(len--)putchar(cc[len]);
}
typedef long long ll;
const ll mod = 998244352;
int n,x,a;
const int MAX_N = 32000;
ll f[MAX_N],ff[MAX_N];
signed main(){
//	freopen("P.in","r",stdin);
//	freopen("P.out","w",stdout); 
	read(x),read(n);
	f[0]=ff[0]=1;
	int m=sqrt(mod);
	for(int i=1;i<=m;i++) f[i]=f[i-1]*x%mod;
	for(int i=1;i<=m;i++) ff[i]=ff[i-1]*f[m]%mod;
	for(ri i=1;i<=n;i++){
		read(a);
		print(f[a%m]*ff[a/m]%mod); 
		putchar(' ');
	}
	fwrite(obuf,p3-obuf,1,stdout);
	return 0;
}

例题4:雅加达的摩天楼

印尼首都雅加达市有N座摩天楼,它们排列成一条直线,我从左到右依次将它们编号为0到N-1。除了这N座摩天楼外,雅加达市没有其他摩天楼。
有M只叫做“\(doge\)”的神秘生物在雅加达市居住。它们的编号一次是\(0\)\(M-1\)。编号为\(i\)\(doge\)最初居住于标号为\(Bi\)的摩天楼。每只\(doge\)都有一种神秘的力量,使它们能够在摩天楼之间跳跃,编号为\(i\)\(doge\)的跳跃能力为\(Pi(Pi>0)\)。在一次跳跃中,位于摩天楼 b 而跳跃能力为 \(p\)\(doge\) 可以跳跃到编号为 \(b−p\) (如果 \(0≤b−p<N\))或 \(b+p\) (如果 \(0≤b+p<N\))的摩天楼。。
编号为\(0\)\(doge\)是所有doge的首领,它有一条紧急的消息要尽快传送给编号为\(1\)\(doge\)。任何一个收到消息的\(doge\)有以下两个选择:
1.跳跃到其他摩天楼上;
2.将消息传递给它当前所在的摩天楼上的其他doge。
请帮助\(doge\)们计算将消息从\(0\)\(doge\)传递到\(1\)号的\(doge\)所学要的最少总跳跃步数,或者告诉他们消息永远不可能传递到\(1\)\(doge\)

\(1<=N<=30000,1<=Pi<=30000,2<=M<=30000\)

图论,边数太大,考虑根号分治优化建图,这道题对\(doge\)的跳跃能力进行分治

拆点,把每个点拆出\(\sqrt n\)个点,分别代表距离\(1,2,...,\sqrt n\)

对于点\(i\)

\(i'\)只连接与\(i\)距离为\(1\)的点
\(i''\)只连接与\(i\)距离为\(2\)的点
\(i'...\)只连接与\(i\)距离为\(\sqrt n\)的点

把两两距离为\(1\)的点连起来
把两两距离为\(2\)的点连起来
\(......\)
把两两距离为\(\sqrt n\)的点连起来
\(i’,i''\)都往\(i\)连边,长度为\(0\)
表示到达\(i'\)\(i''\)都等价于到达\(i\)

\(x\)号点上的\(doge\)跳跃距离为\(p\),且\(p>\sqrt n\),则直接连边到它能到达的点

这样建边最多\(n\sqrt n\)

最后跑最短路即可

三元环

给出一张\(n\)个点,\(m\)条边的无向图,问图中有多少个三元环\(\{a,b,c\}\),满足图中存在\(\{a-b,b-c,c-a\}三条边,即三个点构成环\)

方法一:

转换,对边定向,边的方向由度数较小的节点指向度数较大的节点。若度数相同,则按照节点编号由大到小连边。

结论1,新图一定是有向无环图(\(DAG\))

证明:
反证,假设存在一个环:\(a->b->c->a\)
\(d_i\)表示原图中\(i\)号点的度数,那么有\(d_a<=d_b<=d_c<=d_a\),该式子要成立,必须满足\(d_a=d_b=d_c=d_a\),度数相同的节点由小到大连边,那么有\(a>b>c>a\),所以\(a>a\),矛盾,证毕

结论2,新图每个点的出度不超过\(\sqrt m\)

证明:

假设在无向图中点\(u\)的度\(<=\sqrt m\),定向后其出度肯定不会大于等于它在原图中的度,所以\(out_u<=\sqrt m\)

假如在无向图中\(u\)的度\(>\sqrt m\),假如有边\(u->v\),那么原图中\(u\)的度小于等于\(v\)的度,又因为原图中\(u\)的度是\(>\sqrt m\)的,所以满足条件的点\(v\)不超过\(\sqrt m\)个,于是这种情况下\(out_u<\sqrt m\),证毕

统计三元环

  1. 枚举每一个点,对于当前点,将它能到达的点打上标记
  2. 枚举做过标记的点,对于当前被做过标记的点,枚举它能到达的点,假如这个能到达的点是被做过标记的,那么就找到了一个三元环。

三元环是定向的,每个点只会枚举出边,所以每个环被统计的情况是唯一的。

**共\(n\)个点,每个点出度不超过\(\sqrt m\),所以时间复杂度为\(O(n\sqrt m)\)

\(code:\)求三元环贡献和\(max{a_1,a_2,a_3}\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 100000 + 5;
const int MAX_M = 250000 + 5;
struct node{
	int a,b;
}edge[MAX_M];
int n,m,d[MAX_N];
ll a[MAX_N];
vector<int> v[MAX_N];
int mark[MAX_N];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&edge[i].a,&edge[i].b);
		d[edge[i].a]++,d[edge[i].b]++;
	}
	for(int i=1;i<=m;i++){
		int x=edge[i].a,y=edge[i].b;
		if(d[x]>d[y] || (d[x]==d[y] && x<y)) swap(x,y);
		v[x].push_back(y);
	}
	ll ans=0;
	for(int x=1;x<=n;++x){
		for(int i=0;i<v[x].size();i++){
			int y=v[x][i];
			mark[y]=x;
		}
		for(int i=0;i<v[x].size();i++){
			int y=v[x][i];
			for(int j=0;j<v[y].size();j++){
				if(mark[v[y][j]]==x){
					ans+=max(a[v[y][j]],max(a[x],a[y]));
				}
			}
		}
	}
	printf("%lld",ans);
	return 0;
}

方法二:

无向图上进行根号分治(常数较大)

  1. 统计每个点的度数,度数\(<=\sqrt m\)的分为第\(1\)类,度数\(>\sqrt m\)分为第\(2\)
  2. 对于第\(1\)类,暴力枚举每个点,然后暴力枚举这个点相连的任意两条边,再判断这两条边的另一个端点是否相连(因为\(m\)条边最多被遍历一次,出度\(<=\sqrt m\),所以复杂度为\(O(m\sqrt m)\)
  3. 对于第\(2\)类,直接暴力枚举任意三个点,判断这三个点是否构成环(因为这一类点的个数不会超过\(\sqrt m\),所以复杂度为\(O({(\sqrt m)}^3) = O(m\sqrt m)\)
  4. 判断两个点是否连接可以用\(set,map,hash\)

\(code:\)


posted @ 2021-07-14 16:12  Thermalrays  阅读(618)  评论(0编辑  收藏  举报