字符全排列测试+去重思想
//【字符串の全排列】·去重
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int t = 0, n; //计数器,字符串长度-1
char str[N];
bool pd(int k, int i)
{ //【思想】区间内不能出现相同的元素
for (int x = k; x < i; x++)
if (str[x] == str[i])
return false;
return true;
}
void all(int k) //当前位置k
{
if (k == n) //当前位置抵达最后q
{
printf("%d\t:%s\n", ++t, str);
return; //向下探底
}
for (int i = k; i <= n; i++) //依次和后面的元素交换(n=len-1)
{
if (pd(k, i)) // ki是否可以交换【ki之间不能出现和i相同的元素】
{
swap(str[k], str[i]); //交换
all(k + 1); //递归
swap(str[k], str[i]); //恢复
}
}
}
int main()
{
scanf("%s", &str);
n = strlen(str) - 1;
all(0);
return 0;
}
概要:指针+字符串 具有一定优势
允许用=号进行赋值,用指针a[123]来引导定位字符串中单个字符
重要思想:【去重】
假若k与i意图交换位置(以下用[a,b]表示,a在左侧)
要求:b左侧【所有字符】不允许出现与【位置b】相同的【字符】
因为假若出现,在之前的递归过程中,已经出现了此次交换的结果
注意!此处b在层层递归过程中,是从a出发,逐个增大到m
层数=k的,例如13交换就是第一层,34交换是第三层,44交换到顶了就输出
举例子:abbc 意图交换1 3位置
如果没有去重:交换13,产生bbac
事实上,在之前第一层12交换后,产生babc;进入下一层,再2与3交换,出现bbac
这就是反例,举例子利于理解
整数划分问题
//【整数の加法分解】
#include <iostream>
#include <stdio.h>
using namespace std;
int get(int n, int m) // n:拆分后的数字和 m:最大加数
{
if (n == 1 || m == 1) //【总数】=1 或 【最大加数】=1
return 1; //只有一种情况 1=1 或 4=1+1+1+1
if (n < 1 || m < 1) //【总数】不是【正整数】,或 【最大加数】不是【正整数】
return 0; //不合法,返回0
if (m > n) //【最大加数】>【总数】
return get(n, n); // 最大加数不可能超过总数
if (n == m) //如果【加数】=【总数】
return 1 + get(n, n - 1); // 4=4算1种,加上(小于)4的情况
return get(n, m - 1) + get(n - m, m); //其他常规情况
// 17划4,拆成4+ 3+ 2+ 1+
//左侧get负责3+ 2+ 1+模块,右侧get负责4+模块
//左侧相当于【除去4(最大加数=3)】条件下,n的划分
//右侧相当于【固定一项4(挖去)】,剩下部分做【最大加数≤4】的分配
}
int main()
{
int n;
scanf("%d", &n);
printf("%d", get(n, n));
return 0;
}
//一个正整数拆成多个正整数之和,有几种拆分方法?
//关于其他情况的部署,举例子q(17,4)
//可以分解成4+ 3+ 2+ 1+ 共4个模块
//[左侧]:q(17,3) 囊括右侧 3+ 2+ 1+三大模块
//[右侧]:q(13,4) 囊括左侧 4+ 这唯一の模块
//【产生原因】13是把左侧4+给挖去(17-4)后,剩下部分开展(以最大加数=4为限制)的划分
//产生划分的个数和没有失去4+之前一样
//剩余总数=13, 继续进行max=4的划分
//【本质】:最后结果和4+模块的分解个数是完全一致的
//右侧表达式的思想是把 第一列4+去掉后,剩余部分继续进行分解
//而最大值max依旧=4
重要的算法思想,比较新奇,是递归的一部分,写一写我的理解与心得
对于q(n,m),我的理解是:【总数n】,【加数最大值m】max
要求1:分解最小单位是1,不允许出现0
出现则返回0,不统计数据
要求2:n=1或m=1(总数就=1 或者 分解的最大加数=1)
出现则返回1(只有1中划分可能) 1=1 或 n=1+1+1+1(n个1)
要求3:m>n说明加数最大值比总数还大
(比如总数7,我允许你最大加数=13,事实上我最大只能到7)
此时,返回q(n,n) 反璞归真
要求4:m=n 这下真的反璞归真了,得给他往下拆,拆成1+(n-1)
返回q(n,n)=q(n,n-1)+1
要求5:n>m (例如,总数=17,允许最大=14)可以拆成14+ 13+ 12+多个模块
返回q(n,m)=q(n,m-1)+q(n-m,m)
为什么会这样呢,这似乎非常抽象令人费解,在我细心研究后,有以下看法
举例子:q(17,4)=q(17,3)+q(13,4)
这个要如何理解呢
【左侧公式】:可以划分成 4+ 3+ 2+ 1+ 共4个模块
【右侧17-3】:收割了3+ 2+ 1+共3个模块
这里是【向下递归】,寻找【总数=17】、【最大加数=3】的情况
也就是要找出3+ 2+ 1+的各种情况
【右侧13-4】:收割了4+ 共1个模块
这里是对左侧4+模块进行了一次变形
是曲线救国的一个过程
【思考】:代码操作中,4+模块的第一个【4+】被删去
全公式代码-4 ==13 之后对【剩余部分】开展【最大加数=4】的划分
【本质上】:最后统计出来的个数和【没有删除4+】是完全一样的
把所有公式整齐列出,砍去第一个4+后,剩余总数13 分解max=4
这样就可以算出原生4+模块公式的个数
汉诺塔(n盘3柱)
#include <iostream>
#include <stdio.h>
using namespace std;
int t = 0;
void hanoi(int n, int a, int b, int c)
{ //共n偏盘片,从a-->b,借助c
if (n > 0)
{
hanoi(n - 1, a, c, b);//递归
printf("%d:%d-->%d\n", ++t, a, b);//实际移动
hanoi(n - 1, c, b, a);//递归
}
}
int main()
{
int n; //从a到b,以c为辅助
printf("汉诺塔(n盘片,3柱体),请输入n:");
scanf("%d", &n);
hanoi(n, 1, 2, 3);
return 0;
}
/*
【思想】
一共n个盘片,从a移动到b,借助c
将n-1片,从a 移动到【辅助塔c】
将【底片】,从a 移动到【目标塔b】
将n-1片,从c 移动到【目标塔b】
*/
棋盘覆盖
//【P20 2.6棋盘覆盖】
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N = 1e4 + 7;
int num = 0, map[N][N], n;
void cover(int x, int y, int hx, int hy, int size)
{
if (size == 1) //自棋盘大小=1,没得玩
return;
int mid = size >> 1;
int cx = x + mid, cy = y + mid;
int t = ++num;
// t:第几个部署的三角块
// mid:规模半值 8
// xy系列:全局边界(左上点) 0 0
// h系列:全局边界(右下点) 16 16
// c系列:中间点(右下块的左上点) 8 8
//触发3个else和一个if(黑点方向)
//【黑点坐标·左上块】
if (hx < cx && hy < cy)
cover(x, y, hx, hy, mid); //部分递归
else
{
map[cx - 1][cy - 1] = t; //染色
cover(x, y, cx - 1, cy - 1, mid); //完全递归
}
//【黑点坐标·右上块】
if (hx < cx && hy >= cy)
cover(x, cy, hx, hy, mid);
else
{
map[cx - 1][cy] = t;
cover(x, cy, cx - 1, cy, mid);
}
//【黑点坐标·左下块】
if (hx >= cx && hy < cy)
cover(cx, y, hx, hy, mid);
else
{
map[cx][cy - 1] = t;
cover(cx, y, cx, cy - 1, mid);
}
//【黑点坐标·右下块】
if (hx >= cx && hy >= cy)
cover(cx, cy, hx, hy, mid);
else
{
map[cx][cy] = t;
cover(cx, cy, cx, cy, mid);
}
}
void show()
{
for (int x = 0; x < n; x++)
{
for (int y = 0; y < n; y++)
printf("%d\t", map[x][y]);
printf("\n\n\n");
}
}
int main()
{
int x, y;
scanf("%d%d%d", &n, &x, &y);
cover(0, 0, x - 1, y - 1, n); //起点,黑点,规模
show();
return 0;
} //注意!n必须是2的k次方
循环赛 日程表
//【循环赛日程表P35 2.11】
#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 1e4 + 7;
int a[N][N]; //总表
int mid; //【四分块】半径
int n; //【小幂块】个数
int all; //【全块】半径
int mi; //阶数(幂)
int sum; // show使用
void show()
{
printf("[%d]\n", ++sum);
for (int x = 1; x <= all; x++)
{
for (int t = 1; t <= all; t++)
printf("%d\t", a[x][t]);
printf("\n\n\n");
}
printf("\n\n");
system("pause");
printf("\n\n");
}
void init()
{
scanf("%d", &mi);
n = mid = 1;
for (int x = 1; x < mi; x++)
n *= 2;
all = n * 2;
for (int x = 1; x <= all; x++)
a[1][x] = x;
printf("初始态:n=%d t=%d 第一行部署12345678\n", n, all);
}
int main()
{
//【小幂块】拆成4个【四分块】
//每次行部署完成,【小幂块】半径*2,【四分块】半径*2
//【小幂块】个数/2
init();
for (int x = 0; x < mi; x++) // 总轮次,每轮结束,处理完成 2 4 8 行
{
for (int t = 0; t < n; t++) //扫描【小幂块】(偏移 0 1 2 3)
{
for (int i = mid + 1; i <= 2 * mid; i++)
{ //【四分块】各点扫描(右下块)
for (int j = mid + 1; j <= 2 * mid; j++)
{
int y = t * mid * 2 + j; //偏移,得出真实的y
int sx = i - mid; //上行【四分块】的行数
a[i][y] = a[sx][y - mid]; //右下=左上
a[i][y - mid] = a[sx][y]; //左下=右上
}
}
} //【小幂块】个数/2,【四分块】半径*2
show(), n /= 2, mid *= 2;
}
return 0;
}
根号划分
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define s(a, l, r) sort(a + l, a + r + 1)
#define g(a, l, r) sort(a + l, a + r + 1, greater<int>())
const int N = 1e6 + 7;
int a[N], b[N], n;
void show()
{
for (int x = 0; x < n; x++)
printf("%d ", a[x]);
printf("\n");
}
void merge(int *a, int l, int r)
{
if (l >= r)
return;
int t = (int)sqrt(l - r + 1);
if (t > 1)
{
for (int x = 0; x < t; x++)
merge(a, l + x * t, l + (x + 1) * t);
merge(a, l + t * t, r);
//注意!末尾特判,因为可能不够凑出整个sqrt
} //实践过程使用STL武器库,
s(a, l, r); //改成g就是从大到小排序
}
int main()
{
scanf("%d", &n);
for (int x = 0; x < n; x++) //从1开始存
scanf("%d", &a[x]);
merge(a, 0, n - 1), show();
return 0;
}
/*
16
58 96 24 75 15 85 74 69 12 54 87 50 65 32 84 61
19
58 96 24 75 525 15 85 74 274 69 12 54 233 87 50 65 32 84 61
*/
整数因子分解
#include <iostream>
#include <stdio.h>
int res = 0, n;
void asd(int n)
{
if (n == 1)
res++;
for (int i = 2; i <= n; i++)
if (n % i == 0) //可以整除就向下分解
asd(n / i); //全排列过程
}
int main()
{
scanf("%d", &n), asd(n);
printf("%d", res);
return 0;
}
众数问题
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int a[N];
int zs, num;
void range(int *a, int n, int &l, int &r)
{
int mid = n / 2;
for (l = 0; l < n; l++)
if (a[l] == a[mid])
break;
for (r = l + 1; r < n; r++)
if (a[r] != a[mid])
break;
}
void asd(int *a, int n)
{
int l, r, mid = n >> 1;
range(a, n, l, r);
int now = r - l; //对减,得出元素的个数
if (now > num) //是否需要更新
num = now, zs = a[mid];
//如果左右两边,剩余空间,比当前【众数の个数】大,说明还有机会
if (l > num)
asd(a, l);//划掉与mid相等的数字,搜 L个(左区域L个)
if (n - r > num)
asd(a + r, n - r);//划掉mid相同数字,(右区域n-r个)
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
sort(a, a + n);
asd(a, n);
printf("众数:%d 个数:%d\n", zs, num);
return 0;
}
/*
48
2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6
*/
//划分思想:左侧残部(共l个)如果比当前【最大重数】大
//才有存在更大重数的可能性,才向下递归
//关于范围 1 2 2 2 4 5 距离
// L=1 R=4(实际要搜1 45两块)
// a:最左侧开始 l=1 搜1
// a+r: 4号位置开始 n-r=2搜45
递归二分
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int n, m, a[N], t;
int getL(int t, int l, int r)
{
if (l >= r)
return l;
int mid = l + r >> 1;
if (a[mid] >= t)
getL(t, l, mid);
else
getL(t, mid + 1, r);
}
int getR(int t, int l, int r)
{
if (l >= r)
return r;
int mid = l + r + 1 >> 1;
if (a[mid] <= t)
getR(t, mid, r);
else
getR(t, l, mid - 1);
}
int main()
{
scanf("%d%d", &n, &m);
for (int x = 0; x < n; x++)
scanf("%d", &a[x]);
while (m--)
{
scanf("%d", &t);
int l = getL(t, 0, n - 1);
if (a[l] != t)
{
printf("-1 -1\n");
continue;
}
int r = getR(t, 0, n - 1);
printf("%d %d\n", l, r);
}
return 0;
}
非递归二分
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int n, m, a[N];
//《模板示例》
//更新边界R在左侧,L在右侧
//[l,mid]-->r=mid (左侧) l=mid+1
//[mid,r]-->l=mid (右侧) r=mid-1
/*
二分搜索,应用于具有单调性的序列之中
现有一串不减序列,给到n个数字和m个询问
数组内编号0~n-1
输出所询问数字的启止范围
如果不存在输出-1 -1
*/
bool check(int a)
{ //满足性质的mid处在【左右】哪个区间
return true;
}
int T1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1; //无需+1型,mid在R位置
if (check(a[mid])) //此时满足的mid在右边区间,答案在左区间,更新R(左区间的右端点)
r = mid;
else
l = mid + 1;
}
return l;
}
int T2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1; //无需+1型,mid在R位置
if (check(a[mid])) //此时满足的mid在左边区间,答案在右区间,更新L(右区间的左端点)
l = mid;
else
r = mid - 1;
}
return l;
}
int main()
{
scanf("%d%d", &n, &m);
for (int x = 0; x < n; x++)
scanf("%d", &a[x]);
int t;
while (m--)
{
scanf("%d", &t);
int l = 0, r = n - 1;
while (l < r) //寻找左侧出发点
{
int mid = l + r >> 1;
if (a[mid] >= t)
r = mid;
else
l = mid + 1;
}
if (a[l] != t) //首次搜索都搜不到说明根本没有
{
printf("-1 -1\n");
continue;
}
printf("%d ", l); //否则,得出起始位置
l = 0, r = n - 1; //寻找右侧终止点
while (l < r)
{
int mid = l + r + 1 >> 1;
if (a[mid] <= t)
l = mid;
else
r = mid - 1;
}
printf("%d\n", r);
}
return 0;
}
/*
思想过程:【追寻左端点】
1.宏观lr步数,查询t输入
2.while,lr没有交叠
A.mid=l+r>>1(暂定)
思想论:从不满足性质的一侧开始找,部署【首个满足条件元素具有的性质】(左侧开始找,递增,找首个>=)
应该说左侧都是小的数字,右侧都是大的数字
B.关于 a[mid]的判断
a.首个满足性质的部署mid
思想论:mid满足性质,说明落在了右区间,我们要寻找的左端点应该在左区间找
向下取整时:左区间[l,mid] 右区间[mid+1,r]
向上取整时:左区间[l,mid-1] 右区间[mid,r]
跳出循环时候交叠,r在左侧,l在右侧
b.思考1:r=mid(mid在右区间,我们要去左区间找,所以更新【左区间的右端点r】)
c.反推1:l=mid+1
d.反推2:l+r>>1
思想论:mid踩在左侧r说明向下取整不用+1,mid踩在右侧l说明向上取整需要+1
结束的时候看一下a[l]不相等说明这玩意压根就不存在
我们的追踪方法会让l停留在【首个满足该性质的元素】位置上
思想过程:【追寻右端点】
1.宏观lr步数,查询t输入
2.while,lr没有交叠
A.mid=l+r>>1(暂定)
思想论:从不满足性质的一侧开始找,部署【首个满足条件元素具有的性质】(左侧开始找,递增,找首个>=)
应该说左侧都是小的数字,右侧都是大的数字
B.关于 a[mid]的判断
a.首个满足性质的部署mid
思想论:mid满足性质,说明落在了左区间,我们要寻找的右端点应该在右区间找
向下取整时:左区间[l,mid] 右区间[mid+1,r]
向上取整时:左区间[l,mid-1] 右区间[mid,r]
跳出循环时候交叠,r在左侧,l在右侧
b.思考1:l=mid(mid在左区间,我们要去右区间找,所以更新【右区间的左端点l】)
c.反推1:r=mid-1
d.反推2:l+r+1>>1
思想论:mid踩在右侧l说明向上取整,需要+1
*/
被遗弃的章节
1.简单的阶乘
#include <iostream>
#include <stdio.h>
using namespace std;
#define ull unsigned long long
ull JC(ull k)
{
if (k == 1) //终止条件
return 1;
return JC(k - 1) * k;
}
int main()
{
// n元素阶乘测试
ull n;
printf("nの阶乘,请输入n:");
scanf("%llu", &n);
printf("%llu", JC(n));
return 0;
}
2.简单的斐波那契数列
#include <stdio.h>
#include <iostream>
#include <math.h>
using namespace std;
#define ull unsigned long long
ull fib(ull k)
{
if (k <= 1)
return 1;
return fib(k - 1) + fib(k - 2);
}
int main()
{
//斐波那契测试
ull n;
printf("斐波那契测试,输入n:");
scanf("%llu", &n);
printf("%llu", fib(n));
return 0;
}