目录
P1088 [NOIP2004 普及组] 火星人 - 全排列函数
leetcode 题号:238. 除自身以外数组的乘积 - 前后缀
P1088 [NOIP2004 普及组] 火星人 - 全排列函数
输入 #1
5
3
1 2 3 4 5
输出 #1
1 2 4 5 3
小知识:全排列 (next_permutation)
对于next_permutation函数,其函数原型为:
#include <algorithm>
bool next_permutation(iterator start,iterator end)
当当前序列不存在下一个排列时,函数返回false,否则返回true
数字
next_permutation(num,num+n)函数是对数组num中的前n个元素进行全排列,同时并改变num数组的值。如例2
另外,需要强调的是,next_permutation()在使用前需要对欲排列数组按升序排序,否则只能找出该序列之后的全排列数。如例1
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int num[3]={1,2,3};
//int num[3]={2,1,3}; //例1
//int num[6]={1,2,3,4,5,6}; //例2
do
{
for(int i=0;i<sizeof(num)/4;i++)
cout<<num[i]<<" ";
puts("");
}while(next_permutation(num,num+3));
return 0;
}
例1:
2 1 3
2 3 1
3 1 2
3 2 1
例2:
1 2 3 4 5 6
1 3 2 4 5 6
2 1 3 4 5 6
2 3 1 4 5 6
3 1 2 4 5 6
3 2 1 4 5 6
字符串
next_permutation是一个原地算法(会直接改变这个集合,而不是返回一个集合),它对一个可以遍历的集合(如string,如vector),将迭代器范围 [first, last] 的排列 排列到下一个排列。
也就是说,可以排序字符串;
字符串“123”的按照字典序正确的全排列是:"123" "132" "213" "231" "312" "321"
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
//string number = "213";
string number = "abc";
do
{
cout << number << endl;
}while(next_permutation(number.begin(), number.end()));
return 0;
}
输出:
213
231
312
321
abc
acb
bac
bca
cab
cba
值得注意的是,空格也是字符,也会一起全排序
所以,该题根本不要思考,直接秒杀
#include<bits/stdc++.h>
using namespace std;
int a[10005],n,m;
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) cin>>a[i];
while(m--) next_permutation(a,a+n);
for(int i=0;i<n;i++) printf("%d ",a[i]);
return 0;
}
最大乘积 - 蓝桥杯国赛
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
int num[10], arr[12];
int maxv = 0, ret,j,x,y;
int js(int l, int r)
{
int t = 0;
for (int i = l; i <= r; i++)
t = t * 10 + num[i];
return t;
}
int main()
{
for (int i = 0; i < 9; i++) num[i] = i + 1;
do
{
for (int i = 0; i < 9; i++)
{
int a = js(0, i);
int b = js(i + 1, 8);
ret = a * b;
//cout<<a<<" "<<b<<" "<<ret<<endl;
memset(arr,0,sizeof(arr));
while (ret)
{
arr[ret%10]++;
ret /= 10;
}
for (j = 1; j <= 9; j++) if (arr[j]!=1) break;
if (j == 10)
{
//cout << a * b<<endl;
maxv = max(maxv, a * b);
x=a;
y=b;
//cout<<x<<" "<<y<<" "<<maxv<<endl;
}
}
} while (next_permutation(num, num + 9));
cout<<x<<" "<<y<<" "<<maxv<<endl;
return 0;
}
AcWing 2067. 走方格 - DP优化暴搜
第十一届蓝桥杯省赛第一场C++A/B组
输入样例1:
3 4
输出样例1:
2
输入样例2:
6 6
输出样例2:
0
爆搜 O(2^(m+n))
直接从 (1,1) 开搜,每搜到 (n,m) 一次,ans ++一次。
直接超时....
#include<iostream>
using namespace std;
int n,m;
int ans;
void dfs(int x,int y)
{
if(x&1||y&1)
{
if(x==n&&y==m)
{
ans++;
return ;
}
if(x<n) dfs(x+1,y);
if(y<m) dfs(x,y+1);
}
}
int main()
{
cin>>n>>m;
dfs(1,1);
cout<<ans<<endl;
return 0;
}
最简单方法:DP
f[i][j]表示(1,1)走到(i,j)的集合;
对于从上往下走, 有1 ~ i 种方式,即i-1种;从左往右同理
模板做法: if(确认边界) else (动态规划公式)
#include<bits/stdc++.h>
using namespace std;
const int N=40;
int n,m;
int f[40][40];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(i==1||j==1) f[i][j]=1; //定边界
else
{
if(i&1||j&1)
f[i][j]=f[i-1][j]+f[i][j-1];
}
}
cout<<f[n][m];
return 0;
}
#include<iostream>
using namespace std;
int n,m;
int dp[35][35];
int main()
{
cin>>n>>m;
dp[1][1]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(i%2!=0||j%2!=0)
dp[i][j]+=dp[i-1][j]+dp[i][j-1];
//+=:当前状态只能是从上边或者左边来,那么当前总方案数就是这两状态之和
cout<<dp[n][m];
}
#include<iostream>
using namespace std;
int n,m;
int dp[35][35];
int main()
{
cin>>n>>m;
dp[1][1]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if((i%2!=0||j%2!=0)&&(i!=1||j!=1)) //如果ij不是同时偶且如果ij不是同时等于1
dp[i][j]=dp[i-1][j]+dp[i][j-1];
cout<<dp[n][m];
}
leetcode 题号:169. 多数元素 - 抵消法
- 一个数组中有一个数字出现次数大于 n/2 ,从第 0 个字符开始,假设它就是最多的那个数字,遇到相同的数字则计 数 +1 ,遇到不同的则计数 -1 ,其实就是互相消耗,等到计数为 0 的时候,表示本次互拼完毕,从下一个字符重新开 始互拼,但是归根结底出现次数大于 n/2 的这个数字数量更多,因此也是最后保留的字符。
- 示例: "23335" 首先从字符 2 开始计数 1 ,遇到 3 ,不同则 -1 ,互拼消耗 重新从剩的"335" 开始的过程,这时候保存的字符为 3 ,遇到 3 则计数 +1 , 遇到5则计数 -1 ,在计数不为 0 时,走到末尾保存的字符就是个数超过 n/2 的字符
int majorityElement(int* nums, int numsSize){
int c=1;
int tmp=nums[0];
for(int i=1;i<numsSize;i++)
{
if(tmp==nums[i]) c++;
else c--;
if(c==0) tmp=nums[i+1];
}
return tmp;
}
leetcode 题号:238. 除自身以外数组的乘积 - 前后缀
思路:
计算左侧乘积:第 0 个元素的左边乘积, arr[0] = left 然后计算第 1 位左侧乘积 left*=nums[0] -> left = 1*2第 1 个元素的左边乘积, arr[1] = left 然后计算第 2 位左侧乘积 left*=nums[1] -> left = 1*2*3第 2 个元素的左边乘积, arr[2] = left 然后计算第 3 位左侧乘积 已经没必要了,因为第2 元素是末尾元素了一次循环完毕后,返回数组中每个元素存储的都是自己左侧元素的乘积。 arr[] 中的值: [1,2,6]
计算右侧乘积:第 2 个元素的右边乘积, arr[2] *= right 然后计算第 1 位右侧乘积right*=nums[2] -> right =1*4第 1 个元素的右边乘积, arr[1] *= right 然后计算第 0 位右侧乘积right*=nums[1] -> right =1*4*3第 0 个元素的右边乘积, arr[0] *= right 然后计算第 -1 位右侧乘积 -1 位已经不需要计算了循环完毕后,返回数组中的每个元素都是其他元素的乘积了 arr[2]*=1 ; arr[1]*=4; arr[0]*=12
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* productExceptSelf(int* nums, int numsSize, int* returnSize){
int *ret = (int *)malloc(numsSize * sizeof(int));
*returnSize = numsSize;
int left=1,right=1;
for(int i=0;i<numsSize;i++)
{
ret[i]=left;
left*=nums[i];
}
for(int i=numsSize-1;i>=0;i--)
{
ret[i]*=right;
right*=nums[i];
}
return ret;
}
相同思想,前后缀同时进行
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n=nums.size();
vector<int>ans(n,1);
int prefix=1,suffix=1;
for(int i=0;i<n;i++){
ans[i]*=prefix;
ans[n-i-1]*=suffix;
prefix*=nums[i];
suffix*=nums[n-i-1];
}
return ans;
}
};
牛客:HJ31 单词倒排
使用了scanf函数的读入指定字符集的功能
#include <cstdio>
#include<iostream>
using namespace std;
int main(){
char str[100][22];
int i=0;
int x;
while(1){
x=scanf("%[a-z|A-Z]",str[i]); //符合条件,返回1,不符合返回0
if(getchar()=='\n') break;
if(x) i++;
}
for(int j=i;j>=0;j--){
printf("%s ",str[j]);
}
return 0;
}
isalpha()判断一个字符是否是字母
#include <cstdio>
#include<iostream>
using namespace std;
int main()
{
string s;
getline(cin,s);
int len = s.size();
int p1 = len - 1, p2 = len; //从后向前
while(p1 >= 0)
{
while(p1 >= 0 && !isalpha(s[p1]))
p1--;
p2 = p1;
while(p1 >= 0 && isalpha(s[p1]))
p1--;
for(int i = p1 + 1; i <= p2; i++) //完全模拟
printf("%c", s[i]);
printf(" ");
}
return 0;
}
四平方和 - 蓝桥杯第七届【A组】
对于这种多循环问题,我们采取循环出n-1个未知数, 最后一个未知数用前面的n-1个未知数推出,这样可以节约大量时间!
80分(超时)
#include<iostream>
#include<cmath>
using namespace std;
int n;
int main()
{
cin>>n;
for(int i=0;i<=sqrt(n);i++)
{
for(int j=i;j<=sqrt(n);j++)
{
for(int k=j;k<=sqrt(n);k++) //对于升序输出,可以i=0,j=i,k=j....,省时
{
int l=sqrt(n-i*i-j*j-k*k); //最后一个未知数直接算出
if(i*i+j*j+k*k+l*l==n)
{
printf("%d %d %d %d",i,j,k,l);
return 0;
}
}
}
}
return 0;
}
AC:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int a,b,c,d;
int n;
int main (){
scanf ("%d",&n);
for(int a=0;(a*a<<3)<=n;a++)
for(int b=a;(b*b<<2)<=n-a*a;b++)
for(int c=b;(c*c<<1)<=n-a*a-b*b;c++){
int d = (int)sqrt(n-a*a-b*b-c*c);
if(a*a+b*b+c*c+d*d==n) {
printf ("%d %d %d %d\n",a,b,c,d);
return 0;
}
}
return 0;
}
7-3 英文单词排序 (10 分)
这题看似简单,实则是真的绝;
刚开始想到:
#include<bits/stdc++.h>
using namespace std;
bool cmp(string a,string b)
{
return a.size()<b.size();
}
int main()
{
int j;
string s[100];
int i=0;
while(1)
{
cin>>s[i];
if(s[i]=="#")
break;
i++;
}
sort(s,s+i,cmp);
for(j=0;j<i;j++)
cout<<s[j]<<" ";
}
但是有一个测试点过不去;测试了很久很久发现,sort具有不稳定性 ,就是当单词长度相同时不能保证按照输入的顺序不变
尝试使用冒泡排序:
#include<iostream>
using namespace std;
int main(){
string s;
string s1[25];
int i=0;
cin>>s;
while(s!="#"){
s1[i]=s;
//cout<<s1[i]<<i<<endl;
i++;
cin>>s;
}
for(int a=0;a<i-1;a++){//sort 不稳定 试试冒泡
for(int b=i-1;b>a;b--){
if(s1[b-1].size()>s1[b].size()){
s=s1[b];
s1[b]=s1[b-1];
s1[b-1]=s;
}
}
}
for(int a=0;a<i;a++){
cout<<s1[a]<<" ";
}
return 0;
}
过是过了,但是感觉十分麻烦,而且复杂度提升,于是上网搜了一下如何让sort稳定;
用STL stable_sort 稳定排序:(果然stl大法好呀)
#include<bits/stdc++.h>
using namespace std;
bool cmp(string a,string b)
{
return a.size()<b.size();
}
int main()
{
int j;
string s[100];
int i=0;
while(1)
{
cin>>s[i];
if(s[i]=="#")
break;
i++;
}
stable_sort(s,s+i,cmp); //直接前加stable_
for(j=0;j<i;j++)
cout<<s[j]<<" ";
}
7-6 一帮一 - struck
acm真题
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=55;
struct str{
int s;
int f;
char name[10];
}mes[maxn];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%s",&mes[i].s,mes[i].name);
mes[i].f=0;
}
for(int i=0;i<n;i++){
for(int j=n-1;j>=0;j--){
if(mes[i].s+mes[j].s==1&&mes[i].f==0&&mes[j].f==0){
printf("%s %s\n",mes[i].name,mes[j].name);
mes[i].f=1;
mes[j].f=1;
}
}
}
return 0;
}
519. 跳石头
#include <iostream>
using namespace std;
const int N=1e5+10;
int L,n,m;
int a[N];
int check(int mid)
{
int last=0,c=0;
for(int i=1;i<=n;i++)
{
if(a[i]-last<mid) c++;//如果这一块石头和上一块石头的距离比x小,计数+1。而且如果石头移走,last还是上一块石头的位置。
else last=a[i];//否则这块石头就不必移走,last=这块石头的位置。
}
return c<=m;
}
int main()
{
cin>>L>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];//a[0]为第一快石头
a[++n]=L; //表示最后一块石头,只是方便理解
int l=0,r=L;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l;
return 0;
}