分治专题。

本文深入探讨了分治算法,从快速排序的优化实现到逆序对的计算,再到最大子段和与棋盘覆盖问题的解决,每个主题都配以详细的解题思路和代码示例,最后讨论了一道复杂的分治题目MooFest G,全面展示分治法在算法问题中的应用。
摘要由CSDN通过智能技术生成

1.先介绍快排,在洛谷的这题中,所用的快排要比平时写的快排快才能AC,所以这里给出题目和AC代码
快速排序

#include<iostream>
#include<string.h>
using namespace std;
int n, a[1000005];
void quicksort(int *a, int s, int e){//应用二分思想
  int mid = a[s+(e - s)/2];//中间数
  int i = s, j = e;
  while(i <= j){//这里注意要有=
    while(a[i] < mid) i++;//查找左半部分比中间数大的数
    while(a[j] > mid) j--;//查找右半部分比中间数小的数
    if(i <= j){//如果有一组不满足排序条件(左小右大)的数
      swap(a[i], a[j]);//交换
      i++;
      j--;
    }
  }
  if(s < j){//递归搜索左半部分
    quicksort(a, s, j);
  }
  if(i < e){//递归搜索右半部分
    quicksort(a, i, e);
  }
}
int main(){
  cin>>n;
  for(int i = 0; i < n; i++){
    cin>>a[i];
  }
  quicksort(a, 0, n-1);
  for(int i = 0; i < n-1; i++){
    cout<<a[i]<<" ";
  }
  cout<<a[n-1]<<endl;
  return 0;
}

2.接下来是求排列数的逆序对,即给出排列好的数,不改变排列数的顺序,如果下标i < j 则元素也应该a[i] < a[j] ,否则为逆序对,这题是为了计算逆序对的数量.
逆序对

#include<iostream>
#include<string.h>
using namespace std;
typedef long long ll;
int n;
ll ans = 0;
int a[100005], b[100005];
void merge(int *a, int s, int mid, int e, int *b){
  int p1 = s, p2 = mid+1, ss = 0;
  while(p1 <= mid && p2 <= e){
    if(a[p1] > a[p2]){//如果满足逆序的条件,所做的计数 
      ans = ans + e - p2 + 1;
      b[ss++] = a[p1++];
    }else{
      b[ss++] = a[p2++];
    }
  }
  while(p1 <= mid){
    b[ss++] = a[p1++];
  }
  while(p2 <= e){
    b[ss++] = a[p2++];
  }
  for(int i = 0; i < e-s+1; i++){//这里的意思是把数组范围内,排好序之后的数,都赋值给原数组 
    a[s+i] = b[i];
  }
}
void MergeSortAndcount(int *a, int s, int e, int *b){//先分一半,排好序,然后再 MergeSortAndcount(a);去计算左边取一个和右边取一个逆序数的数量
  if(s < e){
    int mid = s + (e - s)/2;
    MergeSortAndcount(a, s, mid, b);
    MergeSortAndcount(a, mid + 1, e, b);
    merge(a, s, mid, e, b);
  }
}
int main(){
  cin>>n;
  for(int i = 0; i < n; i++){
    cin>>a[i];
  }
  MergeSortAndcount(a, 0, n-1, b);
  cout<<ans<<endl;
  return 0;
}

3.题目为给出图片在这里插入图片描述在这里插入图片描述
接下来给出题解代码:

#include<iostream>
#include<string.h>
using namespace std;
int a[10005]; 
void max1max2(int *a, int s, int e, int &max1, int &max2){
  if(s == e){//当所给的数组只有一个数时,此时这个数是最大的,第二大的可以给很小的数 
    max1 = a[s];
    max2 = -100005;
  }else if(s+1 == e){//当只有俩个数时,直接判断出来即可,然后给函数的引用 
    max1 = max(a[s], a[e]);
    max2 = min(a[s], a[e]);
  }else{//如果更多数时,则可以才用将数组分段去求,即求左边的最大和次打,再求出右边的最大和次大,最后比较这四个数,给出最大和次大的值即可 
    int mid = s + (e - s)/2;//分段 
    int smax1, smax2;
    max1max2(a, s, mid, smax1, smax2);//这里是求左边最大和次大 
    int emax1, emax2;
    max1max2(a, mid+1, e, emax1, emax2);//这里是求右边的最大和次大 
    if(smax1 > emax1){//最后,这里判断赋值即可 
      max1 = smax1;
      max2 = max(smax2, emax1);
    }else{
      max1 = emax1;
      max2 = max(smax1, emax2);
    }
  }
}
int main(){
  int n;
  cin>>n;
  for(int i = 0; i < n; i++){
    cin>>a[i];
  }
  int max1, max2;
  max1max2(a, 0, n-1, max1, max2);//因为要把最大的数和次大的数返回,所以这个函数用了引用给外部的值 
  cout<<max1<<" "<<max2<<endl;
  return 0;
}

4.最大子段和
求给出数段中的最大的子段和,这里用的是分治法去求的最大子段和,当然也可以用动态规划去解决,且动态规划的复杂度更小,接下来给出解题思路:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
接下来给出代码:

#include<iostream>
#include<string.h>
using namespace std;
typedef long long ll;
int n, a[2000200];
ll Max(ll x, ll y){
  return x>y?x:y;
} 
int maxans(int *p, int s, int e){
  if(s == e){
    if(p[s] < 0){
      return 0;
    } 
    return p[s];
  }else{
    ll sum = 0; 
    int mid = s + (e - s)/2;
   // int sumleft = maxans(a, s, mid);//第一种情况,最大和在左边 
   // int sumright = maxans(a, mid+1, e);//第二种情况,最大和在右边 
    ll s1 = -19260817, sleft = 0;//接下来是第三种情况,最大和在左右俩边都有 
    for(int i= mid; i >= s; i--){//这里是用mid作为开始,是因为这里是要从这里开始取大的值 
      sleft = sleft + p[i];//这里让s1= -19260817的原因和下面的原因一样 
      if(sleft > s1){
        s1 = sleft;
      }
    }
    ll s2 = -19260817, sright = 0;//让s2等于负数且比题目给的绝对值小,因为第一个比较的数可能是负数 
    for(int i = mid+1; i <= e; i++){
      sright = sright + p[i];
      if(sright > s2){
        s2 = sright;
      }
    }
    return Max(Max(maxans(p, s, mid), maxans(p, mid+1, e)), s1+s2);
  }
}
int main(){
  cin>>n;
  for(int i = 1; i <= n; i++){
    cin>>a[i];
  }
  ll maxn = maxans(a, 1, n);
  cout<<maxn<<endl;
  return 0;
}

5.棋盘覆盖问题:地毯填补
解题思路:一般公主的位置(特殊位置)在正方形四部分的哪个位置,第一铺的中间的所选的地毯在那个位置要没有地毯.才能不覆盖掉特殊位置.在这里插入图片描述在这里插入图片描述
解题代码为:

#include<iostream>
#include<string.h>
using namespace std;
int k, map[1050][1050];
int x, y, zx, zy;//xy为公主的坐标或特殊点的坐标(即已经被覆盖了地毯的点),zx,zy为小正方形的左上角坐标 
void pudi(int n, int x, int y, int zx, int zy){
  if(n == 1){//当大正方形的边长都为1时,直接结束递归调用 
    return;
  }//这里是把大正方形分成四个小正方形,判断zx,zy的点在哪个小正方形,然后哪个小正方形为空,不铺毯子 
  int mid = n / 2;
  if(zx+mid > x && zy+mid > y){//特殊点在左上角 
    cout<<zx+mid<<" "<<zy+mid<<" "<<1<<endl;//先在中间填一个地毯,这块地毯把正方形分成了四块更小的正方形 
    pudi(mid, x, y, zx, zy);//递归填补左上角小正方形地毯 
    pudi(mid, zx+mid-1, zy+mid, zx, zy+mid);//递归填补右上角小正方形地毯 
    pudi(mid, zx+mid, zy+mid-1, zx+mid, zy);//递归填补左下角小正方形地毯 
    pudi(mid, zx+mid, zy+mid, zx+mid, zy+mid);//递归填补右下角小正方形地毯 
  }else if(zx+mid > x){//特殊点在右上角 
    cout<<zx+mid<<" "<<zy+mid-1<<" "<<2<<endl;
    pudi(mid, zx+mid-1, zy+mid-1, zx, zy);
    pudi(mid, x, y, zx, zy+mid);
    pudi(mid, zx+mid, zy+mid-1, zx+mid, zy);
    pudi(mid, zx+mid, zy+mid, zx+mid, zy+mid);
  }else if(zy+mid > y){//特殊点在左下角 
    cout<<zx+mid-1<<" "<<zy+mid<<" "<<3<<endl;
    pudi(mid, zx+mid-1, zy+mid-1, zx, zy);
    pudi(mid, zx+mid-1, zy+mid, zx, zy+mid);
    pudi(mid, x, y, zx+mid, zy);
    pudi(mid, zx+mid, zy+mid, zx+mid, zy+mid);
  }else{//特殊点在右下角 
    cout<<zx+mid-1<<" "<<zy+mid-1<<" "<<4<<endl;
    pudi(mid, zx+mid-1, zy+mid-1, zx, zy);
    pudi(mid, zx+mid-1, zy+mid, zx, zy+mid);
    pudi(mid, zx+mid, zy+mid-1, zx+mid, zy);
    pudi(mid, x, y, zx+mid, zy+mid);
  }
}
int main(){
  cin>>k;
  cin>>x>>y;
  int s = 1 << k;//刚开始的特殊点是公主的坐标,不能覆盖地毯的点 
  pudi(s, x, y, 1, 1); 
  return 0;
}

也有办法把那些繁琐的代码简化,如下:
在这里插入图片描述
6.接下来是一道考虑较多的分治题目:MooFest G
这里要考虑到每对点的情况,所以比较复杂:在这里插入图片描述在这里插入图片描述

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
long long n;
long long ans = 0;
struct Node{
  int vi;
  int xi;
}p[20010];
bool cmp1(Node a, Node b){//以v为第一关键字排序
  if(a.vi != b.vi){
    return a.vi < b.vi;
  }
  return a.xi < b.xi;
}
bool cmp2(Node a, Node b){//以x为第一关键字排序(其实这个排序不需要第二关键字)
  if(a.xi != b.xi){
    return a.xi < b.xi;
  }
  return a.vi < b.vi;
}
void sumant(int s, int e){
  if(s == e){
    return;
  }
  int mid = s + (e - s)/2, l = s;
  long long s1 = 0, s2 = 0;
  sumant(s, mid);//计算前半部分的 
  sumant(mid+1, e);//计算后半部分的 
  //sort(p+s, p+mid+1, cmp2);//将左边按x的大小排序 
  //sort(p+mid+1, p+e+1, cmp2);//将右边按x的大小排序 
  for(int i = s; i <= mid; i++){
    s1 = s1 + p[i].xi;//这里是计算总的x和,之后用s1去存比p[i]的x大的 
  }
  for(int i = mid+1; i <= e; i++){
    while(l <= mid && p[l].xi < p[i].xi){
      s2 += p[l].xi;//存小于当前坐标x的和 
      s1 -= p[l].xi;//存大于当前坐标x的和 
      l++; 
    }
    int cnt1 = l - s, cnt2 = mid - l + 1;//cnt1去存小于当前坐标的个数,cnt2去存大于当前坐标的个数 
    ans += p[i].vi*(cnt1*p[i].xi - s2 + s1 - cnt2*p[i].xi);//这里是去计算每头牛产生的对其他牛的声音,然后计算全部的和 
  }
  sort(p+s, p+e+1, cmp2);//在分完俩份,这里刚开始递归到这里的时侯是只有俩个数,去排序 
}
int main(){
  cin>>n;
  for(int i = 1; i <= n; i++){
    cin>>p[i].vi>>p[i].xi;
  }
  sort(p+1, p+n+1, cmp1);
  sumant(1, n);
  cout<<ans<<endl; 
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值