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

所以暂时不写关于算法的博客了,但又因为更新博客的需要,会多写写关于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. 什么是viewstate,能否禁用?是否所用控件都可以禁用

    viewstate用于在两次postback之间保持状态的一种机制禁用viewstate将不能在回发之间保存状态 当控件状态无关使用viewstate将造成性能问题时需要禁用viewstate Vie ...

  2. 题解 洛谷 P4112 【[HEOI2015]最短不公共子串】

    给定两个字符串\(A\)和\(B\),我们需要找出一个串,其在\(A\)中出现且不在\(B\)中出现,这个串为子串或者子序列,求在每种情况下,该串的最短长度. 考虑到后缀自动机可以识别一个字符串的所有 ...

  3. Java 并发实践 — ConcurrentHashMap 与 CAS

    转载 http://www.importnew.com/26035.html 最近在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,以及CAS的一些概念. ...

  4. Python3网络爬虫开发实战PDF高清完整版免费下载|百度云盘

    百度云盘:Python3网络爬虫开发实战高清完整版免费下载 提取码:d03u 内容简介 本书介绍了如何利用Python 3开发网络爬虫,书中首先介绍了环境配置和基础知识,然后讨论了urllib.req ...

  5. Microsoft Cloud App Security 微软的云应用安全

    1.概述 微软2015年收购的一家云安全创业公司 Adallom 正式推出产品,同时更名为微软 Cloud App Security.Adallom 成立于 2012年,是一家 SaaS 云安全公司, ...

  6. JVM系列之:String.intern的性能

    目录 简介 String.intern和G1字符串去重的区别 String.intern的性能 举个例子 简介 String对象有个特殊的StringTable字符串常量池,为了减少Heap中生成的字 ...

  7. manual for emacs markdown-mode(English)

    markdown-mode now requires Emacs 24.3 or later. Markup insertion and replacement keybindings under C ...

  8. python基础全部知识点整理,超级全(20万字+)

    目录 Python编程语言简介 https://www.cnblogs.com/hany-postq473111315/p/12256134.html Python环境搭建及中文编码 https:// ...

  9. Seaborn基础3

    import seaborn as sns import numpy as np import matplotlib.pyplot as plt sns.set(rc = {"figure. ...

  10. PHP array_walk_recursive() 函数

    实例 对数组中的每个元素应用用户自定义函数: <?phpfunction myfunction($value,$key){echo "The key $key has the valu ...