因为基础算法快学完了,图论又太难(我太蒻了),想慢慢学。

所以暂时不写关于算法的博客了,但又因为更新博客的需要,会多写写关于STL的博客。

(毕竟STL函数库还是很香的(手动滑稽))

请出今天主角:STL全排列函数prev_permutation()和next_permutation()

Part 1:引入和导语

首先,我们需要知道,algorithm库里有一些奇怪的函数。

我们在做题的时候,经常会遇到一些全排列的问题(bushi

这时我们通常要用递归搜索解决问题,但是那样会很麻烦,代码也会又臭又长,甚至更加danteng(不会写)

这时怎么办???好消息,万能的STL库又来拯救我们了!

dalao们创造了全排列函数prev_permutation()和next_permutation()

那么怎么用呢?

Part 2:用法和性质

首先,这两个函数都是bool类型的,区别在于:

prev_permutation() 求字典序比当前顺序小的第一个排列,如果有,返回1。如果当前排列已经是字典序最小的排列,返回0。排列结果存在所排列的数组里(会改变数组元素)

next_permutation() 求字典序比当前顺序大的第一个排列,如果有,返回1。如果当前排列已经是字典序最大的排列,返回0。排列结果存在所排列的数组里(会改变数组元素)

使用格式next_permutation(数组名+s,数组名+e)。s表示从第s个元素开始,e表示到第e个元素,对s-e间的元素进行排列。

prev_permutation()格式同上。

这两个函数可以对数字和字符进行全排列操作。

这里需要注意的是:如果你的顺序不是从大到小(或者从小到大)使用函数的时候,并不能生成全排列,只能生成从当前排列的字典序+-1的排列。

所以想要真正的全排列,我们需要先给元素排序(如果全是数字或者字母,用sort()就可以,看情况重载)

如果您不想用sort()可以先用全排列函数排列出字典序最小(或最大)再重新进行全排列。

Part 3:应用与实战

在了解了全排列函数的使用方法和规律后,我们应该拿几个题开刀,熟练一下。

于是我找到了以下几个题:

洛谷P1706 全排列问题

这个题可以说是全排列的板子题了。

题目很简单,输出从1到n的全排列数,只要用上我们的STL函数,代码简直短的不能再短,唯一的问题是,他要求右对齐,五个场宽。

这个可能会让一些萌新挠头,可能会有cout<<"(五个空格)";这种操作。(虽然我有时会做出这种zz操作哈哈哈毕竟我是蒟蒻)

但其实,没有那么复杂,这时格式化输入输出scanf和printf的好处就体现出来了。

在cstdio库里,我们可以这样操作:printf("%5d",a);这样输出,就是右对齐,空出5个场宽。

而且如果输出的数字要占两个场宽,会自动减少空出场宽的量,使输出始终右对齐。

ps:我们还可以printf("%-5d",a);这样是左对齐,空出5个场宽。其他性质与上述一样。

解决了这个问题,此题就变得非常简单,让dalao们康康我的代码:

//P1706
//#include<zhangtao.std>
#include<algorithm>
#include<cstdio>
using namespace std;
int num[];
int n;
int main()
{
scanf("%d",&n);
for(int i=;i<n;i++)
{
num[i]=i+;//赋值1-n
}
do
{
for(int j=;j<n;j++)
{
printf("%5d",num[j]);//输出被全排列的数组
}//全排列的结果会存在数组里(可以认为是调整数组顺序)
printf("\n");
}
while(next_permutation(num,num+n)!=)//当返回值为0(无下一个全排列)时,结束循环
return ;
}

洛谷P1088 火星人

先上题目:

题面比较英戳思婷(interesting),你要代表人类和火星人交流?(花里胡哨)

抛开这花里胡哨的题面,我们看本质,题目中给出样例:

123=1

132=2

213=3

231=4

312=5

321=6

很明显,这是1-3的全排列(6种),按字典序分别代表1-6。

题目中给出了排列顺序,给出的顺序代表1。

转化一下问题,其实就是让我们求当前数列按字典序升序的后面第n个排列是什么,所以我们可以用next_permutation()求解。

这样就很简单了,不用sort,我们直接对输入的数组进行n次next_permutation()全排列,然后输出操作后的数组就OK了。

让dalao们康康我的代码。

//P1088
//#include<zhangtao.std>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int finger[];
int len,shit;//不要在意变量名啊喂!
int main()
{
cin>>len>>shit;
for(int i=;i<len;i++)
{
cin>>finger[i];//输入数组顺序
}
for(int i=;i<shit;i++)
{
next_permutation(finger,finger+len);//进行n次全排列
}
for(int i=;i<len;i++)
{
cout<<finger[i]<<" ";//输出全排列后的数组
}
return ;
}

经过这两个题的历练,大家应该意识到全排列函数的方便之处。

下面我放一段大佬的代码,用递归写全排列函数。(注意与火星人那个题无关了!!!)

#include<bits/stdc++.h>
using namespace std;
int n,pd[],used[];//pd是判断是否用过这个数
void print()//输出函数
{
int i;
for(i=;i<=n;i++)
printf("%5d",used[i]);//保留五位常宽
cout<<endl;
}
void dfs(int k)//深搜函数,当前是第k格
{
int i;
if(k==n) //填满了的时候
{
print();//输出当前解
return;
}
for(i=;i<=n;i++)//1-n循环填数
{
if(!pd[i])//如果当前数没有用过
{
pd[i]=;//标记一下
used[k+]=i;//把这个数填入数组
dfs(k+);//填下一个
pd[i]=;//回溯
}
}
}
int main()
{
cin>>n;
dfs();//注意,这里是从第0格开始的!
return ;
}//摘自洛谷大佬Kai_Admin的题解

这样是深度优先搜索+回溯算法的解析,大佬Kai_Admin的代码注解很详细,我这个蒟蒻就不多BB了。

Part 4:全排列函数战术骗分

民间流传着这样一首诗:

骗分过样例,暴力出奇迹。

爆搜挂着机,打表出省一。

这首诗歌颂的是骗分策略。(当然我个人还是建议不要骗分啦)

但是我们有时想不出正解,就不得不用这种令(十)人(分)作(有)呕(效)的手段。

我们常常会遇到一些关于全排列的题目,如果想不出正解,一定不要忘了用这两个全排列函数枚举。

虽然是暴力枚举,但是能拿分,我们永远不嫌分多,对吧?

下面教大家如何有效的骗分。

对于大部分题而言,骗分是一场与时间复杂度之间的较量,是一场与TLE之间的对决。

在确定骗分策略,开始写代码之前,我建议:

1、检查算法复杂度。确定自己的骗分策略可以应付多大的数据,有没有更好的骗分策略(比如打表等),或者你的骗分算法可不可以优化。

2、大概能骗到多少分。如果你对你的骗分算法的时间复杂度很没有把握,那就要看看有没有样例分,要不要直接输出“-1”。(有的题不存在解法会要求输出“-1”)

3、够不够暴力。如果你的骗分策略很复杂,很难写,甚至可能比正解还要冗长,建议把时间留到后面的题,本题直接拿样例分即可。

确定了以上几点,我们就可以进行骗分执法了,下面上一个可以骗分的题目。

可怜的骗分板子题:

洛谷P4163 排列(2007四川OI省选)

给大家看看正解思路吧

没错,就是状态压缩动态规划!

下面是AC代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int n,d,S,ans,num[],f[],a[],b[<<],c[],dp[(<<)+][];//S为全集,ans为答案,num为数字,f为阶乘,a为10的幂,b为每种状态选了多少个数字,c为计数数组,dp为状压DP数组
inline int read(){//读优
int date=,w=;char c=;
while(c<''||c>''){if(c=='-')w=-;c=getchar();}
while(c>=''&&c<=''){date=date*+c-'';c=getchar();}
return date*w;
}
int sum(int x){//求状态x选了多少个数字
int y=;
while(x){
if(x&)y++;
x>>=;
}
return y;
}
void make(){//初始构造阶乘,10的幂,状态的含义
f[]=a[]=;//从0开始。。。
for(int i=;i<=;i++){
f[i]=f[i-]*i;
a[i]=a[i-]*;
}
for(int i=;i<(<<);i++)b[i]=sum(i);
}
void work(){//工作
for(int i=;i<S;i++)//枚举状态
for(int j=;j<n;j++)
if((<<j)&i){//判定合法
int k=a[b[i]-]%d*num[j+]%d;//求加上j后的影响
for(int l=;l<d;l++)dp[i][(l+k)%d]+=dp[i^(<<j)][l];//转移
}
ans=dp[(<<n)-][];//记录答案
for(int i=;i<=n;i++)c[num[i]]++;
for(int i=;i<=;i++)ans/=f[c[i]];//去重
printf("%d\n",ans);//输出
}
void init(){//读入+预处理
char ch[];
scanf("%s",ch);d=read();
n=strlen(ch);
for(int i=;i<n;i++)num[i+]=ch[i]-'';//转化成数字
memset(dp,,sizeof(dp));
memset(c,,sizeof(c));//别忘了清空。。。
S=<<n;
dp[][]=;//初始化
}
int main(){//主函数So easy!
make();
int t=read();
while(t--){
init();
work();
}
return ;
}//摘自洛谷大佬斯德哥尔摩的题解

对于大佬的代码,我也不多说,毕竟本蒟蒻根本看不懂,只能%%%。

显然,像我一样的蒟蒻们是不可能会如此高端的算法的。

我们虽然不会正解,但是就真的没法做了吗?

显然不!!!

既然题目中提到了全排列,我们就可以利用全排列函数做点什么。

题目中要求:给出一个仅包含数字0-9的字串和n,要求求出这个字串的全排列中有几个数可以整除n。

我们可不可以直接枚举这个字串的全排列呢?

先检查算法复杂度:

我们先考虑复杂度最坏的情况。

对于0-9这10个数的全排列,一共有10的阶乘共计3628800个,如果加上分离十位数和mod运算的话,复杂度再乘以11。

大概是四千万左右。(9位数仅仅是几百万,8位数几十万,相当于四千万的零头,所以不考虑了。)

题目中给出的时间限制是500ms=0.5s,计算机一秒钟可以运算3-8亿次。

也就是说我们最大最大可以接受的数据范围是:9组10位数的数据。

很不错,这甚至已经接近正解了,毕竟题目中给出的最大数据仅仅有15组。

下面说一下骗分思路:

我们对于每一组输入的数据进行sort排序,使其从小到大排列,然后进行next_permutation()求下一个字典序排列。

再把数组转化成数字(这里要开longlong,因为10位数已经超出了int的表示范围)对给出的数进行模运算,如果运算结果为0,答案++,最后输出答案。

//P4163
//#include<zhangtao.std>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
char dd[];//定义一个字符数组,用来存输入数据(因为输入数据没有空格)
int num[];//用来把字符数组转化为数字来方便操作
long long x,lenth,mod,n,ans;//不开longlong见祖宗,博文中提到过原因
int main()
{
scanf("%lld",&n);//输入一共几组数据
for(int i=;i<n;i++)
{
scanf("%s%lld",dd,&mod);//格式化输入字符数组和mod
lenth=strlen(dd);//strlen()函数检测字符数组长度
for(int j=;j<lenth;j++)
{
num[j]=dd[j]-'';//字符转化数字
}
sort(num,num+lenth);//数组从小到大排序,为next_permutation()做准备
//你也可以写cmp函数,从大到小排序。然后用prev_permutation()函数,但是我没那么优秀(闲)
do
/*do-while语句,如果使用while(next_permutation(num,num+lenth)!=0)
会导致第一种排列方式(从小到大)枚举不到,因为检查条件的时候直接操作了一次*/
{
for(int j=;j<lenth;j++)
{
x=x*+num[j];//数组转数字
}
if(x%mod==) ans++;//满足条件,答案++
x=;//清零变量
}
while(next_permutation(num,num+lenth)!=);
//如果还有下一个全排列(函数返回值不为0)进行循环枚举
printf("%lld\n",ans);//输出答案
ans=;//清空答案,准备下次循环枚举
}
return ;
}

以上是骗分代码,注意它并不是正解!!!

众所周知,洛谷的评测机在不同时间运行速度是不一样的,在人多的时候11个测试点可以AC9个点。

夜深人静的时候,可以AC10个点(第11个测试点495ms读毫秒通过)。

除了第一组数据过于毒瘤,其他全部安全通过,可以说是一套效率很高的骗分策略了。

总而言之,遇到全排列问题的时候,千万不要忘记这两个兄弟:

prev_permutation()

next_permutation()

今天的学习总结就到这里。另外,快开学了,祝大家额,身(少)体(掉)健(头)康(发)吧。

STL函数库的应用第四弹——全排列(+浅谈骗分策略)的更多相关文章

  1. 分治算法(二分查找)、STL函数库的应用第五弹——二分函数

    分治算法:二分查找!昨天刚说不写算法了,但是突然想起来没写过分治算法的博客,所以强迫症的我…… STL函数库第五弹——二分函数lower_bound().upper_bound().binary_se ...

  2. STL函数库的应用第三弹——数据结构(栈)

    Part 1:栈是什么 栈(stack)又名堆栈,它是一种运算受限的线性表.限定仅在表尾进行插入和删除操作的线性表. 这一端被称为栈顶,相对地,把另一端称为栈底. 向一个栈插入新元素又称作进栈.入栈或 ...

  3. ----堆栈 STL 函数库 ----有待补充

    #include<cstdio> #include<string> #include<vector> #include<iostream> using ...

  4. STL函数库的应用第一弹——数据结构(队列)

    队列是什么? 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作. 和栈一样,队列是一种操作受限制的线性表.进行插入操作的端称为队尾,进行删除操作的端称为队头 ...

  5. STL函数库的应用第二弹——快排sort函数与结构体关键字排序

    时隔20多天,本蒟蒻终于记起了他的博客园密码!!! 废话不多说,今天主题:STL快排函数sort()与结构体关键字排序 Part 1:引入和导语 首先,我们需要知道,algorithm库里有一些奇怪的 ...

  6. C++STL标准库学习笔记(四)multiset续

    自定义排序规则的multiset用法 前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标记了出来,只不过这一次的笔记主要是我的补充 ...

  7. Android项目实战(四十四):浅谈Postman (网络请求调试插件)

    前言: Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件.    在项目开发中,可以依赖此工具模拟API测试. 使用详解: 各种情况Api的模拟请求的Postman使用方 ...

  8. Salesforce LWC学习(二十四) Array.sort 浅谈

    本篇参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort sal ...

  9. ajax的使用:(ajaxReturn[ajax的返回方法]),(eval返回字符串);分页;第三方类(page.class.php)如何载入;自动加载函数库(functions);session如何防止跳过登录访问(构造函数说明)

    一.ajax例子:ajaxReturn("ok","eval")->thinkphp中ajax的返回值的方法,返回参数为ok,返回类型为eval(字符串) ...

随机推荐

  1. 使用iOS网络请求

    https://github.com/yuantiku/YTKNetwork/blob/master/Docs/2.0_MigrationGuide_cn.md

  2. MySQL(一)简介与入门

    一.数据库简介 这个博客详细介绍:http://www.cnblogs.com/progor/p/8729798.html 二.MySQL的安装 这个博客详细介绍:https://blog.csdn. ...

  3. 深入探究JVM之垃圾回收器

    @ 目录 前言 正文 一.垃圾收集算法 标记-复制 标记-清除 标记-整理 分代回收 二.常用的垃圾回收器 Serial/SerialOld ParNew Parallel Scavenge/Para ...

  4. Java基础加强笔记——测试、反射、注解

    目录 1. Junit单元测试 2. 反射 3. 注解 Junit单元测试: 测试分类: 1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值. 2. 白盒测试:需要写代码的.关注程序具 ...

  5. python获取主机IP,主机名

    获取主机内网,外网IP,主机名 代码如下: #!/usr/bin/env python #-*- coding:utf-8 -*- # author:Zeng Xianhe import socket ...

  6. 蜻蜓点水说说Redis的String的奥秘

    本篇博客参考:掘金Redis小册 敖丙 如果面试官问你,单线程的Redis为什么那么快,你可能脱口而出,因为单线程,避免上下文切换:因为基于内存,比硬盘读写快很多:因为采用的是多路复用网络模型.不管你 ...

  7. leetcode 5473

    这个题真是当时想麻烦了,,,感谢LLdl 提供的题解 其实一个很重要的点就是,如果后面的玩意翻转了偶数次,那就跟没变一样.如果是奇数次就取反. 怪我天真,第一反应就去位运算去了,,,,哪有那么复杂诶 ...

  8. ionic 侧边栏实例

    侧边栏的使用范例: <body > <ion-side-menus> <!-- 中间内容 --> <ion-side-menu-content ng-cont ...

  9. Python os.lseek() 方法

    概述 os.lseek() 方法用于设置文件描述符 fd 当前位置为 pos, how 方式修改.高佣联盟 www.cgewang.com 在Unix,Windows中有效. 语法 lseek()方法 ...

  10. Python decode()方法

    描述 Python decode() 方法以 encoding 指定的编码格式解码字符串.默认编码为字符串编码.高佣联盟 www.cgewang.com 语法 decode()方法语法: str.de ...