注意
注意数据范围关系到时间复杂度,还有取值范围决定最大值的取值。
记住零的存在,只有一次机会。
填空题仔细看题,仔细看题,仔细看题
机会只有一次,好多隐藏条件,真坏。
注意数据的取值范围,不要随便设置Max和Min的值,最好直接和数据有关(i==0时进行Max和Min赋值)。
注意零的存在,考察的细节很多。
一道细节题
按照蓝桥杯的规则提交上不能知道对错,题目很简单但是我却在细节上错了两次。
国赛的时候注意了,为啥感觉存在问题,却又主观忽略呢?
对于一维dp先自己进行手模查找规律(斐波那契数列),简单点的应该就没问题了。
//当只能用cin输入string的时候,注意优化。
std::ios::sync_with_stdio(false);
//使用之后最好就不要和scanf混用了。
注意看看挑战程序竞赛关于dp的知识点,尽量水一些样例吧。
一定不要放弃,暴力一般能做出来,能水点样例就水点样例。
注意多次重复使用priority_queue还有其他功能函数时记得将以前的数据pop掉
priority_queue<int,vector,greater >是从小到大排序。
默认是从大到小。
//重载小于号,这样就可以用优先队列操作结构体了。
friend bool operator < (node a,node b){
return a.v<b.v;
}
algorithm
全排列函数
标题:密文搜索
福尔摩斯从X星收到一份资料,全部是小写字母组成。 他的助手提供了另一份资料:许多长度为8的密码列表。
福尔摩斯发现,这些密码是被打乱后隐藏在先前那份资料中的。请你编写一个程序,从第一份资料中搜索可能隐藏密码的位置。要考虑密码的所有排列可能性。
数据格式:
输入第一行:一个字符串s,全部由小写字母组成,长度小于1024*1024 紧接着一行是一个整数n,表示以下有n行密码,1<=n<=1000
紧接着是n行字符串,都是小写字母组成,长度都为8要求输出: 一个整数, 表示每行密码的所有排列在s中匹配次数的总和。
例如:
用户输入:
aaaabbbbaabbcccc
2
aaaabbbb
abcabccc
则程序应该输出:
4
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
std::ios::sync_with_stdio(false);
string S;
cin>>S;
int n;
cin>>n;
string s;
long long sum=0;
while(n--){
cin>>s;
sort(s.begin(),s.end());//如果不排序,情况可能会减少。
//输出按照字典序从小到大排序,且不会重复。
do{
if(S.find(s)!=string::npos)sum++;
}while(next_permutation(s.begin(),s.end()));
}
cout<<sum<<endl;
return 0;
}
做了几道题发现蓝桥杯对于取模后的余数的骚操作很多啊
字符串
字符串的子序列
abc的子序列是所有元素的有序组合。
c
b
bc
a
ac
ab
abc
剪枝
暴力剪枝
不是亲眼所见,不敢相信
己方暴力
上手就是for循环,剪枝?不存在的。
敌方暴力
物理加法术暴击
思维加算法优化。
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node{
long long d,t;
}a[100010];
bool cmp(node a,node b){
return a.d<b.d;
}
long long c[1010000];
long long num[1010000];
int main(){
long long n,l,cnt;
scanf("%lld%lld",&n,&l);
cnt=0;
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i].d,&a[i].t);
//得到每个点对应的总人数
num[i]=num[i-1]+a[i].t;
}
// sort(a+1,a+n+1,cmp);感觉还是带上好,毕竟没有说明是按照顺序输入的。
for(int i=n;i>=1;i--){
//为后面的s4做准备。
c[i]=c[i+1]+(l-a[i].d)*a[i].t;
}
long long s1=0,s2=0,s3=0,s4=0;
long long Min=c[1];//最远距离
//第一次剪枝:当集合点落在居民家中的时候才能作为合理答案讨论。
for(long long i=1;i<=n-2;i++){
s1+=num[i-1]*(a[i].d-a[i-1].d);
//第二次剪枝:当数据递增过程中数据大于已知则重新开始新的样例。
if(s1>Min)continue;
s2=0;
for(long long j=i+1;j<=n-1;j++){
//第三次剪枝:将每次循环都利用上进行累计。
s2+=(num[j-1]-num[i])*(a[j].d-a[j-1].d);
if(s1+s2>Min)continue;
s3=0;
for(long long k=j+1;k<=n;k++){
s3+=(num[k-1]-num[j])*(a[k].d-a[k-1].d);
s4=c[k+1];
if(s1+s2+s3+s4<Min)Min=s1+s2+s3+s4;
}
}
}
cout<<Min<<endl;
return 0;
}
暴力常识
long long 十的18次方
int 十的9次方
数组大于十的6次方的需要定义在函数外面
1S时间复杂度
- 10的六次方轻轻松松
- 10的七次方勉强
- 10的八次方悬
数论
排列组合
等差等比数列
素数定义
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
素数筛
求一个数所有因子的个数,因子和
- 所有自然数都是可以拆解为多个质数的乘积并且每个自然数可以拆解的质数因子个数和数值是确定的,可以借助这些质数因子任意组合相乘得到自然数的全部因子数值。
- 因为若非质数则必然存在因子,若因子非质数则必然存在因子,如此拆分下去,拆到最后只会剩下质数因子相乘得到起初的自然数。
求一个数所有因子的个数,因子和
年月日30/31天
一年中是31天的月份有7个,是30天的月份有4个。
一年中是31天的月份有1月、3月、5月、7月、8月、10月、12月一共七个月,是30天的月份有4月、6月、9月、11月一共4个月。
而闰年2月有29天,平年2月有28天
闰年
1、能被4整除,但不能被100整除;
2、能被400整除;
闰年366天 平年365
闰年二月29平年二月28
等差数列
等差数列的应用
暴力流打法
二进制巧用
从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。
通过二进制的特性枚举所有方案。
此特性还可以用于枚举某一问题的所有情况。
for(long long i=0;i<1<<n;i++){
for(long long j=0;j<n;j++){
if(i>>j&1){//i右移j位判断,第j+1位是否为1;
}
}
}
把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。
这里不能随意排序,因为已经出现的组合不能再次出现。
for(long long i=0;i<n;i++){
for(long long j=i+1;j<n;j++){
for(long long k=j+1;k<n;k++){
}
}
}
归并排序
需要通过局部有序推理结果,所以快排行不通
归并:局部推全局
快排:全局到局部
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[1000000];
int t[1000000];
long long sum=0;
void merge_sort(int l,int r){
if(l>=r)return;
int mid=(l+r)/2;
merge_sort(l,mid),merge_sort(mid+1,r);
int k=0,j=mid+1,i=l;
while (i <= mid && j <= r)
if(a[i]>a[j])t[k++]=a[j++],sum+=mid-i+1;
else t[k++]=a[i++];
while(j<=r)t[k++]=a[j++];
while(i<=mid)t[k++]=a[i++];
for(int i=l,k=0;i<=r;i++)a[i]=t[k++];
}
int main(){
long long n;
cin>>n;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
merge_sort(0,n-1);
cout<<sum<<endl;
return 0;
}
并查集(查找图中的环)
注意要在输入的时候查询是否能构成环。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
vector<int>v[100001];
int a[1000010];
int x[1001001];
int s,e,cnt;
bool flag=false;
void init(int n){
for(int i=1;i<=n;i++)x[i]=i;
}
int father(int y){
if(x[y]==y)
return y;
//查找并且顺便更新根节点
return x[y]=father(x[y]);
}
void def(int nod,int pre,int c){
if(nod==e){
flag=true;
cnt=c;
return;
}
for(int i=0;i<v[nod].size();i++){
//解决无向图,重复节点死循环问题
if(v[nod][i]==pre)continue;
a[c]=v[nod][i];
def(v[nod][i],nod,c+1);
if(flag)return;
}
}
int main(){
int n;
int a1,b1;
int fa,fb;
cin>>n;
//注意初始化节点
init(n);
for(int i=0;i<n;i++){
scanf("%d%d",&a1,&b1);
fa=father(a1);
fb=father(b1);
//在两点连通之前没有其余的通路
if(fa!=fb){
x[fa]=fb;
//避免连接环,放置出现直接a1~b1的情况。
v[a1].push_back(b1);
v[b1].push_back(a1);
}
//两点连通之前就存在通路(同一个父节点)
else {
s=a1,e=b1;
}
}
a[0]=s;
def(s,-1,1);
sort(a,a+cnt);
for(int i=0;i<cnt;i++){
if(i==0)printf("%d",a[i]);
else printf(" %d",a[i]);
}
return 0;
}
线段树
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
struct node{
int l,r,num;
}d[1000000];
int a[100000];
int cnt=1;
void ff(int x){
d[x].num=max(d[x<<1].num,d[x<<1|1].num);
}
void build(int x,int l,int r){
d[x].l=l;
d[x].r=r;
if(l==r){
d[x].num=a[r];
return;
}
int mid=l+r>>1;
//mid是左子树的节点,
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
ff(x);
}
int query(int l,int r,int x ){
int M,mid;
if(l>d[x].r||r<d[x].l||l>r)return 0;
if(d[x].l>=l&&d[x].r<=r){
return d[x].num;
}
else mid=(d[x].l+d[x].r)>>1;
//注意mid是左子树的节点
// if(mid<l)M=query(l,r,x<<1|1);
// else if(mid<=r)M=max(query(l,mid,x<<1),query(mid+1,r,x<<1|1));
// else M=query(l,r,x<<1);
//可能不运行下方l<=mid代码,所以要给M初始化
M=-9999999;
if(l<=mid)M=query(l,r,x<<1);
//建议使用下面的方式,简洁方便。
//mid是左子树的节点
if(mid<r)M=max(M,query(l,r,x<<1|1));
return M;
}
void update(int v,int i,int x){
if(d[x].l==d[x].r&&i==d[x].l){
d[x].num=v;
return;
}
int mid=d[x].l+d[x].r>>1;
//因为mid节点属于左子树。
if(mid<i)update(v,i,x<<1|1);
else if(i<=mid)update(v,i,x<<1);
ff(x);
}
int main(){
int n,t;
int a1,b1;
scanf("%d",&n);
scanf("%d",&t);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
build(1,1,n);
for(int i=0;i<t;i++){
scanf("%d%d",&a1,&b1);
printf("%d\n",query(a1,b1,1));
}
return 0;
}
大数
import java.util.*;
import java.math.*;
public class Biginteger {
public static void main(String args[]) {
BigInteger a,b,c,t;
Scanner input=new Scanner(System.in);
a=input.nextBigInteger();
b=input.nextBigInteger();
c=input.nextBigInteger();
//返回的是个大数对象,大数对象可以直接输出
c=a.divide(b);//注意除法是按照整数规则来的。
System.out.println(c);
for(int i=4;i<2020;i++) {
t=c;
c=a.add(b);
a=b;
b=t;
}
c=a.multiply(b);
System.out.println(c);
c=a.subtract(b);
System.out.println(c);
// System.out.println(c);
}
}
国赛准备注意事项
注意,国赛!!!国赛!!!国赛!!!
国赛无水题,各种细节各种策略
感觉像是一道小题,暗藏玄机。
涉及较大的数字运算的时候注意使用long long
//此题还要注意深搜的格式,避免忘记细节
//有一个7X7的方格。方格左上角顶点坐标为(0,0),右下角坐标为(7,7)。
//求满足下列条件的路径条数:
//1、起点和终点都是(0,0)
//2、路径不自交
//3、路径长度不大于12
//4、对于每一个顶点,有上下左右四个方向可以走,但是不能越界。
#include<iostream>
using namespace std;
int a[8][8];
int b[][2]={{0,1},{0,-1},{1,0},{-1,0}};
long long sum,cnt;
void def(int x,int y){
//注意分析 路径不自交 用笔画画就知道为什么cnt不能小于等于4
if(x==0&&y==0&&cnt<=12&&cnt>=4){
sum++;return;
}
if(cnt>12)return;
for(int i=0;i<4;i++){
int a1=b[i][0]+x,b1=b[i][1]+y;
if(a1>=0&&a1<=7&&b1>=0&&b1<=7&&a[a1][b1]==0){
a[a1][b1]=1;
cnt++;
def(a1,b1);
cnt--;
a[a1][b1]=0;
}
}
}
int main(){
def(0,0);
cout<<sum<<endl;
return 0;
}
还愿
果然,每一步都算数,努力没有白费。
第十一届蓝桥杯C/C++B组全国总决赛二等奖。
后记
最近看到很多人通过蓝桥杯关注我,倍感荣幸,在此提供一些更为实用的技巧。
- 蓝桥杯真的是暴力杯,但是暴力算法,也不是人人都会(例如:递归全排列,二进制枚举所有情况等等)。
- 用好STL,二分查找,字符串匹配,求最大公约数,全排列等等,C++都有对应的函数。
功能函数 - 学好数论,就算填空用不到,也是一种谈资(例如:考研面试)
数论内容 - 定位好自己,如果目标是国二,那真的先把会做的做好,因为有些时候往往自以为是的“正”解却不如暴力得分高。
- A掉样例也是有分的,(A掉样例的快速方法:字符串输入字符串输出)。
最后真心话:本人准备蓝桥杯时,学过线段树,树状数组,DP递推等等高端算法,但实际上若没有十足的把握和足够的刷题量,仅仅依靠所学的基础知识,千万不要尝试高端算法解题,否则既浪费考场时间得分还低,暴力才是王道。
还有不要作弊,虽然现在是线上,但是凭本事得到的才是最好的。