NOIP2020复习

联赛难度常用模板

基础技能:

头文件与输入输出:

#include<cstdio>  //输入输出头文件

#include<cstring> //字符串头文件,数组整体赋值头文件

#include<cstdlib> //随机数头文件

#include<algorithm>//快排头文件

#include<cmath> //sqrt等数学函数头文件(abs之类的建议手打,因为自带的可能出锅)

#include<ctime> //随机数初始化头文件

using namespace std;//开了algorithm/其它不以c开头的头文件时要打的东西

char str[10];

int main()

{

       int a,b;

       scanf("%d%d",&a,&b);

       printf("%d %d %d\n",a,b,a+b);

long long c,d;

       scanf("%lld%lld",&c,&d);

       printf("%lld %lld %lld\n",c,d,c+d);

       scanf("\n%s",str+1);  //注意+1代表从第一位开始(若不写+1则默认从第零位开始)

       int len=strlen(str+1); //前面有+1,求长度时也要+1

       for (int i=1;i<=len;i++) printf("%c",str[i]); //字符串的输出建议逐个字符输出

       printf("\n");

       double x,y;

       scanf("%lf%lf",&x,&y);

       printf("%.6lf\n",x+y); //注意怎么保留小数

}

数组整体赋值、清空:

#include<cstdio>

#include<cstring>//记得一定要开这个库(不开本机可能不会报错,但交上去会挂)

int a[10010],b[10010];

int main()

{

       memset(a,0,sizeof(a)); //a整体赋值为0

       memset(a,100,sizeof(a));//a整体赋值为一个较大值

       memcpy(a,b,sizeof(b));//a整体赋值为b数组每一位对应的值

       return 0;

}

存边方式:

#include<cstdio>

int tot;

int las[5010],nxt[10010],to[10010]; //注意nxt,to的两倍空间(双向边时),注意变量名称不要写成英文全拼(不同编译环境下容易出锅)。Las[x]表示编号为x的点最后一次出现的位置。Nxt[tot]表示当前位置tot对应的点所跳到的上一个位置,to[tot]表示当前位置tot对应的点所连向的点。找与x相连的所有点,就是从las[x]一直往前跳。

void insert(int x,int y)

{

       nxt[++tot]=las[x];

       las[x]=tot;

       to[tot]=y;

}

int main()

{

       int m;

       scanf("%d",&m);

       int x,y;

       for (int i=1;i<=m;i++)

       {

              scanf("%d%d",&x,&y);

              insert(x,y); insert(y,x); //双向边

       }

x=1;

       for (int i=las[x];i;i=nxt[i]) //枚举与x相连的所有点并输出

       {

              printf("%d\n",to[i]);

       }

}

快排:

#include<cstdio>

#include<algorithm>//头文件

using namespace std;//记得加上

int a[1010];

bool cmp(int x,int y)

{

       return x>y; //符号是小于就是从小到大排序,符号是大于就是从大到小排序。

}

int main()

{

       int n;

       scanf("%d",&n);

       for (int i=1;i<=n;i++) scanf("%d",&a[i]);

       sort(a+1,a+1+n,cmp);// 不写cmp就默认从小到大

       for (int i=1;i<=n;i++) printf("%d ",a[i]);

       return 0;

}

结构体多关键字快排:

#include<cstdio>

#include<algorithm>

using namespace std;

struct gjy{

       int x,num;     

} a[1010];

bool cmp(gjy a,gjy b)

{

       return (a.x<b.x) || (a.x==b.x && a.num<b.num); .x为第一关键字,.num为第二关键字,均从小到大

}

int main()

{

       int n;

       scanf("%d",&n);

       for (int i=1;i<=n;i++) scanf("%d",&a[i].x),a[i].num=i;

       sort(a+1,a+1+n,cmp);

       for (int i=1;i<=n;i++) printf("%d ",a[i].x);

       return 0;

}

EOF:

当题目说输入若干组数据,以文件结束符为结尾时。

#include<cstdio>

int main()

{

       int n,x;

       while (scanf("%d",&n)!=EOF)

       {

              scanf("%d",&x);

              printf("%d %d\n",n,x);

       }

       return 0;

}

水分/打表/找规律:

  1. 伪贪心有时可以水很多分
  2. 数学题不会就打表找规律
  3. 数据范围小的可以打表建立数据库

随机数://造数据或者打玄学做法时可使用,不要求掌握

#include<cstdio>

#include<cstdlib>//专属头文件

#include<ctime>//专属头文件

int main()

{

       srand(time(0)); //随机种子初始化

       int x=rand()%10+1; //rand的范围在本机应该是3万多

       printf("%d\n",x);

       return 0;

}

图论:

最短路(spfa):

var     a:array[1..1000,0..1000]of longint;

        f:array[1..1000,1..1000]of longint;

        dis:array[1..1000]of longint;

        data:array[1..100000]of longint;

        bz:array[1..1000]of boolean;

        x,y,t,n,m,i,j,l,r:longint;

begin

        readln(n,m);

        fillchar(f,sizeof(f),10);

        for i:=1 to m do

        begin

                readln(x,y,t);

                if f[x,y]<168430090 then

                begin

                        if f[x,y]>t then f[x,y]:=t;

                end else

                begin

                        inc(a[x,0]);

                        a[x,a[x,0]]:=y;

                        f[x,y]:=t;

                end;

        end;

        fillchar(data,sizeof(data),0);

        fillchar(dis,sizeof(dis),10);

        fillchar(bz,sizeof(bz),false);

        i:=1;

        l:=0;

        r:=1;

        data[1]:=i;

        dis[i]:=0;

        while l<r do

        begin

                inc(l);

                t:=data[l];

                for j:=1 to a[t,0] do

                        if dis[t]+f[t,a[t,j]]<dis[a[t,j]] then

                        begin

                                dis[a[t,j]]:=dis[t]+f[t,a[t,j]];

                                if bz[a[t,j]]=false then

                                begin

                                        bz[a[t,j]]:=true;

                                        inc(r);

                                        data[r]:=a[t,j];

                                end;

                        end;

                bz[t]:=false;

        end;

        for j:=1 to n do

                if dis[j]<f[i,j] then f[i,j]:=dis[j];

        for i:=2 to n do writeln(f[1,i]); //这是选择1为起点到其它所有点的最短路

end.

 

最小生成树:

#include<cstdio>

#include<algorithm>

using namespace std;

struct zzx{

       int x,y,z;

} a[10010];

int fa[10010];

bool cmp(zzx a,zzx b)

{

       return a.z<b.z;      

}

int father(int x)

{

       if (x==fa[x]) return x;

       fa[x]=father(fa[x]);

}

int main()

{

       int n,m;

       scanf("%d%d",&n,&m);

       for (int i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);

       sort(a+1,a+1+m,cmp);

       for (int i=1;i<=n;i++) fa[i]=i;

       int s=0;

       for (int i=1;i<=m;i++)

       {

              int fax=father(a[i].x);

              int fay=father(a[i].y);

              if (fax!=fay)

              {

                     s=s+a[i].z;

                     fa[fax]=fay;

              }

       }

       printf("%d\n",s);

}

 

LCA:

//fa表示每个点的父节点,deep表示每个点的深度

int fa[100],deep[100];

int LCA(int a,int b)

{

       //在函数中确保a的深度大于b的深度,方便后面操作。

       if(deep[a]<deep[b])

              swap(a,b);

       //让b不断地跳到他的父节点上,直到与a的深度相同

       while(deep[a]>deep[b])

              b=deep[b];

       //让a和b同时往上跳,直到两者相遇。

       while(a!=b)

       {

              a=deep[a];

              b=deep[b];

       }

       return a;

}

倍增:

https://blog.csdn.net/Q_M_X_D_D_/article/details/89924963(有讲解)

//fa表示每个点的父节点

int fa[100],DP[100][20];

void init()

{

       //n为结点数,先初始化DP数组

       for(int i=1;i<=n;i++)

              dp[i][0]=fa[i];

       //动态规划求出整个DP数组

       for(int j=1;(1<<j)<=n;j++)

              for(int i=1;i<=n;i++)

                     DP[i][j]=DP[DP[i][j-1]][j-1];

}

//查询函数

int LCA(int a,int b)

{

    //确保a的深度大于b,便于后面操作。

       if(dep[a]<dep[b])

              swap(a,b);

    //让a不断往上跳,直到与b处于同一深度

    //若不能确保a的深度大于b,则在这一步中就无法确定往上跳的是a还是b

       for(int i=19;i>=0;i--)

       {

        //往上跳就是深度减少的过程

              if(dep[a]-(1<<i)>=dep[b])

                     a=dp[a][i];

       }

    //若二者处于同一深度后,正好相遇,则这个点就是LCA

       if(a==b)

              return a;

    //a和b同时往上跳,从大到小遍历步长,遇到合适的就跳上去,不合适就减少步长

       for(int i=19;i>=0;i--)

       {

        //若二者没相遇则跳上去

              if(dp[a][i]!=dp[b][i])

              {

                     a=dp[a][i];

                     b=dp[b][i];

              }

       }

    //最后a和b跳到了LCA的下一层,LCA就是a和b的父节点

       return dp[a][0];

}

 

差分:

序列差分:https://gmoj.net/junior/#main/show/2162

对于一个矩形,通过四个顶点位置的值的加减,实现矩形内所有位置的加减操作。单点查询时用前缀和。

var     n,q,x,y,i,j,x1,y1,x2,y2:longint;

        a:array[1..3001,1..3001]of longint;

        sum:array[0..3000,0..3000]of longint;

begin

        assign(input,'square.in');reset(input);

        assign(output,'square.out');rewrite(output);

        readln(n);

        for i:=1 to n do

        begin

                readln(x1,y1,x2,y2);

                inc(a[x1,y1]);

                dec(a[x1,y2+1]);

                dec(a[x2+1,y1]);

                inc(a[x2+1,y2+1]);

        end;

        for i:=1 to 3000 do

                for j:=1 to 3000 do

                        sum[i,j]:=sum[i-1,j]+sum[i,j-1]-sum[i-1,j-1]+a[i,j] ;

        readln(q);

        for i:=1 to q do

        begin

                readln(x,y);

                writeln(sum[x,y]);

        end;

        close(input);close(output);

end.

树上差分:

树上两点x,y,使得x~y的路径上所有边+a。则把x的父边+a,y的父边+a,他们lca的父边-2a。查询一条边的值,通过查询该边的子树和来实现。

 

树上两点x,y,使得x~y的路径上所有点+a。则把x的值+a,y的值+a,他们lca的值-a,他们lca的父亲的值-a。查询一个点的值,通过查询它的子树和来实现。

 

 

二分图匹配(匈牙利算法):

https://blog.csdn.net/sunny_hun/article/details/80627351(有讲解)

#include<cstdio>

#include<cstring>

int a[1010][1010];

int f[1010];

bool bz[1010];

bool pd(int x)

{

       for (int i=1;i<=a[x][0];i++)

              if (!bz[a[x][i]])

              {

                     bz[a[x][i]]=true;

                     if (f[a[x][i]]==0 || pd(f[a[x][i]]))     

                     {

                            f[a[x][i]]=x;

                            return true;

                     }

              }

       return false;

}

int main()

{

       int n,m,e;

       scanf("%d%d%d",&n,&m,&e);

       int x,y;

       for (int i=1;i<=e;i++)

       {

              scanf("%d%d",&x,&y);

              if (y>m) continue;

              a[x][++a[x][0]]=y;

       }

       int ans=0;

       for (int i=1;i<=n;i++)

       {

              memset(bz,0,sizeof(bz));

              if (pd(i)) ans++;

       }

       printf("%d\n",ans);

       return 0;

}

 

常见期望计算:

三个点,相邻两点间由双向边相连。

 

设f[i]表示从1号点出发,第一次到i号点的期望步数。

则f[1]=0,f[2]=1。现在求f[3]。

考虑一开始只能从1号点到2号点。在2号点时,可以回到1号,也可以去往3号,各为1/2的概率。若去往3号,则结束。若回到1号,则相当于重头开始。

利用方程思想:

f[3]=1/2*(f[2]+1)+1/2*(f[2]+1+f[3]),前半部分为去往3号点,后半部分为回到1号点重头开始。解得f[3]=4.

树上的期望计算也可以用类似的方程思想。

 

树链剖分:

https://blog.csdn.net/HiChocolate/article/details/77170675 (模板,搬运)

将树边分为轻边和重边。树上的每一层都有且只有一条重边,为所连向的儿子拥有最多后代(子树大小最大)的边。重边所连向的儿子称为重儿子。

树链剖分的过程为2次dfs

第一次:按照定义找出重边、重儿子

第二次:按照优先走重边的原则走出一个dfs序,点x在dfs序中的位置记为tree[x],并算出每个点所属重链的起点top[x]。

剖分完之后,每条重链就相当于一段区间,用数据结构(如线段树等)去维护。

把所有的重链首尾相接,放到同一个数据结构上,然后维护这一个整体即可。

如果u和v在同一条重链上,直接用数据结构修改tree[u]至tree[v]间的值或查询答案。

如果u和v不在同一条重链上,一边进行修改,一边将u和v往同一条重链上靠,然后就变成了上面的情况。

具体操作:我们把深度较大的一方x跳到他的fa[top[x]]上,并修改或查询tree[top[x]]~tree[x]

由于一条重链在数据结构中是一段连续的区间,所以直接查询tree[top[x]]~tree[x]是没问题的。

数论:

Gcd:

#include<cstdio>

int gcd(int x,int y)

{

       if (x%y==0) return y; else return gcd(y,x%y);

}

int main()

{

       int a,b;

       scanf("%d%d",&a,&b);

       printf("%d\n",gcd(a,b));

       return 0;

}

快速幂:

#include<cstdio>

long long mi(long long a,long long b)

{

       long long s=1;

       while (b)

       {

              if (b&1) s=s*a;

              a=a*a;

              b/=2;

       }

       return s;

}

int main()

{

       int n,m;

       scanf("%d%d",&n,&m);

       long long s=mi(n,m);

       printf("%lld\n",s);

       return 0;

}

求某个数在%p意义下的逆元:

当p为质数时,一个数除以x就等于乘以x^(p-2)。以此实现模意义下得除法运算。

当p不为质数时,没有逆元,此时应避免出现除法运算。

 

质数筛法:

#include<cstdio>

bool bz[100010];

int zhi[100010];

int main()

{

       int n;

       scanf("%d",&n);

       for (int i=3;i*i<=n;i+=2)

              if (!bz[i])

                     for (int j=i;i*j<=n;j+=2) bz[i*j]=true; //注意bz并没有标记偶数

       zhi[0]=1; zhi[1]=2;

       for (int i=3;i<=n;i+=2)

              if (!bz[i]) zhi[++zhi[0]]=i;

       for (int i=1;i<=zhi[0];i++) printf("%d ",zhi[i]);

       return 0;

}

 

Exgcd:

https://www.cnblogs.com/mrclr/p/9380300.html

exgcd可以用来求解方程ax +by = gcd(a, b)的一组x,y。

int exgcd(int a,int b,int &x,int &y) //注意x,y前的&

{

       int s;

       if (!b)

       {

              s=a,x=1,y=0;

              return s;

       }

       s=exgcd(b,a%b,y,x); y-=a/b*x;

       return s;

}

 

龟速乘:

我们有两个数a,b,要求a*b%p的结果。

如果a和b虽然都不超过long long,但乘在一起就超过了怎么办呢?

把a*b变成b个a相加,然后像快速幂那样,只是每次乘法变成了加法。

这样由于是一点一点加上去的,每次加都取模,所以就不会爆long long了

https://blog.csdn.net/jz_terry/article/details/86670193

long long cheng(long long a,long long b)

{

       long long s=0;

       while (b)

       {

              if (b&1) s=(s+a)%Mo;

              a=(a+a)%Mo;

              b>>=1;

       }

       return s;

}

 

线性求1~n在%p意义下的逆元:递推,f[i]=f[p%i]*(p-p/i)%p  

推导:令p=a*i+b,则a*i+b 0(%p)。因为要求i的逆元,所以应将式子转化为i^-1=...的形式。即1/i=-a/b=-y/x*(y%x)^-1,为避免出现负数,还应加上p。所以得到上述递推式。

 

线性求1~n中每个数i的阶乘i!的逆元:f[i]=f[i+1]*(i+1)%p.

证明:i!^-1=(i+1)!^-1)(i+1)  (乘上(i+1)是为了消掉(i+1)^-1,这样就可以由n倒着推来了)

DP

背包问题dp:

01背包:

有t种物品和一个容量为n的背包。第i种物品只有1个,体积是v[i],价值是w[i]。选择物品装入背包使这些物品的体积总和不超过背包容量,且价值总和最大,求出这个最大价值。

var     i,j,t,n:longint;

        f:array[0..1000,0..1000]of longint;

        w,v:array[1..1000]of longint;

function max(a,b:longint):longint;

begin

        if a>b then exit(a) else exit(b);

end;

begin

        readln(n,t); //t是物品数,n是容量数

        for i:=1 to t do

                readln(w[i],v[i]);

        for i:=1 to t do

                for j:=1 to n do

                        if j>=w[i] then

                        f[i,j]:=max(f[i-1,j],f[i-1,j-w[i]]+v[i]) else

                        f[i,j]:=f[i-1,j];

        writeln(f[t,n]);

end.

完全背包:

有t种物品和一个容量为n的背包。第i种物品有无穷个,体积是v[i],价值是w[i]。
选择物品装入背包使这些物品的体积总和不超过背包容量,且价值总和最大,求出这个最大价值。

var     w,v:array[0..10000]of longint;

        f:array[0..1000,0..10000]of longint;

        i,j,n,m,k,l,t:longint;

function max(x,y:longint):longint;

begin

        if x>y then exit(x) else exit(y);

end;

begin

        readln(n,t);

        for i:=1 to t do readln(w[i],v[i]);

        for i:=1 to t do

        begin

                for j:=1 to n do

                begin

                         for k:=0 to j div w[i] do

                         begin

                                if w[i]*k>j then f[i,j]:=f[i-1,j]

                                else f[i,j]:=max(f[i,j],f[i-1,j-w[i]*k]+v[i]*k)

                         end;

                end;

        end;

        write(f[t,n]);

end.

 

最长不下降子序列的dp:

设b[i]表示最长不下降子序列的第i位最小能是多少(要满足第i位不小于第i-1位)

每次新加入一个数,如果能增长子序列则增长,增长不了就看看能不能更新b。

var     n,i,j:longint;

        a,b:array[0..100]of longint;

begin

        readln(n);

        for i:=1 to n do read(a[i]);

        b[0]:=0;

        for i:=1 to n do

        begin

                if a[i]>=b[b[0]] then

                begin

                        inc(b[0]);

                        b[b[0]]:=a[i];

                end else

                begin

                        for j:=b[0]-1 downto 1 do  //若这一部分改为二分,复杂度就是nlogn了。

                                if a[i]>=b[j] then

                                begin

                                        b[j+1]:=a[i];

                                        break;

                                end;

                        if b[1]>a[i] then b[1]:=a[i];

                end;

        end;

        writeln(b[0]);

end.

 

树形dp:

一棵n个点的树,每个点有一个权值(可正可负),求树中权值和最大的子树,输出该最大的权值。

#include<cstdio>

int tot;

int nxt[2010],to[2010],las[1010];

int f[1010];

int a[1010];

void insert(int x,int y)

{

       nxt[++tot]=las[x];

       las[x]=tot;

       to[tot]=y;

}

void dfs(int x,int fa)

{

       f[x]=a[x];

       for (int i=las[x];i;i=nxt[i])

              if (to[i]!=fa)

              {

                     dfs(to[i],x);

                     f[x]=f[x]+f[to[i]];

              }

}

int main()

{

       int n;

       scanf("%d",&n);

       for (int i=1;i<=n-1;i++)

       {

              int x,y;

              insert(x,y);

              insert(y,x);

       }

       for (int i=1;i<=n;i++) scanf("%d",&a[i]);

       dfs(1,0);

       int mx=0;

       for (int i=1;i<=n;i++)

              if (f[i]>mx) mx=f[i];

       printf("%d\n",mx);

}

 

单调队列:

不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。

https://blog.csdn.net/ljd201724114126/article/details/80663855

 

斜率优化:

https://blog.csdn.net/jz_terry/article/details/103212006

其它:

二分:(容易出锅,建议打完之后调试或是脑补模拟几遍)

一个从小到大排好序的序列,找出第一个大于x的数的位置。

#include<cstdio>

int a[1010];

int main()

{

       int n,x;

       scanf("%d",&n);

       for (int i=1;i<=n;i++) scanf("%d",&a[i]);

       scanf("%d",&x);

       int l=1; int r=n+1; //因为r表示的是满足条件的最小位置,所以初值不能为n(a[n]不一定大于x)

       while (l<r)

       {

              int mid=(l+r)/2;

              if (a[mid]<=x) l=mid+1; else r=mid;

       }

       printf("%d\n",r);

       return 0;

}

一个从小到大排好序的序列,找出最后一个小于x的数的位置。

#include<cstdio>

int a[1010];

int main()

{

       int n,x;

       scanf("%d",&n);

       for (int i=1;i<=n;i++) scanf("%d",&a[i]);

       scanf("%d",&x);

       int l=1; int r=n; //定义l-1是满足条件的,r+1是不满足条件的

       while (l<=r) //与前面不同,l=r时也要做,判断l满不满足条件

       {

              int mid=(l+r)/2;

              if (a[mid]>=x) r=mid-1; else l=mid+1;

       }

       printf("%d\n",l-1);

       return 0;

}

 

线段树:

单点修改,区间查询:

#include<cstdio>

#include<cstdlib>

#include<iostream>

using namespace std;

int n,m,k,x,y,ans;

int a[100010];

int tree[400010];

void maketree(int x,int st,int en)

{

       int m;

       if (st==en) tree[x]=a[st];

       else

       {

              m=(st+en)>>1;

              maketree(x+x,st,m);

              maketree(x+x+1,m+1,en);

              tree[x]=max(tree[x+x],tree[x+x+1]);

       }

}

void change(int x,int st,int en,int p,int value)

{

       int m;

       if (st==en) tree[x]=value;

       else

       {

              m=(st+en)>>1;

              if (p<=m)

              {

                     change(x+x,st,m,p,value);

              }

              else change(x+x+1,m+1,en,p,value);

              tree[x]=max(tree[x+x],tree[x+x+1]);

       }

}

void find(int x,int st,int en,int l,int r)

{

       int m;

       if (l==st && r==en) ans=max(tree[x],ans);

       else

       {

              m=(st+en)>>1;

              if (r<=m) find(x+x,st,m,l,r);

              else if (l>m) find(x+x+1,m+1,en,l,r);

              else

              {

                     find(x+x,st,m,l,m);

                     find(x+x+1,m+1,en,m+1,r);

              }

       }

}

int main()

{

       scanf("%d",&n);

       for (int i=1;i<=n;i++)

       {

              scanf("%d",&a[i]);

       }

       maketree(1,1,n);

       scanf("%d",&m);

       for (int i=1;i<=m;i++)

       {

              scanf("%d%d%d",&k,&x,&y);

              if (k==1)

              {

                     change(1,1,n,x,y);

              }

              else if (k==2)

              {

                     ans=0;

                     find(1,1,n,x,y);

                     printf("%d\n",ans);

              }

       }

}

区间修改,区间查询:

#include<cstdio>

#include<cstdlib>

#include<cstring>

#include<iostream>

using namespace std;

long long f[300010];

int a[300010];

int g[300010];

long long ans;

int n,m;

int x,l,r,c;

void maketree(int x,int st,int en)

{

       int mid;

       if (st==en) f[x]=a[st];

       else

       {

              mid=(st+en)/2;

              maketree(x*2,st,mid);

              maketree(x*2+1,mid+1,en);

              f[x]=max(f[x*2],f[x*2+1]);     

       }

}

void update(int x,int st,int en,int l,int r,int v)

{

       int mid;

       if (st==l && en==r)

       {

              f[x]=f[x]+v;

              g[x]=g[x]+v;

       }

       else

       {

              f[x*2]+=g[x];

              f[x*2+1]+=g[x];

              g[x*2]+=g[x];

              g[x*2+1]+=g[x];

              g[x]=0;

              mid=(st+en)/2;

              if (r<=mid) update(x*2,st,mid,l,r,v);

              else if (l>mid) update(x*2+1,mid+1,en,l,r,v);

              else

              {

                     update(x*2,st,mid,l,mid,v);

                     update(x*2+1,mid+1,en,mid+1,r,v);

              }

              f[x]=max(f[x*2],f[x*2+1]);

       }

}

void getmax(int x,int st,int en,int l,int r)

{

       int mid;

       if (st==l && en==r)

       {

              ans=max(ans,f[x]);

       }

       else

       {

              //

              f[x*2]+=g[x];

              f[x*2+1]+=g[x];

              g[x*2]+=g[x];

              g[x*2+1]+=g[x];

              g[x]=0;

              //

              mid=(st+en)/2;

              if (r<=mid) getmax(x*2,st,mid,l,r);

              else if (l>mid) getmax(x*2+1,mid+1,en,l,r);

              else

              {

                     getmax(x*2,st,mid,l,mid);

                     getmax(x*2+1,mid+1,en,mid+1,r);

              }

       }

}

int main()

{

       scanf("%d",&n);

       for (int i=1;i<=n;i++)

       {

              scanf("%d",&a[i]);

       }

       maketree(1,1,n);

       scanf("%d",&m);

       for (int i=1;i<=m;i++)

       {

              scanf("%d",&x);

              if (x==1)

              {

                     scanf("%d%d%d",&l,&r,&c);

                     update(1,1,n,l,r,c);

              }

              else

              {

                     ans=-2147483647000;

                     scanf("%d%d",&l,&r);

                     getmax(1,1,n,l,r);

                     printf("%d\n",ans);

              }

       }

       //system("pause");

       return 0;

}

树状数组:

https://blog.csdn.net/jz_terry/article/details/78543825

记得two[i]=i&(-i);

单点修改,区间查询

#include<cstdio>

int a[100001];

int two[100001];

int c[100001];

int sum[100001];

char ch;

int qiuhe(int x)

{

       int s=0;

       while (x>0)

       {

              s+=c[x];

              x=x-two[x];

       }

       return s;

}

int main()

{

       int n,m;

       scanf("%d",&n);

       int i;

       for (i=1;i<=n;i++)

       {

              scanf("%d",&a[i]);

              sum[i]=sum[i-1]+a[i];

       }

       for (i=1;i<=n;i++) two[i]=i&(-i);

       for (i=1;i<=n;i++)

              c[i]=sum[i]-sum[i-two[i]];

       scanf("%d",&m);

       int x,y,k,t;

       for (i=1;i<=m;i++)

       {

              scanf("\n%c%d%d",&ch,&x,&y);

              if (ch=='C')

              {

                     k=x;

                     t=a[x];

                     a[x]=y;

                     while (k<=n)

                     {

                            c[k]=c[k]-t+y;

                            k=k+two[k];

                     }

              } else

              {

                     printf("%d\n",qiuhe(y)-qiuhe(x-1));                   

              }

       }

       return 0;

}

KMP:

#include<cstdio>

char a[100001];

char b[100001];

int p[100001];

int f[100001];

int main()

{

       int n,m;

       scanf("%d%d",&n,&m);

       scanf("%s",a+1);

       scanf("%s",b+1);

       int i,j;

       for (i=2;i<=m;i++)

       {

              j=p[i-1]; //p[i]表示匹配串中,以第i位结尾的一个后缀,即i-p[i]+1~i,与1~p[i]相同。要使得p[i]在满足上述条件下最大。

              while (j>0 && b[j+1]!=b[i]) j=p[j]; //先沿用p[i-1],然后因为要多满足1位,所以不断失配,不断调整缩减。

              if (b[j+1]==b[i]) p[i]=j+1;     //找到合法的就更新,没找到p[i]就为0

       }

       for (i=1;i<=n;i++)

       {

              j=f[i-1];//f[i]表示原串中第i位为后缀,原串的i-f[i]+1~i与匹配串的1~f[i]相同,最大化f[i]。

              while (j>0 && b[j+1]!=a[i]) j=p[j]; 先沿用f[i-1],然后因为要多满足1位,所以不断失配,不断调整缩减。

              if (b[j+1]==a[i])

              {

                     f[i]=j+1;

                     if (f[i]==m)

                     {

                            printf("%d\n",i-m+1);

                            f[i]=p[f[i]]; //找到一个完整匹配后,为了继续找下一个,f[i]要回跳一步。

                     }

              }

       }

       return 0;

}

最小堆:

https://blog.csdn.net/hrn1216/article/details/51465270(如果你忘了原理的话)

堆排序

#include<cstdio>

#include<cstring>

#include<cstdlib>

using namespace std;

int g[300010],i,n,a;

void up(int t)

{

       int q;

       if(t==1)return;

       q=t/2;

       if(g[q]>g[t])

       {

              a=g[q]; g[q]=g[t]; g[t]=a;

              up(q);

       }

}

void down(int t)

{

       int p;

       if(t*2>n)return;

       p=t*2;

       if((p<n)&&(g[p+1]<g[p]))p++;

       if(g[p]<g[t])

       {

              a=g[p]; g[p]=g[t]; g[t]=a;

              down(p);

       }     

}

int main()

{

       scanf("%d",&n);

       for(i=1;i<=n;i++)

       {

              scanf("%d",&g[i]);

              up(i);

       }

       for(;n>1;)

       {

              printf("%d\n",g[1]);

              g[1]=g[n];

              n--;

              down(1);

       }

       printf("%d\n",g[1]);

}

 

哈希:

通过对每一位乘上不同系数并取模的方式,将一个字符串/一个很大的数,转化为一个比较小的数(相当于赋予其一个编号),并尝试存入数组中下标为该编号的位置。若数组中该编号下标已被存放,且原串不同,则将该编号逐步累加,直到找到一个空位插入。

 

马拉车:

https://blog.csdn.net/Gao_Jue_Yi/article/details/81435328(你以前的博客)

#include<cstdio>

#include<cstring>

char a[11000010];

char b[22000010];

int p[22000010];

int main()

{

       scanf("%s",a+1);

       int n=strlen(a+1);

       b[1]='*';

       int m=1;

       for (int i=1;i<=n;i++)

              b[++m]=a[i],b[++m]='*';

       int x=0; int y=0;

       int ans=0;

       for (int i=1;i<=m;i++)

       {

              if (y>i)

              {

                     if (p[x*2-i]+i-1<=y) p[i]=p[x*2-i]; else p[i]=y-i+1;

              } else p[i]=1;

              while (i-p[i]>=1 && i+p[i]<=m && b[i-p[i]]==b[i+p[i]]) p[i]++;

              if (y<i+p[i]-1)

              {

                     y=i+p[i]-1;

                     x=i; 

              }

              if (p[i]-1>ans) ans=p[i]-1;

       }

       printf("%d\n",ans);

       return 0;

}

 

网络流:(应该不属于联赛范围,不要求掌握)

#include<cstdio>

#include<cstring>

int n,m,S,T;

int las[10010],nxt[200010],to[200010];

long long num[200010];

int gap[10010],dis[10010];

int tot=-1;

int min(long long x,long long y)

{

       if (x<y) return x; else return y;

}

void insert(int x,int y,long long z)

{

       nxt[++tot]=las[x];

       las[x]=tot;

       to[tot]=y;

       num[tot]=z;

}

long long dfs(int x,long long s)

{

       if (x==T) return s; //已经流到汇点了,返回流量

       int have=0; //已经流了多少流量到汇点T

       for (int i=las[x];i!=-1;i=nxt[i])

              if (dis[to[i]]+1==dis[x] && num[i]>0) //距离标号,增加流量要满足最短路性质,且这条边有值

              {

                     int now=dfs(to[i],min(s-have,num[i]));

                     num[i]-=now; num[i^1]+=now; //i^1是to[i]->x的反向边编号,通过存边时实现

                     have+=now; //更新存储流量

                     if (have==s) return s; //如果已经满流了,就直接返回,防止距离标号被不可能的流改变

              }

       if (--gap[dis[x]]==0) dis[1]=n; //若距离标号存在断层,则不可能再流了

       gap[++dis[x]]++;//更新距离标号

       return have;

}

int main()

{

       scanf("%d%d%d%d",&n,&m,&S,&T);

       memset(las,255,sizeof(las));

       memset(nxt,255,sizeof(nxt));

       for (int i=1;i<=m;i++)

       {

              int x,y; long long z;

              scanf("%d%d%lld",&x,&y,&z);

              insert(x,y,z);

              insert(y,x,0);   

       }

       long long sum=0;

       gap[0]=n;

       while (dis[S]<n) sum+=dfs(S,9000000000000000LL);

       printf("%lld\n",sum);

       return 0;

}

 

套路&技巧&注意事项:

  1. 求[l..r]方案数的一般都是转化为[1..r]-[1..l-1]。
  2. 涉及区间覆盖的,想一想差分。
  3. 求最小最大时想二分

4、不能的个数=总数-能的个数。

5、c++很少会报错,所以要自己注意数组是否会越界,要不要开long long。

6、打二分时要多调试几遍。

7、注意时间分配,想不到就打暴力。

8、一些看上去超时的搜索在加上剪枝后能拿到很多分甚至能切掉,所以搜索要尽量打剪枝,比如最大最小值剪枝、合法性剪枝、记忆化等。

9、注意数据范围内那些奇怪的特殊数据。

10、c++中位运算的优先级很低,所以最好加括号。

11、做题要多看几遍题目,想做法时样例要先看懂。

12、有些要取模的题在卡常时可以隔几个去一次模,因为取模很慢。

13、调用数组也比较慢,至少比直接用变量慢,所以卡常时能用变量就别调用数组。

14、记得初始化。

15、在空间允许的情况下,不要吝啬开long long耗费的空间,感觉数值可能比较大就开long long。

16、遇到实数类型就直接用double(pascal的extended),以防被卡精度。

17、线段树空间要开4n。也就是说100000个数要开到400000的空间来存。

18、数据很大好像只能O(1)过的一般都是公式结论题,推不出公式可以打个暴力来找规律。

19、sqrt一定要开cmath。虽然在编译器上只开algorithm也可以通过,但交上去会错。

20、不要用get、last、next等英文全拼的变量名,开了cmath库时不要用x0,y0,x1,y1之类。因为不同编译环境下可能会编译错误。

 

 

猜你喜欢

转载自blog.csdn.net/jz_terry/article/details/109458507
今日推荐