【历史遗留_2018】【NOIP提高组必备知识】NOIP的各种总结.md

26 篇文章 0 订阅
26 篇文章 1 订阅
本文介绍了各种算法解题思想,包括暴力、模拟搜索、递推递归、搜索最优解和取巧策略,如二分答案、区间分治、记忆化动规和倍增法。此外,还详细讲解了数组和结构体在数据结构模拟中的应用,如队列、栈、堆和链表,并涉及代数算法、数论和图论基础,如质数表、最大公约数和最小生成树。最后讨论了图的存储结构和相关算法,如Dijkstra、SPFA和Floyd算法。
摘要由CSDN通过智能技术生成

各种总结 [各种妙啊]

[by_2018年的041]

解题思想

暴力

<这个就不附代码了>

模拟搜索

后台打表

递推递归

搜索最优解

  • 贪心出一解,and暴搜 😆

取巧

二分答案

  • 在可能存在答案的单调序数列上的 Θ ( l o g N ) \Theta(logN) Θ(logN)查找
int l,r,mid;
/*[l,r]的初始化
为答案可能出现的两端*/
while(l<r)
  {
   mid=(l+r)/2;
   if(f(mid)>=goal)
     l=mid;
   else
     r=mid+1;
  }
// 结果 : l==r 都为目标地址

区间分治

  • 将问题划分成几个部分处理再合并产生答案, Θ ( d N ) , ( d 是 深 度 常 数 ) \Theta(dN),(d是深度常数) Θ(dN),(d)

记忆动归

  • 在各个参数范围允许的情况下,函数的每一次返回值都可以被保存,并且高效率协助剪枝优化,时间复杂度近似 Θ ( N ) \Theta(N) Θ(N)

倍增法求区间最值[RMQ]

  • 开一数组f[i][j],用于存储区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]中的 最大(小)值

  • 初始化f[i][0]=value[i];

  • 递推式:f[i][j]=max(f[i][j-1],f[i+(1<<j-1)-1][j-1]);

  • 代码实现 : (当时年少轻狂的头文件呀快速读写呀)

    #include<iostream>
    #include<cstdlib> 
    #include<cstdio>
    #include<cmath>
    
    using namespace std;
    
    #define N_       10007
    #define Log_2_N_ 107
    
    int n,m,i,j,endi,endj,log_2[Log_2_N_],a[N_],f[N_][Log_2_N_];
    
    inline void input(int&a)
    {
     register char ch;register bool pos=true;
     while((ch=getchar())<'0'||ch>'9')
       if(ch=='-')
         pos=false;
     a=ch-'0';
     while((ch=getchar())>='0'&&ch<='9')
       a=(a<<1)+(a<<3)+ch-'0';
     if(!pos)
       a=-a;
     return;
    }
    
    inline void output(int a)
    {
     if(!a)
       {
        putchar('0');
        return;
       }
     register int f=0;
     register char s[12],sig='\0';
     if(a<0)
       {
        sig='-';
        a=-a;
       }
     while(a)
       {
        s[f++]=a%10+'0';
        a/=10;
       }
     if(sig)
       putchar(sig);
     while(f--)
       putchar(s[f]);
     return;
    }
    ///前面不看
    int main()
    {
     for(input(n),input(m);i<n;f[i][0]=a[i++])
        input(a[i]);
     endj=ceil(log(n)/log(2));
     for(j=1;j<=endj;j++)
        {
         log_2[j]=floor(log(j)/log(2));
         endi=n-(1<<j);
         for(i=0;i<=endi;i++)
            f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);//重点!!!!!!!
        }
     do
       log_2[j]=floor(log(j)/log(2));
     while(++j<=n);
     while(m--)
       {
        input(i);input(j);i--;j--;
        output(max(f[i][log_2[j-i+1]],f[j+1-(1<<log_2[j-i+1])][log_2[j-i+1]]));
        putchar('\n'); 
       }
     return 0;
    }
    

数组/结构体 模拟各种数据结构

  • 队列queue
  • stack
  • map还是heap来着
  • 链表chain

效率太慢 ? ? 开数组模拟吧 !


算法部分

代数算法

a ∗ b ( m o d m ) a*b(modm) ab(modm)的O(1)算法

​ '妙啊’系数 ⋆ ⋆ ⋆ ⋆ \star\star\star\star

template<typename T>       // 一般是int,long long这类
inline T multiply(T a,T b,T m)
{
 T c=a*b-(T)((long double)a*b/m+0.5)*m;
 return c<0?c+m:c;				// ※归正※
}

位运算

  • 膜二的k次幂(n&0x7ff)(以k=10为例,其中`0x7ff`可换作(1<<k)-1)

  • 求a的二进制中有几个1

    inline int count(int a)
    {
     int c=0;
     while(a)
       {
        c++;
        a&=a-1;
       }
     return c;
    }
    
  • 待添加

数论

乘法原理

质数表
#include<cstdlib>
#include<cstdio>
#include<cmath>

using namespace std;

#define N_ 100001

int n,i,j,endi,pf,prime[N_]={};
bool isnp[N_]={1,1};

void output(int a)
{
 if(a>9)
   {
    output(a/10);
    a%=10;
   }
 putchar(a+'0');
 return;
}

int main()
{
 for(scanf("%i",&n),endi=sqrt(n),i=2;i<=endi;i++)
    {
     if(!isnp[i])
       prime[++pf]=i;
     j=pf;
     while(prime[j]*i>n)j--;
     while(j)
       isnp[prime[j--]*i]=true;
    }
 while(i<=n)
   {
    if(!isnp[i])
      prime[++pf]=i;
    j=pf;
     while(prime[j]*i>n)j--;
     while(j)
       isnp[prime[j--]*i]=true;
    i++;
   }
 for(i=1;i<=pf;i++,putchar('\n'))
    output(prime[i]);
 return 0;
}
因数个数表
#include<cstdlib>
#include<cstdio>
#include<cmath>

using namespace std;

#define N_ 100001

int n,i,j,endi,endj,pf,prime[N_],isnp[N_],factor[N_]={0,1};

void output(int a)
{
 if(a>9)
   {
    output(a/10);
    a%=10;
   }
 putchar(a+'0');
 return;
}

int main()
{
 for(scanf("%i",&n),endi=sqrt(n),i=2;i<=endi;i++)
    {
     factor[i]+=2;
     if(!isnp[i])
       prime[++pf]=i;
     for(j=i<<1;j<=n;j+=i)
        {
         isnp[j]=true;
         factor[j]++;
        }
    }
 while(i<=n)
   {
    factor[i]+=2;
    if(!isnp[i])
      prime[++pf]=i;
    for(j=i<<1;j<=n;j+=i)
       {
        isnp[j]=true;
        factor[j]++;
       }
    i++;
   }
 for(i=1;i<=n;i++,putchar('\n'))
    {
     output(i);
     putchar('\t');
     output(factor[i]);
    }
 return 0;
}
求最大公约数(gcd)
  • 由辗转相除法得 :
inline int gcd(int a,int b)
{
 while(a)
   {
    a^=b;
    b^=a;
    a^=b;
    a%=b;
   }
 return b;
}
求最大公倍数(lcm)
  • ∵a×b=lcm(a,b)×gcd(a,b) ∴ \therefore lcm(a,b)=a*b/gcd(a,b);
inline int gcd(int a,int b)
{
 while(a)
   {
    a^=b;
    b^=a;
    a^=b;
    a%=b;
   }
 return b;
}

inline int lcm(int a,int b)
{
 return a*b/gcd(a,b);
}

图论实践 [还需训练]

关于图的几个概念定义

  • 连通图:在无向图中,若任意两个顶点与都有路径相通,则称该无向图为连通图。
  • 强连通图:在有向图中,若任意两个顶点与都有路径相通,则称该有向图为强连通图。
  • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
  • 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

图的存储结构

|[参考网址1][https://blog.csdn.net/qq_22238021/article/details/78281939]| |[参考网址2][https://blog.csdn.net/EbowTang/article/details/44163561]| |[参考网址3][https://blog.csdn.net/qiu931110/article/details/80429589]|

邻接矩阵

a[i][j]表示节点i到节点j的距离

a[i↓][j→]012n-1
00d[0][1]d[0][2]d[0][n-1]
1d[1][0]0d[1][2]d[1][n-1]
2d[2][0]d[2][1]0d[2][n-1]
n-1d[n-1][0]d[n-1][1]d[n-1][2]0
边数组

p[i]表示第i条边,其成员from表示起点,to表示终点,value表示边权值

struct Pass_
{int from,to,value;}
p[N_];
邻接类表

邻接表[记入度]/逆邻接表[记出度]/十字链表[前两种之和]/邻接多重表[无向边关联记录]

  • 二维数组状(eg.邻接表)

flowin[i]表示i节点的入,to[i][j]表示从i节点出发的记录编号为j的边

p[i↓]flowinto [j→0]1n-1
011NULLNULL
10NULLNULLNULL
2513NULL
n-1312NULL
  • 链式(eg.逆邻接表)

    last[i]表示最后记录到的以第i个节点为起点的边编号

    p[i]表示编号为i的边,其成员to表示该边的终点,next为上一条与该边拥有共同起点的边的编号,value表示该点边权

last[i]p[i↓]tonextvalue
101NULLv[0][1]
n-11130v[0][3]
123NULLv[1][3]
m-11n-1142v[1][14]
NULLm-1418v[n-1][4]
  • 十字链表[即是邻接表和逆邻接表的结合]

    为同时存储邻接表和逆邻接表,所以有两个指针位[其余参考上面两种结构]

  • 邻接多重表[无向]

    加一个指针位指向与本边等位的边的编号2

相关考点

|[参考网址1][https://www.cnblogs.com/chuninggao/p/7301083.html]| |[参考网址2][https://blog.csdn.net/zezzezzez/article/details/70245548]| |[参考网址3][https://blog.csdn.net/qq_22238021/article/details/79489256]|

最短路
  • Dijkstra[使用邻接矩阵的时间复杂度为O(n^2),用优先队列的复杂度为O((m+n)logn)近似为O(mlogn)]

    • 单源最短路算法,即计算从起点出发到每个点的最短路,不能出现负权
    • 每次选择一个未访问过的到已经访问过的所有点的集合的最短边
    • 可用于记录路径
  • SPFA[又称bellman-ford,O(kE)E为边数,k一般为2或3]

    • dis[n][u]是从s(源点)到u的只经过n条边的最短路径长度

    • 当图G中没有从源可达的负权图时,从s到u的最短路径上最多有n-1条边。因此,

      dist(n-1)[u]就是从s到u的最短路径长度,显然,若从源s到u的边长为e(s,u),则dis1[u]=e(s,u).对于k>1,dis(k)[u]满足如下递归式,dis(k)[u]=min{dis(k-1)[v]+e(v,u)}.

    • 实现如下:用数组dis记录更新后的状态,cnt记录更新的次数,队列q记录更新过的顶点,算法依次从q中取出待更新的顶点v,按照dis(k)[u]的递归式计算。在计算过程中,一旦发现顶点K有cnt[k]>n,说明有一个从顶点K出发的负权圈,此时没有最短路,应终止算法。否则,队列为空的时候,算法得到G的各顶点的最短路径长度。

    • 优化

      SLF(Small Label First)是指在入队时如果当前点的dist值小于队首, 则插入到队首, 否则插入到队尾。

    • 应用:

      眼见的同学应该发现了,上面的[差分约束][https://baike.baidu.com/item/差分约束系统/5182920]四个字,是的SPFA可以很好的实现差分约束系统。

      ​ |[差分约束系统参考网站][https://blog.csdn.net/consciousman/article/details/53812818]|

  • Floyd[全称Floyd-Warshall,O(n^3)]

    • DP : dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

      即从顶点i到j且经过顶点k的最短路径长度

    • 可以解最小环问题

  • BFS[宽度优先搜索]

    • 如走迷宫经常用的,以一个点出发,向外扩散更新.[^个人经常用]
  • 最小生成树

    • Kruskal算法

      • 排序查找最小权贡献边[将不联通的两点连起]

        #include<cstdio>
        #include<cstdlib>
        #include<iostream>
        #include<algorithm>
        
        using namespace std;
        
        #define N_ 10001
        #define M_ 100001
        
        int n,m,i,anss,dot[N_];
        
        inline void input(int&a)
        {
         char ch;
         while((ch=getchar())<'0'||ch>'9');
         a=ch-'0';
         while((ch=getchar())>='0'&&ch<='9')
           a=(a<<1)+(a<<3)+ch-'0';
         return;
        }
        
        void output(int a)
        {
         if(a>9)
           {output(a/10);a%=10;}
         putchar(a+'0');
         return;
        } 
        
        int set(int a)
        {return dot[a]==a?a:dot[a]=set(dot[a]);}
        
        struct Pass_
        {int u,v,d;
        inline void in()
        {input(u);input(v);input(d);}
        #ifdef DEBUG
        inline void out()
        {output(u);putchar('\t');
         output(v);putchar('\t');
         output(d);putchar('\n');}
        #endif
        }p[M_];
        
        inline bool cmp_p(Pass_ a,Pass_ b)
        {return a.d<b.d;}
        
        int main()
        {
         for(input(n),input(m);i<m;i++)
            p[i].in();
         for(i=1;i<=n;i++)
            dot[i]=i;
         sort(p,p+m,cmp_p);
         for(i=0;i<m;i++)
            if(set(p[i].u)!=set(p[i].v))
              {
               dot[set(p[i].u)]=set(p[i].v);
               anss+=p[i].d;
              }
         m=set(1);
         for(i=2;i<=n;i++)
            if(set(i)!=m)
              {
               puts("-1");
               return 0;
              }
         output(anss); 
         return 0;
        }
        
    • Prim算法[Dijkstra思想]

      • 查找未加入的最小代价点,加入一(这)个点就用这个点更新一下它可以到达的点
    • 二分图判断

      • 代码:
        #include<iostream>
        #include<vector>
        #include<cstdio>
        #include<cstring>
        using namespace std;
        int n,m;
        int vis[10001];
        vector<int> v[10001];
        bool dfs(int x)
        {
           vector<int>::iterator it;
           for(it=v[x].begin();it!=v[x].end();it++)
           {
               if(vis[*it]==vis[x])return false;
               if(vis[*it]==0)
               {vis[*it]=-vis[x];
               if(!dfs(*it))return false;
               }
           }
           return true;
        }
        int main()
        {
           int t;
           cin>>t;
           while(t--){
               scanf("%d%d",&n,&m);
               for(int i=1;i<=n;i++)
               {
                   v[i].clear();
               }
               while(m--)
               {
                   int a,b;
                   scanf("%d%d",&a,&b);
                   v[a].push_back(b);
                   v[b].push_back(a);
               }
               memset(vis,0,sizeof(vis));
               int k=0;
               for(int i=1;i<=n;i++)
               {
                   if(vis[i]==0)
                   {
                       vis[i]=1;
                       if(!dfs(i))
                       {
                           k=1;
                           cout<<"Wrong"<<endl;
                           break;
                       }
                   }
               }
               if(!k)cout<<"Correct"<<endl;
           }
           return 0;
        }
         ```
        
  • 缩点

    是个啥

结构判断
  • 连通图
  • b a la b a la . . .

并查集

1.记录祖先
int father[N_];      // father[i]记录i节点的根节点(也可以表示为所属子集编号) 后来个人更喜欢用set[];
2.一步到位的神犇代码
int belongs(int fla) // 合并查找一步到位
{return father[fla]==fla?fla:father[fla]=find(father[fla]);}
// 合并节点x,y只需把father[belongs(x)]=father[y]即可
3.便于理解的模板
int belongs(int i)  // 返回i节点的根节点顺便更新路径上的点
{
// 找根节点
 register int fa=i;
 while(father[fa]!=fa)
   fa=father[fa];
// 更新路径上的点
 register int fla=i,temp;
 while(father[fla]!=fa)
 {
  temp=father[fla];
  father[fla]=fa;
  fla=temp;
 }
 return fa;
}

void combine(int x,int y)  // 合并节点x,y所在的集合
{
 register int fx=belongs(x),fy=belongs(y);
 if(fx!=fy)
   father[x]=fy;
}									// 实际的合并并未完成,具体查找还需belongs跑一遍

几何

凸包

  • 斜率优化< 暂定 : 详见2018夏令营内容 >

面积

  • 凸包减内凹3

细节问题

  • 根据需求选择变量类型 : 整形 , 正负 , 浮点 , 精度 . . . . . .

字符串处理

KSM法

  • 建立树状数组[个人用邻接表]

搜索问题

搜索包含所有元素的最短区间[左右指针O(l)算法]

  • 先固定左指针(l)于第一个有效元素的位置
  • 右指针®从左指针右移一位(l+1)开始动态移动
  • 当右指针®移到与左指针(l)上相同的元素位置时,将左指针(l)移到下一个有效元素的位置
  • 在记录过程中最短长度(min(r-l)),此长度即包含所有元素的最短区间长度
  • 可在更新长度时记录该区间的其他信息

模板

快速输入输出

  • 长乐一中神犇的代码(数据只能是正整数)
inline int get(){				// 输入一个正整数
	char ch;
	while ((ch = getchar()) < '0' || ch > '9');				// 过滤非数字字符
	int res = ch - '0';
	while ((ch = getchar()) >= '0' && ch <= '9')
    res = (res << 1) + (res << 3) + ch - '0';
	return res;
}

inline void put(int x){			// 输出一个正整数
	if (x > 9){				// 因为个位可能为0所以最后输出
		put(x / 10);		// ※递归操作,顺序输出※
		x %= 10;
	}
	putchar(x + '0');
	return;
} 
// 基于字符(串)输入输出
  • 自己的码(可以处理符号位)
inline int input(){
 char ch;int a;bool neg=false;
 while((ch=getchar())<'0'||ch>'9')
   neg=ch=='-';
 a=ch-'0';
 while((ch=getchar())>='0'&&ch<='9')
   a=(a<<1)+(a<<3)+ch-'0';
 return neg?-a:a;
}
inline void output(int a){
 if(a<0){putchar('-');a=-a;}
 if(a>9){output(a/10);a%=10;}
 putchar(a+'0');
 return;
}

结构体相关

struct point{									// 以下注释都是猜测,未证实慎用
	int x, y;
	point (){}								 // 维护point u;
	point (int _x, int _y):			// 维护类型转换point(x,y);
	x(_x), y(_y) {}
} ;

细节

大数据取模

  • 先加膜数再膜,避免出现负数ans[i]=(f(i)%mod+mod)%mod

分块处理

  • 注意 数据量比处理量要少的情况

二分下标

  • 左右下标(l,r)分别表示查找点可能存在的区域[l,r],把我这一性质进行不重不漏的二分实现规划

特判临界

  • 等于不等于,包含不包含的问题 要考虑清楚 理清逻辑
  • 指针区间无解,或解在特殊位置上(如:首尾)时 特判最好跟上
  • 数据在0(或其他特殊值)附近的处理

运算符顺序

  • = : 先左后右

  • 位运算符[<< >> | & 等]的优先级很低(靠后),联合其他符号试用需谨慎[加()]

    [ [另附对照表][C:\Users\Teloy_041\Desktop#学习\Cpp#题目\FZBZOI#高二上日常#算法部分\模块笔记# 运算符优先级[表].pdf] ]

尚未查明

  • <heap>
  • 缩点

[一个讲差分约束的博文][https://blog.csdn.net/consciousman/article/details/53812818]


  1. 数值上恰好相等,仅为演示方便 ↩︎ ↩︎

  2. 它给的结构说明并看不懂,于是想了个一样可以达成目的的结构方法 ↩︎

  3. 该内容是41自己瞎想的 , 是没有权威理论依据的 . ↩︎

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值