棋盘问题 分治算法 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