分治,倍增

分治

核心思想

  1. 自上而下通过递归不断地将大问题拆分成两个或多个子问题,直至被拆分出的子问题可以通过简单的方法解决
  2. 然后再自上而下地用子问题的解求解大问题的解
  3. 最终得出初始问题的解

image.png
思路: 每次将要修改的区间的分为左右两边,分别计算出要改变的数量,那么不断分治取min直至左右两边区间变为1(因为\(n=2^k\)所以每次除2一定得到最小区间为1)

// Problem: D. a-Good String
// Contest: Codeforces - Codeforces Round 656 (Div. 3)
// URL: https://codeforces.com/problemset/problem/1385/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 2e5+10;
char s[N];
int n;
int solve(int l, int r, int x)
{
	char c = x + 'a';
	if(l == r)
	{
		if(c == s[l]) return 0;
		return 1;
	}
	int mid = (l + r) >> 1;
	// for(int i=l;i<=r;i++)
	// {
		// cout<<s[i];
	// }
	// cout<<endl;
	int lc = 0, rc = 0;
	for(int i = l; i <= mid; i++)
	{
		if(s[i] != c) lc++;
	}
	for(int i = mid + 1; i<= r; i++)
	{
		if(s[i] != c) rc++;
	}
	return min(lc + solve(mid + 1, r, x+1), rc + solve(l, mid, x+1));
}
int main()
{
	int T; scanf("%d",&T);
	while(T--)
	{
		scanf("%d%s",&n,s);
		printf("%d\n",solve(0,n-1,0));
	}

	return 0;
}


image.png

平面临近点对

问题描述:给定平面上的n个点(二维平面),求出距离最近的两个点。
例题 洛谷1257

算法描述:
分治:以中间点为划分线,分别求出左右两边的最近点对,然后求出左右两边的最近点对的最小值,然后求出中间两边的最近点对,最后取三者的最小值。

算法流程:

  1. 将所有点按照x坐标第一关键字,y为第二关键字排序
  2. 递归求出左右两边的最近点对,记为d1,d2,取最小值d=min(d1,d2),记为中间点为mid,找到区间中所有x坐标距离中间点小于d的点,记为b数组,b数组按照y坐标排序,遍历b数组,对于每个点,找到b数组中y坐标在y-d,y+d之间的点,更新最小距离d
  3. 对分割解释:一个区间的最近临近点对情况分为三种:在左侧区间,在右侧区间,区间两侧各有各有一个点,现在已经到得出左右两边的最近点对距离,接下来该分析最后一种情况。
  4. 对于最后一种情况首先要两侧的点横坐标距离小于d,然后要求出两侧的点中y坐标距离最小的两个点,这样才能保证得到区间两侧两个距离最小的点。

代码实现:

// Problem: 平面上的最接近点对
// Contest: Virtual Judge - %E6%B4%9B%E8%B0%B7
// URL: https://vjudge.net/problem/%E6%B4%9B%E8%B0%B7-P1257
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cmath>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long LL;
typedef pair<double,double> PDD;
const int N=2e5+10;
PDD a[N],b[N];
int n;
bool cmp(PDD a,PDD b){
	if(a.x==b.x) return a.y<b.y;
	return a.x<b.x;
}
bool cmpy(PDD a,PDD b){
	return a.y<b.y;
}
double getd(PDD a,PDD b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double solve(int l,int r){
	if(l==r) return 1e18;//如果只有一个点那么距离最大
	if(l+1==r) return getd(a[l],a[r]);//相邻就直接返回两点距离
	int mid=(l+r)>>1;
	double d=min(solve(l,mid),solve(mid+1,r));//递归求左右两侧最近点
	int k=0;
	for(int i=l;i<=r;i++){//横坐标查找
		if(fabs(a[i].x-a[mid].x)<d) b[k++]=a[i];
	}
	sort(b,b+k,cmpy);//纵坐标排序
	for(int i=0;i<k;i++){
		for(int j=i+1;j<k;j++){
			if(fabs(b[j].y-b[i].y)>d) break;
			d=min(d,getd(b[i],b[j]));//更新最近点距离
		}
	}
	return d;
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%lf%lf",&a[i].x,&a[i].y);
	}
	sort(a,a+n,cmp);
	printf("%.4f",solve(0,n-1));

	return 0;
}

倍增

假设心啊在要查询x步以后的情况,倍增核心思想是:

  1. 需要快速处理出所有\(2^k(k \geq 0)\)后的情况:
    • 需要知道\(1(2^0)\)步后的情况;
    • 假如已经求出了\(2^y(y \geq 0)\)步以后的情况,以此为基础求出\(2^{y+1}\)步以后的情况
  2. 把x拆成\(\log(x)\)份2的幂次方的和,然后依次往后走这么多步即可

image.png

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1e5+10, DEG = 20;
struct Edge{
	int to, next;
}edge[N<<1];
int head[N],tot;
int fa[N][DEG],v[N][DEG],deg[N];
int n,q,a[N];
void addedge(int u,int v)
{
	edge[tot].to = v;
	edge[tot].next = head[u];
	head[u] = tot++;
}
void init()
{
	tot = 0;
	memset(head, -1, sizeof(head));
	memset(v,0x3f, sizeof(v));
}
void bfs(int root)
{
	queue<int> que;
	deg[root] = 0;
	fa[root][0] = root;
	v[root][0] = a[root];
	que.push(root);
	while(!que.empty())
	{
		int tmp = que.front();
		que.pop();
		for(int i = 1; i < DEG; i++)
		{
			fa[tmp][i] = fa[fa[tmp][i-1]][i-1];
			v[tmp][i] = min(v[tmp][i-1],v[fa[tmp][i-1]][i-1]); //更新已知路径上的最小权重,来自于前半段和后半段取最小
		}
		for(int i = head[tmp]; i != -1; i = edge[i].next)
		{
			int t =edge[i].to;
			if(t == fa[tmp][0]) continue;
			deg[t] = deg[tmp] + 1;
			fa[t][0] = tmp;
			v[t][0] = min(v[t][0], a[tmp]); //这里也要记得取min,否则无法及时下一节点往上走一步的最小值不能得到更新
			que.push(t);
		}
	}
}
int LCA(int a,int b)
{
	int ans = 1 << 30;
	if(deg[a]>deg[b]) swap(a,b);
	int hu = deg[a], hv = deg[b];
	int tu = a, tv = b;
	for(int det = hv - hu, i = 0; det; det>>=1, i++)
	{
		if(det&1)
		{
			ans = min(ans, v[tv][i]);
			tv = fa[tv][i];
		}
	}
	if(tu == tv) return ans;
	for(int i = DEG - 1; i >= 0; i--)
	{
		if(fa[tu][i] == fa[tv][i]) continue;
		ans = min(ans, fa[tu][i]);
		ans = min(ans, fa[tv][i]);
		tu = fa[tu][i];
		tv = fa[tv][i];
	}
	ans = min(ans, min(v[tu][0],v[tv][0]));
	return ans; 
}
// 代码由LCA修改过来
int main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	init();
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		addedge(u,v);
		addedge(v,u);
	}
	bfs(1);
	while(q--)
	{
		int u,v;
		cin>>u>>v;
		cout<<LCA(u,v)<<endl;
	}

	return 0;
}

image.png
分析:
生成树的最小权值和,按照是否在最小生成树上分为两种情况

  • 在最小生成树上,此时所求就是最小生成树
  • 不在,此时将u,v这条路加上同时去掉环上最大的边,此时仍保证联通
// Problem: E. Minimum spanning tree for each edge
// Contest: Codeforces - Educational Codeforces Round 3
// URL: https://codeforces.com/problemset/problem/609/e
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 2e5+10;
struct Node{
	int x,y,v,idx;
	bool in;
	bool operator < (const Node &A) const{
		return v < A.v;
	}
}a[N];
int n,m,dist[N],f[N][21],v[N][21],fa[N];
LL q[N];
struct Edge{
	int y,v;
	Edge(int _y, int _v)
	{
		y = _y;
		v = _v;
	}
};
vector<Edge> edge[N];
void dfs(int x)
{
	for(auto i:edge[x])
	{
		if(!dist[i.y])
		{
			dist[i.y] = dist[x]+1;
			f[i.y][0] = x;
			v[i.y][0] = i.v;
			dfs(i.y);
		}
	}
}
int gf(int i)
{
	if(i == fa[i]) return i;
	return fa[i] = gf(fa[i]);
} 
int main()
{	
	scanf("%d%d",&n,&m);
	for(int i = 1;i<=m;i++)
	{
		scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v);
		a[i].idx=i;
	}
	sort(a+1, a+m+1);
	for(int i=1;i<=n;i++) fa[i] = i;
	LL res =0;
	for(int i=1;i<=m;i++)
	{
		int x = gf(a[i].x), y = gf(a[i].y);
		if(x!=y)
		{
			a[i].in=true;
			res+=a[i].v;
			edge[a[i].x].push_back(Edge(a[i].y,a[i].v));
			edge[a[i].y].push_back(Edge(a[i].x,a[i].v));
			fa[x]=y;
		}
	}
	memset(dist,0,sizeof(dist));
	dist[1]=1;
	dfs(1);
	for(int i=1;i<=20;i++)
	{
		for(int j=1;j<=n;j++)
			f[j][i] = f[f[j][i-1]][i-1],
			v[j][i] = max(v[j][i-1], v[f[j][i-1]][i-1]);
	}
	for(int i=1;i<=m;i++)
	{
		if(a[i].in)
		{
			q[a[i].idx] = res;
			continue;
		}
		int x=a[i].x,y=a[i].y;
		if(dist[x]<dist[y]) swap(x,y);
		int z = dist[x]-dist[y];
		int ans=0;
		for(int i=0;z;z>>=1,i++)
			if(z&1)
				ans = max(ans,v[x][i]);
				x = f[x][i];
		if(x!=y)
		{
			for(int i=20;i>=0;i--)
			{
				if(f[x][i]!=f[y][i])
				{
					ans=max(ans,v[x][i]);
					ans=max(ans,v[y][i]);
					x=f[x][i];
					y=f[y][i];
				}
			}
			ans=max(ans,v[x][0]);
			ans=max(ans,v[y][0]);
		}
		q[a[i].idx] = res - ans + a[i].v;
	}
	for(int i=1;i<=m;i++) printf("%lld\n",q[i]);
	
	return 0;
}

image.png

分析: LCM等于这一段数字的乘积意味着划分的区间内的数的质因数只出现一次

// Problem: D. Cut
// Contest: Codeforces - Codeforces Round 717 (Div. 2)
// URL: https://codeforces.com/problemset/problem/1516/D
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1e5+10;
int a[N],n,q;
int f[N][21], v[N];
vector<int> c[N];
bool b[N];
int main()
{	
	// 埃氏筛顺便把每个数的质因子筛出
	memset(b,false,sizeof(b));
	for(int i = 2; i <= 100000; i++)
	{
		if(!b[i])
		{
			c[i].push_back(i);
			// cout<<i<<endl;
			for(int j = i + i; j <= 100000; j+= i)
			{
				b[j] = true;
				c[j].push_back(i);
			}
		}
	}
	scanf("%d%d",&n,&q);
	for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
	// 确定边界,f[i][j]为i跳2的j次幂后下一刀应该切的位置
	f[n+1][0] = n + 1;
	// v表示遍历到i时每个质因子出现的最近位置
	memset(v, 0x3f, sizeof(v));
	// f[i][0]最远的位置被f[i+1][0](不能超过它),a[i]中的质因子的v[i]约束
	for(int i = n; i; i--)
	{
		f[i][0] = f[i+1][0];
		for(auto j : c[a[i]])
		{
			f[i][0] = min(f[i][0],v[j]);
			v[j] = i;
		}
	}
	for(int i = 1; i <= 20; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			if(f[j][i-1] <= n) f[j][i] = f[f[j][i-1]][i-1];
			else f[j][i] = n + 1;
		}
	}
	while(q--)
	{
		int l, r; scanf("%d%d",&l,&r);
		int ans = 0;
		for(int i = 20; i >= 0; i--)
		{
			if(f[l][i] <= r) 
			{
				ans += (1 << i);
				l = f[l][i];
			}
		}
		printf("%d\n", ans + 1);
	}
	
	
	return 0;
}
posted @ 2023-12-21 18:58  viewoverlook  阅读(10)  评论(0编辑  收藏  举报