大二算法课课堂算法练习-分治算法篇(持续更新)

棋盘问题 分治算法 2019.9.17

  • 对每一区块进行递归(注意区分递归层数跳出的条件)
  • 注意二分的时候值别写错了。

例题传送门

//
//  main.cpp
//  棋盘覆盖_分治算法
//
//  Created by 陈冉飞 on 2019/9/17.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

/*
 题意:
 对于一个边长为2的整数次幂的正方形,然后给出一个不用覆盖的点,然后想办法用规定的样式将所有位置覆盖
 思路:
 递归二分分割棋盘,然后将判断那个目标点在具体哪个位置,然后剩下的那三个区块的交点的那三个位置附上值。
 */

#include <iostream>
#include <cmath>
using namespace std;
#define maxn 1500
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
int ans[maxn][maxn],n,x,y,cnt;    //ans 记录了所有的覆盖的值的情况

//递归层次的依据不是总数,而应该是二分的次数
void solve(int l,int cx,int cy){
    if (l == 0) return;
//    cout<<l<<"  "<<cx<<"  "<<cy<<"  "<<endl;
//    for (int i = 0; i < n; i++){
//        for (int j = 0; j < n; j++)
//            printf("%d ",ans[i][j]);
//        printf("\n");
//    }
//    printf("\n\n");
    //遍历这四个位置
    int flag = 1;
    for (int i = cx-l; i < cx+l; i++)
        for (int j = cy-l; j < cy+l; j++)
            if (ans[i][j] && flag){
                flag = 0;
//                if (cnt == 4) printf("*************%d  %d  %d  %d  %d\n",cx,cy,i,j,l);
                if (i < cx && j < cy) ans[cx][cy] = ans[cx-1][cy] = ans[cx][cy-1] = cnt++;
                else if (i < cx && j >= cy) ans[cx][cy] = ans[cx-1][cy-1] = ans[cx][cy-1] = cnt++;
                else if (i >= cx && j < cy) ans[cx][cy] = ans[cx-1][cy-1] = ans[cx-1][cy] = cnt++;
                else ans[cx-1][cy-1] = ans[cx-1][cy] = ans[cx][cy-1] = cnt++;
                solve(l/2, cx-l/2, cy-l/2);
                solve(l/2, cx-l/2, cy+l/2);
                solve(l/2, cx+l/2, cy-l/2);
                solve(l/2, cx+l/2, cy+l/2);
            }
}

int main(int argc, const char * argv[]) {
    scanf("%d%d%d",&n,&x,&y);
    //init
    n = pow(2,n);
    cl(ans,0);cnt = 2;ans[x-1][y-1] = 1;
    solve(n/2,n/2,n/2);
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n;j++)
            printf("%d ",ans[i][j]-1);
        printf("\n");
    }
    return 0;
}

由于没有找到oj上有类似的题,找到了一个还不让注册账号。。。然后就没法提交。。。然后就随便本地跑了两组数据。代码肯定有问题会出错的那种
in
4 3 2
out
2 2 4 4
2 1 1 4
3 1 5 5
3 3 0 5
in
8 7 5
out
3 3 4 4 8 8 9 9
3 2 2 4 8 7 7 9
5 2 6 6 10 10 7 11
5 5 6 1 1 10 11 11
13 13 14 1 18 18 19 19
13 12 14 14 18 17 17 19
15 12 12 16 20 20 17 21
15 15 16 16 20 0 21 21

附上洛谷板题 P1228 地毯填补问题
拿上面写的检测的ac了(一开始re了,因为没改maxn。。。。)
在这里插入图片描述

//
//  main.cpp
//  分治_洛谷P1228
//
//  Created by 陈冉飞 on 2019/9/17.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
#include <cmath>
using namespace std;
#define maxn 1500
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
int ans[maxn][maxn],n,x,y,cnt;

void solve(int l,int cx,int cy){
    if (l == 0) return;
    int flag = 1;
    for (int i = cx-l; i < cx+l; i++)
        for (int j = cy-l; j < cy+l; j++)
            if (ans[i][j] && flag){
                flag = 0;
                if (i < cx && j < cy) {ans[cx][cy] = ans[cx-1][cy] = ans[cx][cy-1] = cnt++;printf("%d %d 1\n",cx+1,cy+1);}
                else if (i < cx && j >= cy) {ans[cx][cy] = ans[cx-1][cy-1] = ans[cx][cy-1] = cnt++;printf("%d %d 2\n",cx+1,cy);}
                else if (i >= cx && j < cy) {ans[cx][cy] = ans[cx-1][cy-1] = ans[cx-1][cy] = cnt++;printf("%d %d 3\n",cx,cy+1);}
                else {ans[cx-1][cy-1] = ans[cx-1][cy] = ans[cx][cy-1] = cnt++;printf("%d %d 4\n",cx,cy);}
                solve(l/2, cx-l/2, cy-l/2);
                solve(l/2, cx-l/2, cy+l/2);
                solve(l/2, cx+l/2, cy-l/2);
                solve(l/2, cx+l/2, cy+l/2);
            }
}

int main(int argc, const char * argv[]) {
    scanf("%d%d%d",&n,&x,&y);
    n = pow(2,n);
    //init
    cl(ans,0);cnt = 2;ans[x-1][y-1] = 1;
    solve(n/2,n/2,n/2);
    return 0;
}

循环赛日程表 分治算法 2019.9.29

  • 四层循环遍历(最一开始真的没想到,看到网上有这么写的才写了一下,四层绕的脑壳疼)
  • dfs一直往下分(注意这个也要注意在一层里往下走的先后顺序,即现在只知道的是最上面一行和最左边一列的数据,所以在每一层中要先递归再更新最值,有一点像倒着的线段树的pushdown的操作。)

先上dfs解决的办法,附上全部debug过程(中间在往下层传参的时候有一个参数多除了个2,然后gg,一个小时么得了。)

//
//  main.cpp
//  循环赛日程表
//
//  Created by 陈冉飞 on 2019/9/29.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
#define maxn 10010
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
int ans[maxn][maxn],n,k; //k为数据范围
#include <cmath>
int cnt = 0;

void solve(int x,int y,int k){      //x,y为中间位置的坐标
    cout<<x<<" "<<y<<" "<<k<<endl;
    if (k == 0) return;
    solve(x,y,k/2);
    solve(x,y+k,k/2);
    //递归之后开始赋值
//    if (k == 1) {
//        cnt++;
//        if (cnt == 2){
//            cout<<"********** "<<x<<"  "<<y<<"  "<<k<<endl;
//            for (int i = 0; i < k; i++)
//                for (int j = 0; j < k; j++)
//                    cout<<x+k+i<<" "<<y+k+j<<"   "<<x+i<<" "<<y+j<<"          "<<x+k+i<<" "<<y+j<<"   "<<x+i<<" "<<y+k+j<<endl;
//        }
//    }
    for (int i = 0; i < k; i++)
        for (int j = 0; j < k; j++){
            ans[x+k+i][y+k+j] = ans[x+i][y+j];  //右下等于左上
            ans[x+k+i][y+j] = ans[x+i][y+k+j];  //左下等于右上
        }
//    for (int i = 1; i <= n; i++){
//        for (int j = 1; j <= n; j++)
//            printf("%d ",ans[i][j]);
//        printf("\n");
//    }
    printf("\n\n");
}

int main(int argc, const char * argv[]) {
    while (~scanf("%d",&n) && n) {
        //init
        for (int i = 1; i <= n; i++) ans[1][i] = ans[i][1] = i;
        solve(1,1,n/2);
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++)
                printf("%d ",ans[i][j]);
            printf("\n");
        }
    }
    return 0;
}

删掉所有的debug语句之后

//
//  main.cpp
//  循环赛日程表
//
//  Created by 陈冉飞 on 2019/9/29.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
#define maxn 10010
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
int ans[maxn][maxn],n,k; //k为数据范围
#include <cmath>
int cnt = 0;

void solve(int x,int y,int k){      //x,y为中间位置的坐标
    cout<<x<<" "<<y<<" "<<k<<endl;
    if (k == 0) return;
    solve(x,y,k/2);
    solve(x,y+k,k/2);
    for (int i = 0; i < k; i++)
        for (int j = 0; j < k; j++){
            ans[x+k+i][y+k+j] = ans[x+i][y+j];  //右下等于左上
            ans[x+k+i][y+j] = ans[x+i][y+k+j];  //左下等于右上
        }
}

int main(int argc, const char * argv[]) {
    while (~scanf("%d",&n) && n) {
        //init
        for (int i = 1; i <= n; i++) ans[1][i] = ans[i][1] = i;
        solve(1,1,n/2);
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++)
                printf("%d ",ans[i][j]);
            printf("\n");
        }
    }
    return 0;
}

四层循环的:
这种方法最一开始不会用,真没想到可以用四层循环解决问题(暴力),大概思路就是通过2的幂次控制小滑窗的大小,然后再来一层循环从左到右把整个表格遍历一边,然后在每个小滑窗的内部再横竖再套两个循环遍历,复杂度logn*n^3

//
//  main.cpp
//  循环赛日程表
//
//  Created by 陈冉飞 on 2019/9/29.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

/*
 循环赛日程表
 分治到最小的的情况,然后直接让那两个人进行比赛。
 然后解决完这两个人的比赛情况之后把这种解决情况看成一个整体,逐步推进,到最后就是4个n/2的日程表的问题了。
 */

#include <iostream>
using namespace std;
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
#define maxn 10010
int s,n,k,ans[maxn][maxn],N;   //n 为运动员个数,k为问题的规模
#include <cmath>

int main(int argc, const char * argv[]) {
    while (~scanf("%d",&n) && n) {
        N = n;
        k = log(n)/log(2);
        s = 1;
        //init
        for (int i = 1; i <= n; i++) ans[1][i] = i;
        for (int i = 1; i <= k; i++) {
            n /= 2;
            for (int j = 1; j <= n; j++) {
                for (int r = s+1; r <= 2*s; r++) {
                    for (int c = s+1; c <= 2*s; c++) {
                        ans[r][c+(j-1)*s*2] = ans[r-s][c+(j-1)*s*2-s];
                        ans[r][c+(j-1)*s*2-s] = ans[r-s][c+(j-1)*s*2];
                    }
                }
            }
            s*=2;
        }
        for (int i = 1; i <= N; i++) {
            for (int j = 1; j <= N; j++) {
                printf("%d ",ans[i][j]);
            }
            printf("\n");
        }
    }
    return 0;
}

归并排序 分治算法 10.10

  • 一直往下二分一个logn的复杂度,然后最后对整体再调整顺序一个n的复杂度,最后整体O(logn*n)的复杂度。
  • 先一直递归到底部,然后再回来一层一层刷新值即可。
  • 注意再一开始递归往下的时候有到最底层的return语句。
//
//  main.cpp
//  归并排序_分治算法
//
//  Created by 陈冉飞 on 2019/10/11.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;

/*
 归并排序
  一直二分下去logn,然后每个小块之间归并排序n。
 时间复杂度O(logn*n)
 
 */
#define maxn 10010
int a[maxn],n,ans[maxn];//将最后的排好序的数组放到ans数组中

void merge(int l,int mid,int r){
//    cout<<l<<" "<<mid<<" "<<r<<endl;
    //将[l,mid]和[mid+1,r]这一段进行融合
    int i = l,p = l,q = mid+1;
    for (i = l; i <= r && p <= mid && q <= r ; )
        if (a[p] <= a[q]) ans[i++] = a[p++];
        else ans[i++] = a[q++];
    //然后再把多余的放进去
    while (p <= mid) ans[i++] = a[p++];
    while (q <= r) ans[i++] = a[q++];
    for (int x = l; x <= r; x++) a[x] = ans[x];
}

void mergesort(int l,int r){
    if (l == r) return;
    int mid = (l+r)>>1;
    //递归二分
    mergesort(l,mid);
    mergesort(mid+1,r);
    merge(l,mid,r);//最后整体排序
}

int main(int argc, const char * argv[]) {
    while (~scanf("%d",&n) && n) {
        for (int i = 1; i <= n; i++) scanf("%d",&a[i]);
        mergesort(1,n);
        for (int i = 1; i <= n; i++) printf("%d ",a[i]);
        printf("\n");
    }
    return 0;
}

//8 7 2 3 6 12 4 9 1
//9 15 7 2 3 6 12 4 9 1

快速排序 归并排序 10.12

  • 在每层中穿进去的每一段的左右区间,然后给两个起点ij分别从两头判断,调序。
  • 到最后记得把standard的值再赋回给中间的分开点i
  • 往后递归传区间1以上一步的i为区分点。
//
//  main.cpp
//  快速排序_分治算法
//
//  Created by 陈冉飞 on 2019/10/11.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;

/*
 随机取一个数,将所有的数分为两个区间,然后分别对这两个区间进行赋值
 */

#define maxn 10010
int a[maxn],n,ans[maxn];

void quicksort(int l,int r){
    if (l >= r) return;
    int i = l,j = r,standard = a[l];   //standard 永远取每个区间段的最左端。
    while (i < j) {
        while (i < j && a[j] >= standard) j--;  //大于标准的数就往前走,也就是碰到第一个比standard小的就跳出换位置
        if (i < j) a[i++] = a[j];
        while (i < j && a[i] <= standard) i++;
        if (i < j) a[j--] = a[i];
    }
    a[i] = standard;
    quicksort(l, i-1);
    quicksort(i+1, r);
}

int main(int argc, const char * argv[]) {
    while (~scanf("%d",&n) && n) {
        for (int i = 1; i <= n; i++) scanf("%d",&a[i]);
        quicksort(1,n);
        for (int i = 1; i <= n; i++) printf("%d ",a[i]);
    }
    return 0;
}

堆排序 分治算法 10.14

  • 首先理解大顶堆小顶堆,堆排序就是每次利用大顶堆,然后每次利用大顶堆,把堆顶与最后一个元素调换位置
  • 在后来的每一次排序都是上一次元素少一个,对应的,堆顶元素也就放到相应的倒数第二个,倒数第三个……逐次排序。
//
//  main.cpp
//  堆排序_分治算法
//
//  Created by 陈冉飞 on 2019/10/14.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
#define maxn 10010
int a[maxn],n;

void build_heap(int top,int l){
//    cout<<"]"<<endl;
    //获得top
    if (top<<1 > l) return;
    int newtop = top<<1;
    if (a[top<<1] < a[top<<1|1]) newtop++;
    if (a[top] < a[newtop]) {
        swap(a[top], a[newtop]);
        build_heap(newtop,l);
    }
}

void heap_sort(int l){
//    cout<<">"<<endl;
    for (int i = n/2; i > 0; i--) build_heap(i,n);//所有的非子叶节点传入,进行判断
//    for (int i = 1; i <= n; i++) printf("%d ",a[i]);
//    printf("\n\n");
//    cout<<":::::::::::"<<endl;
    for (int j = n-1; j > 1; j--) {
//        printf("-------------\n");
//        for (int i = 1; i <= n; i++) printf("%d ",a[i]);
//        printf("\n\n");
        swap(a[1], a[j+1]);
//        for (int i = 1; i <= n; i++) printf("%d ",a[i]);
//        printf("\n\n");
//        cout<<j<<"   队首元素 "<<a[1]<<"   队尾元素 "<<a[j]<<endl;
        build_heap(1,j-1);
    }
    
}

int main(int argc, const char * argv[]) {
    while (~scanf("%d",&n) && n) {
        for (int i = 1; i <= n; i++) scanf("%d",&a[i]);
        heap_sort(n);
        for (int i = 1; i <= n; i++) printf("%d ",a[i]);
        printf("\n");
    }

    return 0;
}

// 10 9 2 7 2 8 4 1 6 9 5

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值