首页 > 其他分享 >分治的思想

分治的思想

时间:2025-01-13 22:34:03浏览次数:3  
标签:p1 思想 int 分治 mid ++ 一段 逆序

分治算法是一种很重要的算法策略,它的基本思想是将一个复杂的问题分解成更多相同或相似的子问题,所以分治算法通常以递归的方式实现。

就像之前讲的归并排序(不知道可以翻翻之前的博客),将排序的过程不断拆解,排n个数可以排两个n/2个数,看做排n/2再/2,排4段数,最后可以看做排n段长度为1的数组,在不断合并的过程中完成排序,利用归并排序和分治的思想,可以求解逆序对的问题

先附上归并排序的代码

#include<iostream>
using namespace std;
const int N = 1e5 + 9;
int a[N], b[N];
void MergeSort(int a[], int l, int r)
{
	if (l == r)return;
	int mid = (l + r) / 2;
	MergeSort(a, l, mid);
	MergeSort(a, mid + 1, r);

	int pl = l, pr = mid + 1, pb = l;
	while (pl <= mid || pr <= r)
	{
		if (pl > mid)
		{
			b[pb++] = a[pr++];
		}
		else if (pr > r)
		{
			b[pb++] = a[pl++];
		}
		else
		{
			if (a[pl] < a[pr])b[pb++] = a[pl++];
			else b[pb++] = a[pr++];
		}
	}
	for (int i = l; i <= r; i++)a[i] = b[i];
}
int main()
{
	int n; cin >> n;
	for (int i = 1; i <= n; i++)cin >>a[i];
	MergeSort(a, 1, n);
	for (int i = 1; i <= n; i++)cout << a[i] << " ";
	return 0;
}

不懂逆序对的可以看看洛谷P1908 逆序对

逆序对就是对于给定的一段正整数序列,逆序对就是序列中 ai>aj且i<j 的有序对,如果角标i小于j但值ai>aj,二者相反,就是逆序对,也可看做严格单调递减,知道了逆序对,怎么用归并排序求解呢?

我们知道归并排序会将数组拆成一段一段,再不断合并,那么在合并的过程中相邻的两段是不是前一段的角标都严格小于后一段,只要前一段的值大于后一段,那么就构成了逆序对

我们看一组样例

5 4 2 6 3 1
先拆分,用|表示被拆成的一段
5|4|2|6|3|1
5于4,4与2,6与3,3与1,5与2,6与1有6对
变成 5 4 2|6 3 1
有5与3 5与1 4与3 4与1 2与1 有5对
共11对

要注意的是拆分完后,开始还原,每次只算未拆分前的两段,其余的会在后面合并的过程中算到

附上AC代码

#include<iostream>
using namespace std;
const int N = 5e5 + 5;
int a[N];
int b[N];
int n;
long long  ans = 0;
void mergesort(int l, int r)
{
	if (l == r)return;
	int mid = (l + r)>>1;
	mergesort(l, mid);
	mergesort(mid + 1, r);
    
	int p1 = l, p2 = mid + 1;
	int pos = l;
	while (p1 <= mid&&p2 <= r)
	{
		if (a[p1] <= a[p2])b[pos++] = a[p1++];
		else
		{
			b[pos++] = a[p2++];
			ans += (mid - p1 + 1);
		}
	}
	while (p1 <= mid)b[pos++] = a[p1++];
	while (p2 <= r)b[pos++] = a[p2++];
	for (int i = l; i <= r; i++)
		a[i] = b[i];
}
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	mergesort(1, n);
	cout << ans;
	return 0;
}

当前面一段大于后面一段时,即a[p1]>a[p2]就构成了逆序对,由于两边都是单调递增,都是取更小放入辅助数组,那么从p1开始的到mid的所有数都与a[p2]构成逆序对

再提供另一种思路和写法

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int a[N];
int b[N];
long long mergesort(int l,int r)
{
	if(l==r)return 0;
	int mid=(l+r)/2;
    long long res=mergesort(l,mid)+mergesort(mid+1,r);
	int p1=l,p2=mid+1;
	int pos=0;
	while(p1<=mid&&p2<=r) 
	{
		if(a[p1]<=a[p2])
		{
			b[++pos]=a[p1++];
			res+=p2-mid-1;
		}
		else b[++pos]=a[p2++];
	}
	while(p1<=mid)
	{
		b[++pos]=a[p1++]; 
		res+=p2-mid-1;
	}
	while(p2<=r)b[++pos]=a[p2++];
	for(int i=l;i<=r;i++)a[i]=b[i-l+1];
	return res;
}
int main()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
    cout<<mergesort(1,n)<<endl;
	return 0;
}

当a[p1]<=a[p2]时,那么a[p1]是不是就大于从mid+1到p2-1的所有数,就构成了逆序对,当a[p1]没放完,剩余的每一个数都与后一段的每一个数构成逆序对

可以仔细思考一下,这两种写法有何不同,有助于理解

再来看一道分治的例题[CF 1385D]a-Good string

 

这个问题其实就是将判断a-string变成判断b-string,并将字符串长度减半的过程,通过不断转换,直到长度为1,返回要改变的次数

附上AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
char a[N];
int n;
int solve(int l,int r,char x)
{
	if(l==r)
	{
		if(x==a[l])return 0;
		else return 1;
	}
	int y1=0,y2=0;
	int mid=(l+r)>>1;
	for(int i=l;i<=mid;i++)if(a[i]!=x)y1++;
	for(int i=mid+1;i<=r;i++)if(a[i]!=x)y2++;
	return min(y1+solve(mid+1,r,x+1),y2+solve(l,mid,x+1));
}
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t;cin>>t;
	while(t--)
	{
	   cin>>n;
	   for(int i=1;i<=n;i++)cin>>a[i];
	   cout<<solve(1,n,'a')<<endl;
	}
	return 0;
}

 每次看一下是将左边全边x代价下还是右边变x代价下,然后向下递归

最后再看一道例题洛谷P1429 平面最近点对(加强版)

对于这个题,输入每个点,可以按照x从小到 大对每个点进行排序可以对所有的点进行分治,将大的区间段分成一段一段小的区间段,当分到区间段只有一个点时,返回一个很大的值,表示没有可用的距离,开始返回后,每一段可以看做由两小段拼接而成,它的最近距离d是这两小段的d与两小段之间的点可能成为最近的更小值,然后不断返回区间段的d,找到最小的d

给个图解

假设分成的一段是有p6,p7,p8,p9,它可看作由p6,p7 与p8,p9两段拼接而成它都d就是这两段d的更小值后两端之间的点的距离如p7,p8

以下附上AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
struct Node{
	double x,y;
	bool operator<(const Node &A) const{
	   return x<A.x;
	}
}a[N],c[N];
double solve(int l,int r)
{
	if(l==r)return 1e11;
	int mid=(l+r)>>1;
	double d=min(solve(l,mid),solve(mid+1,r));
	int cnt=0;
	for(int i=l;i<=r;i++)
	{
		if(abs(a[i].x-a[mid].x)<d)
		{
			c[++cnt].x=a[i].y,c[cnt].y=a[i].x;
		}
	}
	sort(c+1,c+1+cnt);
	for(int i=1;i<=cnt;i++)
	{
		for(int j=i+1;j<=cnt&&c[j].x-c[i].x<d;j++)
		{
			d=min(d,sqrt((c[i].x-c[j].x)*(c[i].x-c[j].x)+(c[i].y-c[j].y)*(c[i].y-c[j].y)));
		}
	}
	return d;
}
int main()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i].x>>a[i].y;
	sort(a+1,a+1+n);
	printf("%.4lf",solve(1,n));
	return 0;
}

在求d的过程中利用了一些小技巧,翻转下x,y不影响距离,所有可疑点按y升序排列(因为x之间的距离都小于d)所以y之间的距离也要小与d,剪去不必要的部分

欧克了

标签:p1,思想,int,分治,mid,++,一段,逆序
From: https://blog.csdn.net/zjy200646/article/details/145123518

相关文章

  • 高难度下的一闪---白金ACT游戏设计思想的一点体会
    1、以前光环的开发者好像提出过一个理论,大意是游戏要让玩家保持30秒的循环,持续下去。大意跟后来的心流接近。2、根据我自身的开发体会,想要保持正回路,并不容易。一个是要保持适当的挑战性,毫无难度的低幼式玩法只会让人打瞌睡,而过难则会让玩家默默弃坑,或大骂开发者然后退款......
  • 关于此题[ABC367E] Permute K times倍增思想的一些总结
    传送门第一次接触到倍增思想是用来求lca时,为了避免一层一层往上爬浪费时间复杂度。再后来就是区间DP中一小类问题或者是部分图上问题可以用倍增来优化。那么这道题其实可以看作图上问题来使用倍增优化。看到这道题的数据范围,K达到了\(10^{18}\)量级,这让我们不得不思考log级别往......
  • 线段树分治-学习笔记
    线段树分治-学习笔记阅前须知:本文给出了线段树分治的一道例题以及多道习题,同时给出了部分实现的代码,帮助学习线段树分治。总述线段树分治是一种离线算法,在于把修改挂在线段树的节点上,通过遍历线段树求出每个叶子节点的答案,以减小复杂度。例题P5787二分图题目大意:\(n\)个点......
  • Luogu P2414 NOI2011 阿狸的打字机 题解 [ 紫 ] [ AC 自动机 ] [ 离线思想 ] [ 树状数
    阿狸的打字机:非常牛的AC自动机题。暴力先考虑在暴力的情况下,我们如何计算\(x\)匹配\(y\)的次数。显然,我们会模拟往\(y\)里加字符的过程,在此过程中做KMP进行匹配,统计答案。那么如果涉及多个模式串呢?就可以把KMP加强成AC自动机了。考虑在AC自动机上如何刻画这个......
  • 点分治
    更新日志2025/01/07:开工。概念点分治,通常用于处理大规模的树上路径信息问题。思路我们将原问题划分为多种,对于每个节点,统计经过这个节点且位于这棵子树内的路径答案。为了缩减复杂度,对于每一棵子树,我们都找到它的重心,以重心为新根在子树内进行操作。找重心示例:intcn......
  • 搬水果(哈夫曼树思想)
    描述   在一个果园里,小明已经将所有的水果打了下来,并按水果的不同种类分成了若干堆,小明决定把所有的水果合成一堆。每一次合并,小明可以把两堆水果合并到一起,消耗的体力等于两堆水果的重量之和。当然经过n‐1次合并之后,就变成一堆了。小明在合并水果时总共消耗的体力等于......
  • Faster RCNN核心思想理解-尺度变换梳理
    FasterRCNN是目标检测领域里程碑式的算法,其融合了"RegionProposal","AnchorBased"等早期目标检测的重要思想,并且在开放世界目标检测中又重新获得应用。本文将以分析FasterRCNN为主线,探讨目标检测涉及到的设计思路和理论基础。参考链接:RCNN到FasterRCNN:https://blog.csdn......
  • 分治杂记
    分治杂记分治(DivideandConquer),就是把一个复杂的问题分成若干子问题,分别求解。本质是缩小了问题的规模。普通的分治[ABC373G]NoCrossMatching给定平面上的\(n\)个黑点和\(n\)个白点,构造一种方案,将黑白点两两匹配并连线段,使得任意两条线段不相交。\(n\leq100\),保......
  • 深入了解分治 FFT
    问题提出算法应用于问题,分治FFT的出现是为了解决这样一个问题:给定序列\(g_{1\dotsn-1}\),求序列\(f_{0\dotsn-1}\)。其中\(f_i=\sum_{j=1}^if_{i-j}g_j\),边界为\(f_0=1\)。具体可以见【模板】分治FFT-洛谷对于这个问题我们要求做到\(\Theta(n\log^2n)\)的......
  • 【优选算法 & 分治】深入理解分治算法:分治算法入门小专题详解
             快速排序算法   (1)快速排序法       (2) 快排前后指针     (3)快排挖坑法   颜色分类  题目解析    算法原理   算法原理和移动零非常相似  简述移动零的算法原理   ......