假如给我十个模板

今天是2022年8月26日。距离csp初赛还有三个星期,距离csp复赛还有不到两个月。

除模拟题外,所有的编程题都是对一个或几个题型的综合。这种“题型”最原始的解法或思想我们称之为模板。

如果有十个模板可以带进考场,我会选择哪些?

  1.中国剩余定理

    • 对于同余问题,在满足一定条件的情况下可以直接使用中国剩余定理快速求解。
    • 如果考场上遇到相关数论题,可以迅速完成代码框架搭建,留更多的时间给推导做题方法。
    • #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      const int h=10000001;
      /*
          回忆一下扩欧
          对于ax=1  mod p
          可得(ax-1)=p*(-y)
          a*x+y*p=1
          那么只要a与y互质,就可以求出一组x,y
          x就是我们要求的逆元
           
      */
      ll exgcd(ll a,ll b,ll &x,ll &y){
          //if(a<b)
          //    swap(a,b);
          ll xa=1,xb=0,ya=0,yb=1;
          ll xr,yr,r,p;
          while(b!=0){
              p=a/b;
              r=a%b;
              xr=xa-ya*p;
              yr=xb-yb*p;
              xa=ya;
              xb=yb;
              ya=xr,yb=yr;
              a=b,b=r;    
                          
          }
          x=xa,y=xb;
          return a;
          
      }
      
      
      /*
          关于crt
          求解同余方程组
          x=a1  mod m1
          x=a2  mod m2
          ......
          x=ak  mod mk
          令M=m1*...*mk,Mi=M/mi
          令ti=inv[Mi]  mod mi
          则有一个解x0=a1*inv[M1]*M1+...+ak*inv[Mk]*Mk
          则对于这个问题,我们有最小解
          证明:毕竟对于所有Mj,j!=i,Mj|mi,只需证ai*Mi^-1*Mi-ai|mi 
          那么转化为ai*(inv[Mi]*Mi-1)
          inv[Mi]*Mi=1  mod mi 
          成立
          最小解即为:x0%M
      */
      
      ll a[h],m[h];
      int main(){
          //ll l,r;
          //ll g=exgcd(46,240,l,r);
          //cout<<g<<" "<<l<<' '<<r;
          int n;
          ll maxx=0,M=1;
          cin>>n;
          for(int i=1;i<=n;i++){
              scanf("%d%d",&m[i],&a[i]);
              M*=m[i];
          }
          ll ans=0;
          //cout<<M<<endl;
          //turn(M);
          for(int i=1;i<=n;i++){
              ll l,r;
              ll u=exgcd(M/m[i],m[i],l,r);
              ans+=a[i]*(((l%m[i])+m[i])%m[i])*(M/m[i]);
          }
          cout<<ans%M;
          return 0;
      }
      View Code

       

  2.扩展中国剩余定理

    • 毕竟,不是所有的同余问题模数都互质,对于那些条件更广泛的问题,我们需要扩展中国剩余定理。
    • 而且,其中包含的部分数论小工具对于解题而言也都可能发挥一定的作用。
    • #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      /*
          扩展中国剩余定理
          首先,这玩意和中国剩余定理是两个东西
          更确切地说,你只需要会求拓展欧几里得即可 
          对于两个同余方程
          x=r1  mod m1
          x=r2  mod m2
          将其转化为
          x=m1*k1+r1
          x=m2*k2+r2
          转化为m1*k1+r1=m2*k2+r2
          得到不定方程m1*k1-m2*k2=r2-r1
          若这个方程有解,gcd(m1,m2)|r2-r1
          否则无解 
          若有解,设d=gcd(m1,m2),p1=m1/d,p2=m2/d
          那么将原方程转化为p1*k1-p2*k2=(r2-r1)/d
          这时,p1,p2互质,方程右边是整数
          可以求出一组k1,k2
          
          令l1=k1/((r2-r1)/d),l2=...
          求出p1*l1+p2*l2=1的解 
          那么k1就是l1*(r2-r1)/d
          
          于是k1=l1*(r2-r1)/d,k2=-l2*(r2-r1)/d
          
          拼出x=r1+k1*m1=r1+l1*(r2-r1)/d*m1
          最终的表达式x=r1+l1*(r2-r1)/gcd(m1,m2)*m1
          x就是原来方程组的特解
          原方程组的解系即为x+z*lcm(m1,m2) zEZ
          就是x0=x  mod lcm(m1,m2) 
           
      */
      ll gcd(ll a,ll b){
          if(a<0)
              a*=-1;
          if(b<0)
              b*=-1 ;
          if(a<b)
              swap(a,b);
          ll r;
          while(b!=0){
              r=a%b;
              a=b,b=r;
          }
          return a;
          
      }
      
      ll lcm(ll a,ll b){
          return a*b/gcd(a,b);
      }
      ll exgcd(ll a,ll b,ll &x,ll &y){
          if(a<0)
              a*=-1;
          if(b<0)
              b*=-1;
          ll xa=1,ya=0,xb=0,yb=1;
          ll xr,yr,p,r;
          //a=b*p1+r1
          //r=a-p1*b
          while(b!=0){
              p=a/b,r=a%b;
              xr=xa-p*xb;
              yr=ya-p*yb;
              xa=xb,ya=yb;
              xb=xr,yb=yr;
              a=b,b=r;            
          }
          x=xa,y=ya;
          return a;    
      }
      
      
      int main(){
          
          
          ll n;
          //由是,只需要连续求解即可
          cin>>n;
          ll x,r1,r2,m1,m2,l1,l2;
          cin>>m1>>r1;
          
          for(int i=2;i<=n;i++){
              cin>>m2>>r2;
              ll d=gcd(m1,m2);
              ll p1=m1/d,p2=m2/d;
              ll k1=((r2-r1)%m2+m2)%m2;////为什么是m2????????? 
              exgcd(p1,p2,l1,l2);
              //if(k1<0)
              //    k1*=-1;
              r1=r1+l1*k1/gcd(m1,m2)*m1;
              m1=lcm(m1,m2);
          }
          
          
          x=(r1%m1+m1)%m1;
          cout<<x<<endl;
          return 0;
      } 
      View Code

       

  3.lucas定理

    • 组合数问题不论在初赛还是在复赛都极有可能出现,也是许多数学问题的难点所在。
    • 如果在第一题或者第二题忽然分析出组合数取模的裸题,那就直接拿全分,如果自己在考场现场推导,费时还有可能出错。
    • #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      /*
          又...回到了这里...
          在一次又一次理解失败后,我终于放弃了证明lucas
          lucas定理:
          if(b=0) return 1;
          lucas(a,b,p)=lucas(a/p,b/p,p)*c(a%p,b%p,p)
          而此处的组合数计算为:c(m,n)
          if m>n return 0;
          else return ((n!(m^(p-2)%p))%p)*((n-m)!^(p-2)%p)%p;
          记住了吗?
          いくてす! 
      */
      ll jc[100001];
      ll qpow(ll x,ll y,ll p){
          ll a=x;
          ll ans=1;
          while(y>0){
              if(y&1)
                  ans=(ans*a%p);
              y>>=1;
              a=a*a%p;        
          }
          return ans%p;
          
      }
      ll c(ll n,ll m,ll p){
          if(n<m)
              return 0;
          return ((jc[n]*qpow(jc[m],p-2,p)%p)*qpow(jc[n-m],p-2,p)%p);//
      }
      ll lucas(ll x,ll y,ll p){
          if(y==0)
              return 1;
          return lucas(x/p,y/p,p)*c(x%p,y%p,p)%p;//这里一定要取模 
          
      }
      int main(){
          int t;
          cin>>t;
          ll n,m,p;
          jc[0]=1;
          for(int i=1;i<=t;i++){
              cin>>n>>m>>p;
              for(int j=1;j<=p;j++)
                  jc[j]=(j*jc[j-1]%p);
              cout<<lucas(n+m,n,p)<<endl;
              
              
          }
          
          return 0;
      }
      View Code

       

  4.扩展lucas定理

    • 理由同上,可以解决扩展的组合数取模问题。
    • #include <bits/stdc++.h>
      using namespace std;
      #define ll long long
      struct T
      {
          ll num,p;    
      };
      vector <T> p;
      ll anns=0,n,m,P,fac[1000010],cnt=0;
      void exgcd(ll a,ll b ,ll &x, ll &y)//扩欧 
      {
          if(b==0){x=1,y=0;return;}
          exgcd(b,a%b,y,x);
          y-=a/b*x;
          return ;
      }
      void pre()//分解p 
      {
          int res=P;
          for(int i=2;i<=sqrt(P);i++)
          {
              if(res<i) break;
              if(res%i==0)
              {
                  ll cnt=1;
                  while (res%i==0){cnt=cnt*i;res/=i;}
                  p.push_back({i,cnt});
              }
          }
          if(res>1) p.push_back({res,res});
      }
      
      ll quick_pow(ll a,ll b,ll mod)//快速幂 
      {
          if(b==0) return 1;
          if(b==1) return a%mod;
          if(b&1)  return a%mod*quick_pow(a,b-1,mod)%mod;
          ll cun=quick_pow(a,b/2,mod)%mod;
          return cun*cun%mod;
      }
      
      ll preres(ll n,ll p,ll pe)//处理不含p的乘积 
      {
          ll ans=1;
          if(n==0) {return ans;}
          ll rou=1;
          ll rem=1;
          for (ll i=1;i<=pe;i++){if(i%p) rou=rou*i%pe;}
          rou=quick_pow(rou,n/pe,pe);
          for(ll i=pe*(n/pe);i<=n;i++){if(i%p) rem=rem*(i%pe)%pe;}
          return preres(n/p,p,pe)*rou%pe*rem%pe;
      }
      
      ll prepow(ll n,ll p)//处理每一次的指数 
      {
          if(n<p) return 0;
          return prepow(n/p,p)+(n/p);
      }
      
      ll inv(ll a,ll b)//求逆元 
      {
          ll ans,p;
          exgcd(a,b,ans,p);
          return (ans%b+b)%b;
      }
      void crt(ll m,ll a1)//合并答案 
      {
          ll Mul=P;
          ll pre=Mul/m;
          ll nipre;
          nipre=inv(pre,m);
          anns=(anns+a1*pre%Mul*nipre)%Mul; 
      }
      int main()
      {
          scanf("%lld%lld%lld",&n,&m,&P);  //n!/(m!(n-m)!)
          pre();
          for(int i=0;i<p.size();i++)//枚举约数 
          {
              ll mo=p[i].num,pe=p[i].p;
              ll resn=preres(n,mo,pe),resm=preres(m,mo,pe),resM=preres(n-m,mo,pe);//求n! m! (n-m)!中不含mo的部分的乘积 
              ll ans;
              ll nim,niM;
              nim=inv(resm,pe);
              niM=inv(resM,pe);//求逆元
               
               
              ans=quick_pow(mo,prepow(n,mo)-prepow(m,mo)-prepow(n-m,mo)/*mo的次数*/,pe)*resn%pe*nim%pe*niM%pe;
              
              crt(pe,ans);//合并答案 
          }
          printf("%lld",anns);
          return 0;
      }
      /*15 12 60*/
      View Code

       

  5.矩阵快速幂

    • 很多简单递推的数列问题都可以放进矩阵中快速向后扩展,如果使用该算法可以在很多数列题中直接拿全分,性价比极高。
    • 而这种问题通常的难点也是推出公式而不是写一个矩阵快速幂,所以有了这个模板可以空出更多时间给推导矩阵做法。
    • #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      ll mod=1e9+7;
      ll n,m,k;
      ll a[101][101],ans[101][101],update[101][101];
      void expand(){
          
          for(int i=1;i<=n;i++){
              for(int j=1;j<=n;j++){
                  ll sum=0;
                  for(int l=1;l<=n;l++){
                      sum+=a[i][l]*a[l][j];
                      sum%=mod;
                  }
                  sum%=mod;
                  update[i][j]=sum;
              }
          }
          for(int i=1;i<=n;i++)
              for(int j=1;j<=n;j++)
                  a[i][j]=(update[i][j]%mod);
      }
      void mix(){
          for(int i=1;i<=n;i++){
              for(int j=1;j<=n;j++){
                  ll sum=0;
                  for(int l=1;l<=n;l++){
                      sum+=ans[i][l]*a[l][j];    
                      sum%=mod;        
                  }
                  sum%=mod;
                  update[i][j]=sum;
              }
          }
          for(int i=1;i<=n;i++)
              for(int j=1;j<=n;j++)
                  ans[i][j]=update[i][j];
          
      }
      int main(){
          cin>>n>>k;
          for(int i=1;i<=n;i++)
              for(int j=1;j<=n;j++){
                  cin>>a[i][j];
                  if(i==j)
                      ans[i][j]=1;
              }
          
          //int u=1;        
          while(k>0){
              
              if(k&1)
                  mix();
              
              expand();
              k=k>>1;        
              
          }
          for(int i=1;i<=n;i++){
              for(int j=1;j<=n;j++){
                  cout<<(ans[i][j]%mod)<<' ';
              }
              cout<<endl;
          }
          //1 1  1 1
          //1 1  1 1
          //1*1+
          return 0;
      }
      View Code

       

  6.后缀自动机

    • 很多字符串问题可以使用后缀自动机解决,然而自动机的码量往往不小。
    • 其他的字符串算法较之SAM较为简单,KMP、manacher、trie树都不难理解,所以在众多字符串算法中我选择SAM。

  7.可持久化线段树

    • 数据结构题码量巨大,细节很多,一旦出一个小错整题报废。
    • 在众多数据结构中,当属可持久化最为生疏,有了可持久化线段树,就可以轻易变成可持久化数组,可持久化并查集等等,泛用性很强。
    • #include<bits/stdc++.h>
      using namespace std;
      const int h=25000001;
      int fa[h];
      struct leaf{
          int leftson,rightson;
          int ans;
      }tree[h];
      int tot=0;
      int file[h];
      int mirrow(int root){
          tree[++tot]=tree[root];
          return tot;
      }
      int build(int root,int l,int r){
          root=++tot;
          if(l==r){
              tree[root].ans=fa[l];
              return root;
          }        
          int mid=(l+r)/2;
          tree[root].leftson=build(tree[root].leftson,l,mid);
          tree[root].rightson=build(tree[root].rightson,mid+1,r);
          return root;
      }
      int modify(int root,int l,int r,int x,int ch){
          root=mirrow(root);
          if(l==r){
              tree[root].ans=ch;
              return root;
          }
          int mid=(l+r)/2;
          if(x<=mid)
              tree[root].leftson=modify(tree[root].leftson,l,mid,x,ch);
          else
              tree[root].rightson=modify(tree[root].rightson,mid+1,r,x,ch);
          return root;
      }
      int query(int root,int l,int r,int x){
          if(l==r)
              return tree[root].ans;
          int mid=(l+r)/2;
          if(x<=mid)
              return query(tree[root].leftson,l,mid,x);
          else
              return query(tree[root].rightson,mid+1,r,x);
      }
      int n,m;
      int main(){
          scanf("%d%d",&n,&m);
          for(int i=1;i<=n;i++)
              scanf("%d",&fa[i]);
          file[0]=build(0,1,n);
          for(int i=1;i<=m;i++){
              int r,op,p;
              scanf("%d%d%d",&r,&op,&p);
              if(op==1){
                  int x;
                  scanf("%d",&x);
                  file[i]=modify(file[r],1,n,p,x);
              }
              else{
                  file[i]=file[r];
                  cout<<query(file[i],1,n,p)<<endl;
              }
          }
          
          
          return 0;
      }
      View Code

       

  8.树链剖分

    • 树上权值修改操作必备算法,内置线段树,倍增lca,处理树上问题是一个很好的选择、
    • 然而在考场上硬着头皮不出错打完整套轻重链剖分有一定难度,而且细节很多,所以需要这样一个模板。
    • #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      const ll h=1000001;
      inline ll read() {
          int s = 0, w = 1;
          char ch = getchar();
          while(ch < '0' || ch > '9') {
              if(ch == '-') w= -1;
              ch = getchar();
          }
          while(ch >= '0' && ch <= '9') {
              s = s * 10 + ch - '0';
              ch = getchar();
          }
          return s * w;
      }
      ll line[h],val[h];
      int siz[h],dep[h],dfn[h],son[h],top[h],father[h];
      int n,m,s;
      ll mod;
      struct node{
          ll l,r,v,lz;
      }tree[h*4];
      void pushup(int root){
          tree[root].v=tree[root*2].v+tree[root*2+1].v;
          return;
      }
      void build(int start,int end,int root){
          tree[root].l=start;tree[root].r=end;
          tree[root].lz=0;
          if(start==end){
              tree[root].v=line[start];return;
          }
          ll mid=(start+end)/2;
          build(start,mid,root*2);
          build(mid+1,end,root*2+1);
          pushup(root);
      }
      void pushdown(int root){
          if(tree[root].lz!=0){
              tree[root*2].lz+=tree[root].lz;
              tree[root*2+1].lz+=tree[root].lz;
              tree[root*2].v+=tree[root].lz*(tree[root*2].r-tree[root*2].l+1);
              tree[root*2+1].v+=tree[root].lz*(tree[root*2+1].r-tree[root*2+1].l+1);
              tree[root].lz=0;
          }return;
      }
      ll query(ll root,ll l,ll r){
          if(tree[root].l>=l&&tree[root].r<=r){
              return tree[root].v;
          }pushdown(root);
          ll mid=(tree[root].l+tree[root].r)/2;ll res=0;
          if(mid>=l) res+=query(root*2,l,r);
          if(mid<r) res+=query(root*2+1,l,r);
          return res;
           
      } 
      void update(ll root,ll l,ll r,ll p){
          if(tree[root].l>=l&&tree[root].r<=r){
              tree[root].lz+=p;
              tree[root].v+=p*(tree[root].r-tree[root].l+1);
              return;
          }pushdown(root);
          ll mid=(tree[root].l+tree[root].r)/2;
          if(mid>=l) update(root*2,l,r,p);
          if(mid<r) update(root*2+1,l,r,p);
          pushup(root);
      }
      int head[h],last[h],to[h],tot=0;
      void add(int x,int y){
          last[++tot]=head[x];
          head[x]=tot;
          to[tot]=y;
      } 
      void dfs1(int now,int fa){
          father[now]=fa;
          dep[now]=dep[fa]+1;
          
          siz[now]=1;        
              
          for(int i=head[now];i;i=last[i]){
              int nex=to[i];
              if(nex==fa)
                  continue;
              dfs1(nex,now);
              siz[now]+=siz[nex];
              if(siz[son[now]]<siz[nex])
                  son[now]=nex;
          }    
      }
      int tott=0;
      void dfs2(int now,int fa,int up){
          dfn[now]=++tott;
          line[dfn[now]]=val[now];
          top[now]=up;
          if(!son[now])
              return;
          dfs2(son[now],now,up);
          for(int i=head[now];i;i=last[i]){
              int nex=to[i];
              if(nex==son[now]||nex==fa)
                  continue;
              dfs2(nex,now,nex);
          }
      }
      void tmodify(int x,ll ad){
          //cout<<dfn[x]<<" "<<dfn[x]+siz[x]-1<<endl;
          update(1,dfn[x],dfn[x]+siz[x]-1,ad);
      }
      void pmodify(int x,int y,ll ad){
          while(1){
              if(top[x]==top[y])
                  break;
              if(dep[top[x]]<dep[top[y]])
                  swap(x,y);
              update(1,dfn[top[x]],dfn[x],ad);
              x=father[top[x]];
              
          }
          if(dep[x]<dep[y])
              swap(x,y);
          update(1,dfn[y],dfn[x],ad);
      }
      ll pquery(int x,int y){
          ll cnt=0;
          while(1){
              if(top[x]==top[y])
                  break;
              if(dep[top[x]]<dep[top[y]])
                  swap(x,y);
              cnt+=query(1,dfn[top[x]],dfn[x]);
              cnt%=mod;
              x=father[top[x]];
          }
          if(dep[x]<dep[y])
              swap(x,y);
          cnt+=query(1,dfn[y],dfn[x]);
          return cnt%mod;
      }
      ll tquery(int x){
          return query(1,dfn[x],dfn[x]+siz[x]-1)%mod;
      }
      int main(){
          scanf("%d%d%d%d",&n,&m,&s,&mod);
          int a,b;
          for(int i=1;i<=n;i++)
              scanf("%lld",&val[i]);
          for(int i=1;i<n;i++){
              scanf("%d%d",&a,&b);
              add(a,b),add(b,a);
          }
          dfs1(s,0);
          dfs2(s,0,s);
          build(1,n,1);
          int op;
          int x,y;
          ll z;
          for(int i=1;i<=m;i++){
              scanf("%d",&op);
              if(op==1){
                  scanf("%d%d%lld",&x,&y,&z);
                  pmodify(x,y,z);
              }
              if(op==2){
                  scanf("%d%d",&x,&y);
                  printf("%lld\n",pquery(x,y))%mod;
              }
              if(op==3){
                  scanf("%d%lld",&x,&z);
                  tmodify(x,z);
              }
              if(op==4){
                  scanf("%d",&x);
                  printf("%lld\n",tquery(x)%mod);
              }
          }
          return 0;
      }
      View Code

       

  9.最高标号预流推进网络流算法

    • 现阶段网络最大流问题最优解

  10.模拟退火

    • 遇到一些“很奇怪“的问题的时候,有时甚至难以模拟,这种时候只能硬着头皮建模赌一把。
    • 万一跑出来了呢?

这是对于我而言很重要的十个模板,有一半都是数学内容,的确,在八月之前,我还从没有接触过计算机上的数论题,初赛复习更是被这些东西深深困扰。

说句闲话,数学真的是是很重要的一门学科。

其实如果再给我一点空间,我还会考虑高精度,但是一般情况下考试的时候写高精能拿到的分数并不多,更多时候可能会选择把时间拿来优化算法。

以上。

posted on 2022-08-26 14:12  timedrop  阅读(39)  评论(0编辑  收藏  举报