板子们
非旋 treap (fhq treap)
namespace FHQ{
int r[N],v[N],s[N],c[N][2],ind;ll sum[N];
inline int brand(){
return((rand()&0x7fff)<<16)+((rand()&0x7fff)<<1)+(rand()&1);
}
inline int newnode(int _v){
r[++ind]=brand(),s[ind]=1,v[ind]=sum[ind]=_v;return ind;
}
inline void update(int u){
s[u]=s[c[u][0]]+s[c[u][1]]+1;
sum[u]=sum[c[u][0]]+sum[c[u][1]]+v[u];
}
int merge(int a,int b){
if(!a||!b)return a+b;
if(r[a]<r[b]){
c[a][1]=merge(c[a][1],b);
update(a);
return a;
}else{
c[b][0]=merge(a,c[b][0]);
update(b);
return b;
}
}
void splitv(int a,int b,int&l,int&r){
if(!a){l=r=0;return;}
if(v[a]<=b)l=a,splitv(c[a][1],b,c[a][1],r);
else r=a,splitv(c[a][0],b,l,c[a][0]);
update(a);
}
void splits(int a,int b,int&l,int&r){
if(!a){l=r=0;return;}
if(b<=s[c[a][0]])r=a,splits(c[a][0],b,l,c[a][0]);
else l=a,splits(c[a][1],b-s[c[a][0]]-1,c[a][1],r);
update(a);
}
void insert(int&u,int x){
int a=0,b=0;
splitv(u,x,a,b);
u=merge(a,merge(newnode(x),b));
}
void erase(int&u,int x){
int a=0,b=0,c=0;
splitv(u,x-1,a,b);
splits(b,1,b,c);
u=merge(a,c);
}
void erase_all(int&u,int x){
int a=0,b=0,c=0;
splitv(u,x-1,a,b);
splitv(u,x,b,c);
u=merge(a,c);
}
int kth(int&u,int x){
int a=0,b=0,c=0,res=0;
splits(u,x-1,a,b);
splits(b,1,b,c);
res=v[b];
u=merge(a,merge(b,c));
return res;
}
};
多项式 inv,ln,exp (ntt)
#include<bits/stdc++.h>
#define D(...) fprintf(stderr,__VA_ARGS__)
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
using namespace std;
typedef vector<int> poly;
const int P=998244353;
int fpow(int a,int b){int res=1;for(;b;b>>=1,a=1ll*a*a%P)if(b&1)res=1ll*res*a%P;return res;}
void pt(const poly&a){for(int i=0;i<(int)a.size();++i)D("%d ",a[i]);D("\n");}
int getlim(int n){int x=1;while(x<=n)x<<=1;return x;}
void ntt(poly&a,int g,int lim){
a.resize(lim);
for(int i=0,j=0;i<lim;++i){
if(i<j)swap(a[i],a[j]);
for(int k=lim>>1;(j^=k)<k;k>>=1);
}
poly w(lim>>1);w[0]=1;
for(int i=1;i<lim;i<<=1){
for(int j=1,wn=fpow(g,(P-1)/(i<<1));j<i;++j)w[j]=1ll*w[j-1]*wn%P;
for(int j=0;j<lim;j+=i<<1)
for(int k=0;k<i;++k){
int x=a[j+k],y=1ll*a[i+j+k]*w[k]%P;
a[j+k]=(x+y)%P,a[i+j+k]=(x-y+P)%P;
}
}
if(g==332748118)for(int i=0,I=fpow(lim,P-2);i<(int)a.size();++i)a[i]=1ll*a[i]*I%P;
}
poly pmul(poly a,poly b){
int need=(int)a.size()+b.size()-1,lim=getlim(need);
ntt(a,3,lim),ntt(b,3,lim);
for(int i=0;i<lim;++i)a[i]=1ll*a[i]*b[i]%P;
ntt(a,332748118,lim);
return a.resize(need),a;
}
poly padd(poly a,poly b){
if(a.size()<b.size()){
for(int i=0;i<(int)a.size();++i)(b[i]+=a[i])%=P;
return b;
}else{
for(int i=0;i<(int)b.size();++i)(a[i]+=b[i])%=P;
return a;
}
}
poly pinv(const poly&a,int n=-1){
if(n==-1)n=a.size();
if(n==1)return poly(1,fpow(a[0],P-2));
poly b=pinv(a,(n+1)>>1),tmp=poly(a.begin(),a.begin()+n);
int lim=getlim(n*2-2);
ntt(b,3,lim),ntt(tmp,3,lim);
for(int i=0;i<lim;++i)b[i]=(2-1ll*b[i]*tmp[i]%P+P)%P*b[i]%P;
ntt(b,332748118,lim);
return b.resize(n),b;
}
poly pdao(const poly&a){
poly b((int)a.size()-1);
for(int i=1;i<(int)a.size();++i)b[i-1]=1ll*a[i]*i%P;
return b;
}
poly pji(const poly&a){
poly b((int)a.size()+1);
for(int i=0;i<(int)a.size();++i)b[i+1]=1ll*a[i]*fpow(i+1,P-2)%P;
return b;
}
poly pln(const poly&a){
poly b(pmul(pdao(a),pinv(a)));
b.resize((int)a.size()-1);
return pji(b);
}
poly pexp(const poly&a,int n=-1){
if(n==-1)n=a.size();
if(n==1)return poly(1,1);
poly b=pexp(a,(n+1)>>1),c(b);
c.resize(n),c=pln(c),--c[0];
for(int i=0;i<n;++i)c[i]=(a[i]-c[i]+P)%P;
poly d(pmul(b,c));
return d.resize(n),d;
}
int main(){
return 0;
}
最大流
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=100005;
int n,m,S,T,dep[N],gap[N];bool vis[N];
struct ed{int nxt,to,v;}G[N*10];int lnk[N],cur[N],pp=1;
void ae(int k1,int k2,int k3){
G[++pp]=(ed){lnk[k1],k2,k3},lnk[k1]=pp;
G[++pp]=(ed){lnk[k2],k1, 0},lnk[k2]=pp;
}
int dfs(int k1,int val){
if(k1==T)return val;
int v=val;
for(int&i=cur[k1];i;i=G[i].nxt){
int j=G[i].to;
if(dep[k1]==dep[j]+1&&G[i].v){
int f=dfs(j,std::min(v,G[i].v));
G[i].v-=f,G[i^1].v+=f,v-=f;
if(!v)return val;
}
}
if(!--gap[dep[k1]++])dep[S]=T+1;
++gap[dep[k1]],cur[k1]=lnk[k1];
return val-v;
}
void dfs2(int k1){
vis[k1]=1;
for(int i=lnk[k1];i;i=G[i].nxt){
int j=G[i].to;
if(G[i].v==0||vis[j])continue;
dfs2(j);
}
}
int main(){
scanf("%d%d",&n,&m);S=n*2+1,T=n*2+2;
//
int res=0;
memcpy(cur,lnk,sizeof(lnk)),gap[0]=T;
while(dep[S]<=T){
res+=dfs(S,0x3f3f3f3f);
}
printf("%d\n",res);
return 0;
}
费用流
typedef long long LL;
const int INF=0X3F3F3F3F;
const LL INFLL=0X3F3F3F3F3F3F3F3FLL;
struct MCMF{
int S,T;
vector<int>lnk,pre;
int mf;
LL mc;
int op;
// min-cost-max-flow if op==+1 (default)
// max-cost-max-flow if op==-1
struct edge{
int nxt,to,w;
LL c;
};
vector<edge>G;
MCMF(){}
MCMF(int n,int _op=1){
init(n,_op);
}
void init(int n,int _op=1){
S=0,T=n+1,op=_op;
lnk.assign(T+1,-1);
G.clear();
}
void ae(int k1,int k2,int k3,LL k4){
k4*=op;
G.push_back((edge){lnk[k1],k2,k3,k4}),lnk[k1]=((int)G.size())-1;
G.push_back((edge){lnk[k2],k1,0,-k4}),lnk[k2]=((int)G.size())-1;
}
vector<LL>dis;
vector<bool>vis;
queue<int>q;
bool spfa(){
pre.assign(T+1,-1);
dis.assign(T+1,INFLL);
vis.assign(T+1,0);
while(!q.empty())q.pop();
dis[S]=0;
vis[S]=1;
q.push(S);
while(!q.empty()){
int k1=q.front();
vis[k1]=0;
q.pop();
for(int i=lnk[k1];i!=-1;i=G[i].nxt)if(G[i].w&&dis[k1]+G[i].c<dis[G[i].to]){
dis[G[i].to]=dis[k1]+G[i].c;
pre[G[i].to]=i;
if(!vis[G[i].to]){
vis[G[i].to]=1;
q.push(G[i].to);
}
}
}
return dis[T]!=INFLL;
}
void zg1(){
int f=INF;
for(int i=pre[T];i!=-1;i=pre[G[i^1].to])f=min(f,G[i].w);
for(int i=pre[T];i!=-1;i=pre[G[i^1].to])G[i].w-=f,G[i^1].w+=f;
mf+=f;
mc+=f*dis[T];
}
vector<int>dep,gap,cur;
int SAP(int k1,int k2){
if(k1==T)return k2;
int k3=k2;
for(int&i=cur[k1];i!=-1;i=G[i].nxt)if(dep[k1]==dep[G[i].to]+1&&G[i].w&&dis[k1]+G[i].c==dis[G[i].to]){
int f=SAP(G[i].to,min(k3,G[i].w));
G[i].w-=f,G[i^1].w+=f,k3-=f;
if(k3==0)return k2;
}
if(dep[k1]<=T&&!--gap[dep[k1]++])dep[S]=T+1;
if(dep[k1]<=T)++gap[dep[k1]];
cur[k1]=lnk[k1];
return k2-k3;
}
void zg2(){
cur=lnk;
dep.assign(T+1,0);
gap.assign(T+1,0);
gap[0]=T+1;
int res=0;
while(dep[S]<=T){
res+=SAP(S,INF);
}
mf+=res;
mc+=res*dis[T];
}
void sol(){
mc=0,mf=0;
while(spfa()){
//zg1();
zg2();
}
mc*=op;
}
};
扩展卢卡斯定理 (exlucas)
#include<cstdio>
#include<vector>
#include<algorithm>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
typedef long long ll;
ll n,m;int p;
int fastpow(int a,ll b,int p){
int res=1;
for(;b;b>>=1,a=1ll*a*a%p)if(b&1)res=1ll*res*a%p;
return res;
}
void exgcd(int a,int b,int&x,int&y){
if(!b){x=a,y=0;return;}
exgcd(b,a%b,x,y);
int t=x;
x=y,y=t-a/b*y;
}
int inv(int a,int b){
int x,y;
exgcd(a,b,x,y);
return (x%b+b)%b;
}
int jc(ll n,int p,int k){
if(!n)return 1;
int res=1;
rep(i,1,k)if(i%p)res=1ll*res*i%k;
res=fastpow(res,n/k,k);
rep(i,1,n%k)if(i%p)res=1ll*res*i%k;
return 1ll*res*jc(n/p,p,k)%k;
}
int C(ll n,ll m,int p,int k){
int a=jc(n,p,k),b=jc(m,p,k),c=jc(n-m,p,k);
ll sum=0;
for(ll i=m;i;i/=p)sum-=i/p;
for(ll i=n-m;i;i/=p)sum-=i/p;
for(ll i=n;i;i/=p)sum+=i/p;
return 1ll*a*fastpow(p,sum,k)%k*inv(b,k)%k*inv(c,k)%k;
}
int crt(int x,int p,int k){
return 1ll*x*(p/k)%p*inv(p/k,k)%p;
}
int exlucas(ll n,ll m,int p){
int x=p,ans=0;
rep(i,2,p/i){
int k=1;
while(x%i==0)k*=i,x/=i;
ans+=crt(C(n,m,i,k),p,k);
ans%=p;
}
if(x>1){
ans+=crt(C(n,m,x,x),p,x);
ans%=p;
}
return ans;
}
int main(){
scanf("%lld%lld%d",&n,&m,&p);
printf("%d\n",exlucas(n,m,p));
return 0;
}
tarjan 求割点
#include<cstdio>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
template<typename T>void rd(T&x){int f=0,c;while((c=getchar())<48||57<c)f^=!(c^45);x=(c&15);while(47<(c=getchar())&&c<58)x=x*10+(c&15);if(f)x=-x;}
template<typename T>T min(const T&x,const T&y){return x<y?x:y;}
const int N=20005,M=100005;
int n,m,ind,root,dfn[N],low[N];bool cut[N];
int lnk[N],pp;
struct ed{int nxt,v;}G[M<<1];
void ae(int u,int v){G[++pp]=(ed){lnk[u],v},lnk[u]=pp;}
void tarjan(int u){
dfn[u]=low[u]=++ind;
int tot=0;
for(int i=lnk[u];i;i=G[i].nxt){
int v=G[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(u!=root&&low[v]>=dfn[u]){
++tot;
if(u!=root||tot>1)cut[u]=1;
}
}else{
low[u]=min(low[u],dfn[v]);
}
}
}
int main(){
rd(n),rd(m);
rep(i,1,m){
int u,v;scanf("%d%d",&u,&v);
ae(u,v),ae(v,u);
}
rep(i,1,n)if(!dfn[i])root=i,tarjan(i);
int tot=0;rep(i,1,n)if(cut[i])++tot;printf("%d\n",tot);
rep(i,1,n)if(cut[i])printf("%d ",i);
return 0;
}
2-sat
//by xay5421 2449670833@qq.com
#include<set>
#include<map>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define int long long
#define SZ(x) ((int)(x).size())
#define ALL(x) (x).begin(),(x).end()
#define debug(...) fprintf(stderr,__VA_ARGS__)
//#define debug(...) ((void)0)
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;typedef unsigned long long ull;typedef std::pair<int,int> pii;
template<typename T>void rd(T&x){int f=0,c;while((c=getchar())<48||57<c)f^=!(c^45);x=(c&15);while(47<(c=getchar())&&c<58)x=x*10+(c&15);if(f)x=-x;}
template<typename T>inline void pt(T x){if(x<0)x=-x,putchar('-');if(x>9)pt(x/10);putchar(x%10+48);}
template<typename T>inline void pt(T x,int c){pt(x),putchar(c);}
template<typename T>inline T max(const T&x,const T&y){return x<y?y:x;}
template<typename T>inline T min(const T&x,const T&y){return x<y?x:y;}
const int N=2000005;
int n,m,nowid,nowc,ind,id[N][2],dfn[N],low[N],st[N],col[N];bool ins[N];
struct ed{int nxt,to;}G[N<<1];int lnk[N],pp;
void ae(int u,int v){G[++pp]=(ed){lnk[u],v},lnk[u]=pp;}
void tarjan(int u){
dfn[u]=low[u]=++ind,ins[u]=1,st[++*st]=u;
for(int i=lnk[u];i;i=G[i].nxt)
if(!dfn[G[i].to]){
tarjan(G[i].to);
low[u]=min(low[u],low[G[i].to]);
}else if(ins[G[i].to]){
low[u]=min(low[u],dfn[G[i].to]);
}
if(dfn[u]==low[u]){
++nowc;
do{
col[st[*st]]=nowc;
ins[st[*st]]=0;
}while(st[(*st)--]!=u);
}
}
signed main(){
rd(n),rd(m);
rep(i,1,n)id[i][0]=++nowid;
rep(i,1,n)id[i][1]=++nowid;
rep(_,1,m){
int i,a,j,b;
rd(i),rd(a),rd(j),rd(b);
ae(id[i][a^1],id[j][b]);
ae(id[j][b^1],id[i][a]);
}
rep(i,1,nowid)if(!dfn[i])
tarjan(i);
rep(i,1,n)
if(col[id[i][0]]==col[id[i][1]]){
puts("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
rep(i,1,n)pt(col[id[i][0]]>col[id[i][1]],' ');
return 0;
}
Link Cut Tree(LCT)
int get(int u){return ch[fa[u]][1]==u;}
int isroot(int u){return ch[fa[u]][0]!=u&&ch[fa[u]][1]!=u;}
//void update(int u){mx[u]=max(val[u],max(mx[ch[u][0]],mx[ch[u][1]]));}
void pushdown(int u){
if(rev[u])std::swap(ch[u][0],ch[u][1]),rev[ch[u][0]]^=1,rev[ch[u][1]]^=1,rev[u]^=1;
}
void rotate(int u){
int p=fa[u],gp=fa[p],x=get(u);
if(!isroot(p))ch[gp][get(p)]=u;fa[u]=gp;
ch[p][x]=ch[u][x^1],fa[ch[u][x^1]]=p;
ch[u][x^1]=p,fa[p]=u;
update(p),update(u);
}
void splay(int u){
st[*st=1]=u;
for(int i=u;!isroot(i);i=fa[i])st[++*st]=fa[i];
for(int i=*st;i>=1;--i)pushdown(st[i]);
for(;!isroot(u);rotate(u))
if(!isroot(fa[u]))
rotate(get(u)==get(fa[u])?fa[u]:u);
}
void access(int u){
for(int i=0;u;i=u,u=fa[u]){
splay(u);
ch[u][1]=i;
update(u);
}
}
void makeroot(int u){
access(u);
splay(u),rev[u]^=1;
}
void link(int u,int v){
makeroot(u),fa[u]=v;
}
int findroot(int u){
access(u),splay(u);
while(ch[u][0])u=ch[u][0];
return splay(u),u;
}
void cut(int u,int v){
makeroot(u),access(v),splay(v);
if(ch[u][1]||fa[u]!=v)return;
fa[u]=ch[v][0]=0;
}
hash,哈希
#include<ctime>
#include<cstdio>
#include<cassert>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200005;
int n,m,a[N],P[2];char s[N];
struct pii{
int x,y;
bool operator==(const pii&b)const{return x==b.x&&y==b.y;}
pii operator+(const pii&b)const{return(pii){(x+b.x)%P[0],(y+b.y)%P[1]};}
pii operator-(const pii&b)const{return(pii){(x-b.x+P[0])%P[0],(y-b.y+P[1])%P[1]};}
pii operator*(const pii&b)const{return(pii){int(1LL*x*b.x%P[0]),int(1LL*y*b.y%P[1])};}
}h[2][N],pw[N],B;
pii get(int l,int r){
int k1=lower_bound(a+1,a+1+m,l)-a-1,k2=upper_bound(a+1,a+1+m,r)-a-1;
return h[l&1][k2]-h[l&1][k1]*pw[k2-k1];
}
int main(){
srand(time(0));
P[0]=998244853,P[1]=998244853+(rand()&0x7fff);
B.x=3,B.y=rand()%10+3;
pw[0]=(pii){1,1};
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;++i)pw[i]=pw[i-1]*B;
for(int i=1;i<=n;++i)if(s[i]=='0'){
a[++m]=i;
h[0][m]=h[0][m-1]*B+(pii){i%2+1,i%2+1};
h[1][m]=h[1][m-1]*B+(pii){2-i%2,2-i%2};
}
int q;scanf("%d",&q);
while(q--){
int k1,k2,k3;
scanf("%d%d%d",&k1,&k2,&k3);
puts(get(k1,k1+k3-1)==get(k2,k2+k3-1)?"Yes":"No");
}
return 0;
}
回文自动机,PAM
struct PAM{
int lst,cnt,tot[N],sum[N],len[N],fa[N],ch[N][26];
char s[N];
int newnode(int a,int b){return len[cnt]=a,fa[cnt]=b,cnt++;}
PAM(){clear();}
void clear(){
rep(i,0,cnt-1){
tot[i]=sum[i]=len[i]=fa[i]=0;
memset(ch[i],0,sizeof(ch[i]));
}
lst=cnt=0;
newnode(0,1),newnode(-1,0);
}
int get(int p,int n){for(;s[n-len[p]-1]!=s[n];p=fa[p]);return p;}
void extend(int c,int pos){
s[pos]=c+'a';
int p=get(lst,pos);
if(!ch[p][c]){
ch[p][c]=newnode(len[p]+2,ch[get(fa[p],pos)][c]);
int k1=ch[p][c];
tot[k1]=tot[fa[k1]]+1;
sum[k1]=(sum[fa[k1]]+len[k1])%P;
}
lst=ch[p][c];
}
};
网络流,最大流,dinic,wxw
struct max_flow_t {
struct edge_t {
int u, v, next, cap, flow;
edge_t () {}
edge_t (int a, int b, int c, int d, int e) : u(a), v(b), next(c), cap(d), flow(e) {}
};
vector <edge_t> G;
vector <int> head, nowhead, d;
int n, s, t, tot;
max_flow_t () { G.clear(); head.clear(); tot = 1; }
max_flow_t (int nn) {
n = nn; s = 0; t = n + 1;
G.clear(); head.clear(); head.resize(n + 2, 0); tot = 1;
}
inline void addedge(int u, int v, int cap) {
G.resize(tot + 3);
G[++tot] = (edge_t) {u, v, head[u], cap, 0}, head[u] = tot;
G[++tot] = (edge_t) {v, u, head[v], 0, 0}, head[v] = tot;
}
int bfs() {
d.clear(); d.resize(n + 2, 0); d[s] = 1;
queue <int> q; q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = G[i].next) {
int v = G[i].v;
if (G[i].cap > G[i].flow && d[v] == 0) {
d[v] = d[u] + 1;
q.push(v);
}
}
}
return d[t];
}
int dfs(int u, int Flow) {
if (u == t || !Flow) return Flow;
int flow = 0, f;
for (int &i = nowhead[u]; i; i = G[i].next) {
int v = G[i].v;
if (d[v] == d[u] + 1 && (f = dfs(v, min(Flow, G[i].cap - G[i].flow))) > 0) {
G[i].flow += f; G[i ^ 1].flow -= f;
flow += f; Flow -= f;
if (!Flow) break;
}
}
return flow;
}
int dinic() {
int ans = 0;
while (bfs()) {
nowhead = head;
ans += dfs(s, INF);
}
return ans;
}
} M;
大数分解,PR
typedef long long LL;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
namespace PR{
const int N=11,p[N]={2,3,5,7,11,13,17,19,23,29,61};
LL add(LL k1,LL k2,LL P){(k1+=k2)>=P&&(k1-=P);return k1;}
LL mul(LL k1,LL k2,LL P){
LL k3=0;
for(;k2;k2>>=1,k1=add(k1,k1,P))if(k2&1)k3=add(k3,k1,P);
return k3;
}
LL fpow(LL k1,LL k2,LL P){
k1%=P;
LL k3=1;
for(;k2;k2>>=1,k1=mul(k1,k1,P))if(k2&1)k3=mul(k3,k1,P);
return k3;
}
bool chk(LL P,LL n){
if(P%n==0||fpow(n,P-1,P)!=1)return 0;
LL k=P-1;
while(~k&1){
k>>=1;
LL t=fpow(n,k,P);
if(t!=1&&t!=P-1)return 0;
if(t==P-1)return 1;
}
return 1;
}
bool isp(LL n){
for(int i=0;i<N;++i){
if(n==p[i])return 1;
if(!chk(n,p[i]))return 0;
}
return 1;
}
vector<LL>d;
void report(LL x){
d.push_back(x);
}
void work(LL n){
if(n==1)return;
if(isp(n)){
report(n);
return;
}
while(1){
LL c=rng()%n,x=rng()%n,y=x,i=1,k=2;
if(c==0||c==2)continue;
do{
LL d=__gcd(abs(x-y),n);
if(d!=1&&d!=n){
work(d),work(n/d);
return;
}
if(++i==k)y=x,k<<=1;
x=(mul(x,x,n)+c)%n;
}while(x!=y);
}
}
vector<pair<LL,int> >sol(LL n){
d.clear();
work(n);
sort(d.begin(),d.end());
vector<pair<LL,int> >res;
for(int i=0,j;i<SZ(d);i=j){
j=i+1;
while(j<SZ(d)&&d[i]==d[j])++j;
res.emplace_back(d[i],j-i);
}
return res;
}
}
本文来自博客园,作者:xay5421,转载请注明原文链接:https://www.cnblogs.com/xay5421/p/templates.html