[2019赛前冲刺]考试技巧及模版(第二部分)

本博客是该模板的第二部分
注:由于编者太弱,以下知识可能仅仅只是给自己写的,对dalao没有任何参考价值,请谅解。

4.数据结构相关

4.1 线段树

4.1.1 线段树维护与查询技巧

众所周知我们可以打标记维护信息。
1.当操作较多时,打标记就非常恶心,例如
考虑用形如
[AB...siz]\begin{bmatrix} A\\ B\\ ...\\ siz\\ \end{bmatrix} AB...siz
的矩阵维护,其中AAA,BBB,…均为标记。
将转移写成一个k×kk×kk×k的矩阵,可以使用矩阵乘法快速维护。
例题:「THUSCH 2017」大魔法师
2.当区间数值过大或者是浮点数时,考虑使用离散化。
使用sort,unique,lower_bound等函数几步搞定。
例题:光线追踪
3.当需要维护多颗线段树时,将线段树打成struct,特别是线段树之间信息对传的情况,一定要冷静调试…
例题:[YL2019 day2]two

4.1.2 可持久化线段树

没怎么深入学过…
只会板题qwq

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 200000
int n,Q;
int a[MAXN+5],b[MAXN+5];
int root[MAXN+5];
int rnum;
struct node
{
    int lc,rc;
    int sum;
}tree[MAXN*20+5];
void Build(int &x,int l,int r)
{
    x=++rnum;
    if(l==r)return ;
    int mid=(l+r)>>1;
    Build(tree[x].lc,l,mid);
    Build(tree[x].rc,mid+1,r);
}
int Insert(int x,int l,int r,int pos)
{
    int Nr=++rnum;
    tree[Nr]=tree[x];
    tree[Nr].sum=tree[x].sum+1;
    if(l==r)return Nr;
    int mid=(l+r)>>1;
    if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos);
    else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos);
    return Nr;
}
int Query(int Lr,int Rr,int l,int r,int val)
{
    int Pv=tree[tree[Rr].lc].sum-tree[tree[Lr].lc].sum;
    if(l==r)return l;
    int mid=(l+r)>>1;
    if(val<=Pv)return Query(tree[Lr].lc,tree[Rr].lc,l,mid,val);
    else return Query(tree[Lr].rc,tree[Rr].rc,mid+1,r,val-Pv);
}
int main()
{
    scanf("%d%d",&n,&Q);
    for(int i=1;i<=n;i++)
    {scanf("%d",&a[i]);b[i]=a[i];}
    sort(b+1,b+n+1);
    int p=unique(b+1,b+n+1)-b-1;
    Build(root[0],1,p);
    for(int i=1;i<=n;i++)
    {
        int P=lower_bound(b+1,b+p+1,a[i])-b;
        root[i]=Insert(root[i-1],1,p,P);
    }
    while(Q--)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",b[Query(root[l-1],root[r],1,p,k)]);
    }
}

4.1.3 线段树合并

顾名思义,就是将两个线段树合并起来。
常用于一些比较麻烦的子树问题。
注意空间通常要开到50倍。
模板:[HNOI2012]永无乡

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXN 100000
int rt[MAXN+5],fa[MAXN+5],posd[MAXN+5];
int read()
{
    int x=0,F=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*F;
}
int ask(int x){return (fa[x]!=x)?fa[x]=ask(fa[x]):x;}
struct Seg_tree
{
    int cnt;
    struct node
    {
        int ch[2],sum;
    }p[MAXN*50+5];
    Seg_tree(){cnt=0;}
    int Insert(int l,int r,int pos)
    {
        int id=++cnt;
        p[id].sum=1;
        if(l==r)return id;
        int mid=(l+r)>>1;
        if(pos<=mid)p[id].ch[0]=Insert(l,mid,pos);
        else p[id].ch[1]=Insert(mid+1,r,pos);
        return id;
    }
    int merge(int c1,int c2,int l,int r)
    {
    	//printf("%d %d->%d %d\n",l,r,c1,c2); 
        if(!c1&&!c2)return 0;
        if(!c1)return c2;
        if(!c2)return c1;
        int id=++cnt,mid=(l+r)>>1;
        p[id].sum=p[c1].sum+p[c2].sum;
        p[id].ch[0]=merge(p[c1].ch[0],p[c2].ch[0],l,mid);
        p[id].ch[1]=merge(p[c1].ch[1],p[c2].ch[1],mid+1,r);
        return id;
    }
    int Query(int x,int l,int r,int k)
    {
    	//printf("%d %d %d\n",x,p[x].sum,k);
        if(p[x].sum<k)return -1;
        if(l==r)return posd[l];
        int mid=(l+r)>>1;
        int lc=p[x].ch[0],rc=p[x].ch[1];
        if(k<=p[lc].sum)return Query(lc,l,mid,k);
        else return Query(rc,mid+1,r,k-p[lc].sum);
    }
}Tr;
int n,m,Q;
void add(int u,int v)
{
    int x=ask(u),y=ask(v);
    if(x!=y)
    {
        fa[x]=y;
        rt[y]=Tr.merge(rt[x],rt[y],1,n);
		//printf("%d %d %d\n",x,y,rt[y]);
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        int rnk=read();
        fa[i]=i;
        rt[i]=Tr.Insert(1,n,rnk);posd[rnk]=i;
    }
    for(int i=1;i<=m;i++)add(read(),read());
    scanf("%d",&Q);
    while(Q--)
    {
        char c[1];
        scanf("%s",c);
        int x=read(),k=read();
        if(c[0]=='B')add(x,k);
        else {printf("%d\n",Tr.Query(rt[ask(x)],1,n,k));}
    }
}

4.2 树链剖分

本身概念很简单,然而…
这代码长度…[令人感动]
模板:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 100000
int n,m,rt,MOD,Q;
int val[MAXN+5],dep[MAXN+5],hvs[MAXN+5];
int siz[MAXN+5],id[MAXN+5],fa[MAXN+5],ct;
int top[MAXN+5],head[MAXN+5],nval[MAXN+5];
int tp,ecnt;
int read(){
    int x=0,F=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*F;
}
void Init()
{ecnt=0;memset(head,-1,sizeof(head));}
struct edge{int to,nxt;}e[MAXN*2+5];
void add(int u,int v)
{
    e[++ecnt]=(edge){v,head[u]};
    head[u]=ecnt;
    e[++ecnt]=(edge){u,head[v]};
    head[v]=ecnt;
}
struct Seg_tree
{
    struct node
    {
        int sum,tag;
    }p[MAXN*4+5];
    void Build(int x,int l,int r)
    {
        if(l==r){p[x].sum=nval[l]%MOD;return ;}
        int mid=(l+r)>>1;
        Build(x<<1,l,mid);
        Build(x<<1|1,mid+1,r);
        p[x].sum=(p[x<<1].sum+p[x<<1|1].sum)%MOD;
    }
    void push_down(int x,int l,int r)
    {
        if(p[x].tag)
        {
            int mid=(l+r)>>1;
            (p[x<<1].sum+=1LL*(mid-l+1)*p[x].tag%MOD)%MOD;
            (p[x<<1|1].sum+=1LL*(r-mid)*p[x].tag%MOD)%MOD;
            (p[x<<1].tag+=p[x].tag)%MOD;
            (p[x<<1|1].tag+=p[x].tag)%MOD;
            p[x].tag=0;
        }
    }
    void Insert(int x,int l,int r,int L,int R,int num)
    {
        if(r<L||R<l)return ;
        if(L<=l&&r<=R)
        {
            p[x].sum+=1LL*(r-l+1)*num%MOD;
            p[x].tag+=num;
            return ;
        }
        push_down(x,l,r);
        int mid=(l+r)>>1;
        Insert(x<<1,l,mid,L,R,num);
        Insert(x<<1|1,mid+1,r,L,R,num);
        p[x].sum=(p[x<<1].sum+p[x<<1|1].sum)%MOD;
    }
    int Query(int x,int l,int r,int L,int R)
    {
        if(r<L||R<l)return 0;
        if(L<=l&&r<=R)return p[x].sum;
        push_down(x,l,r);
        int mid=(l+r)>>1;
        return (Query(x<<1,l,mid,L,R)+Query(x<<1|1,mid+1,r,L,R))%MOD;
    }
}Tr;
void dfs1(int x)
{
    int ms=0;
    siz[x]=1;
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int nxt=e[i].to;
        if(nxt==fa[x])continue;
        fa[nxt]=x;
        dep[nxt]=dep[x]+1;
        dfs1(nxt);
        siz[x]+=siz[nxt];
        if(ms<siz[nxt])hvs[x]=nxt,ms=siz[nxt];
    }
}
void dfs2(int x,int ntop)
{
    id[x]=++ct;
    nval[ct]=val[x];
    top[x]=ntop;
    if(hvs[x])dfs2(hvs[x],ntop);
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int nxt=e[i].to;
        if(nxt==hvs[x]||nxt==fa[x])continue;
        dfs2(nxt,nxt);
    }
}
void modf_chain(int x,int y,int num)
{
	num%=MOD;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        Tr.Insert(1,1,n,id[top[x]],id[x],num);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    Tr.Insert(1,1,n,id[x],id[y],num);
}
int Query_chain(int x,int y)
{
    int res=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        res=(res+Tr.Query(1,1,n,id[top[x]],id[x]))%MOD;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    res=(res+Tr.Query(1,1,n,id[x],id[y]))%MOD;
    return res;
}
int main()
{
    Init();
    scanf("%d%d%d%d",&n,&Q,&rt,&MOD);
    for(int i=1;i<=n;i++)val[i]=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        add(x,y);
    }
    dep[rt]=1;
    dfs1(rt);
    dfs2(rt,rt);
    Tr.Build(1,1,n);
    while(Q--)
    {
        scanf("%d",&tp);
        int x=read(),y,z;
        if(tp==1)y=read(),z=read(),modf_chain(x,y,z);
        if(tp==2){y=read();printf("%d\n",Query_chain(x,y));}
        if(tp==3)z=read(),Tr.Insert(1,1,n,id[x],id[x]+siz[x]-1,z);
        if(tp==4)printf("%d\n",Tr.Query(1,1,n,id[x],id[x]+siz[x]-1));
    }
}

4.3 莫队/分块

4.3.1 分块

神奇O(nn)O(n\sqrt{n})O(nn)算法。
需要注意的是,当询问和插入复杂度不均等时,我们可以调整块的大小以平衡复杂度,这种方法叫均值法。可以用方程求解。
块上暴力维护很多操作都十分方便,例如在上面维护有序数列,凸包,dp数组等,仅仅是复杂度的问题…

4.3.2 莫队

基于O(nn)O(n\sqrt{n})O(nn)分块的离线询问神器。
适用于任何离线的,区间[l,r][l,r][l,r]与每次改变一个单位长度的区间有联系(或较低复杂度转移关系)的一切问题。
例题:[BZOJ3809]Gty的二逼妹子序列 分块+莫队

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN 100000
#define MAXM 1000000
int read()
{
	int x=0,F=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')F=-1;s=getchar();}
	while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
	return x*F;
}
int cnt[MAXN+5],bnum[MAXN+5];
int K[MAXN+5],to[MAXN+5],ans[MAXM+5];
struct Query
{
	int l,r,a,b,id;
}Q[MAXM+5];
int n,m,Cl;
bool cmp(Query s1,Query s2)
{
	if(to[s1.l]==to[s2.l])return s1.r<s2.r;
	else return to[s1.l]<to[s2.l];
}
void Insert(int x)
{
	cnt[x]++;
	if(cnt[x]==1)bnum[to[x]]++;
}
void popout(int x)
{
	cnt[x]--;
	if(cnt[x]==0)bnum[to[x]]--;
}
int query(int x,int y)
{
	int res=0;
	int pl=to[x],pr=to[y];
	if(pr-pl<=1){
		for(int i=x;i<=y;i++)res+=(cnt[i]>0);
		return res;
	}
	for(int i=x;i<=pl*Cl;i++)
	res+=(cnt[i]>0);
	for(int i=pl+1;i<pr;i++)
	res+=bnum[i];
	for(int i=(pr-1)*Cl+1;i<=y;i++)
	res+=(cnt[i]>0);
	return res;
}
int main()
{
	n=read(),m=read();
	Cl=floor(sqrt(n));
	for(int i=1;i<=n;i++)
	K[i]=read(),to[i]=(i-1)/Cl+1;
	for(int i=1;i<=m;i++)
	{
		Q[i].l=read(),Q[i].r=read();
		Q[i].a=read(),Q[i].b=read(),Q[i].id=i;
	}
	sort(Q+1,Q+m+1,cmp);
	int L=1,R=1;
	Insert(K[1]);
	for(int i=1;i<=m;i++)
	{
		while(L<Q[i].l)popout(K[L++]);
		while(L>Q[i].l)Insert(K[--L]);
		while(R>Q[i].r)popout(K[R--]);
		while(R<Q[i].r)Insert(K[++R]);
		ans[Q[i].id]=query(Q[i].a,Q[i].b);
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}

4.4 启发式合并/长链剖分

简单来说就是将权值较大的子树和他的父亲共用一个dp/dp/dp/统计数组。
与子树大小有关的用树上启发式合并。与深度有关的用长链剖分。
CF600E Lomsat gelral
先直接暴力统计以轻儿子为根的子树内的答案并清空,再统计以轻儿子为根的子树内的答案并保留,最后再次dfs一遍轻儿子并把答案加上,合并到父亲中。
复杂度玄学一般地就是:O(nlogn)O(nlogn)O(nlogn)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<climits>
using namespace std;
#define MAXN 200000
#define MAXC 200000
#define Fi first
#define Se second
#define LL long long
#define LD long double
#define PB push_back
int n;
int color[MAXN+5],dfn[MAXN+5],Ft[MAXN+5],u,v,vc[MAXN+5];
int Qr[MAXN+5],sz[MAXN+5];
LL A_cn;
LL ans[MAXN+5],MAX,num,cnt[MAXC+5];
vector<int> G[MAXN+5];
void dfs1(int x,int fa)
{
    sz[x]=1;
    dfn[x]=++num;
    vc[num]=x;
    int G_size=G[x].size();
    for(int i=0;i<G_size;i++)
    if(G[x][i]!=fa)
    {
        dfs1(G[x][i],x);
        sz[x]+=sz[G[x][i]];
    }
    Ft[x]=++num;
}
void CG(int x,int del)
{
    cnt[color[x]]+=del;
    if(cnt[color[x]]>MAX)MAX=cnt[color[x]],A_cn=color[x];
    else if(cnt[color[x]]==MAX)A_cn+=color[x];
}
void dfs2(int x,int fa,bool keep)
{
    int m_size=-1,big_child=-1,G_size=G[x].size();
    for(int i=0;i<G_size;i++)
    if(G[x][i]!=fa&&sz[G[x][i]]>m_size){m_size=sz[G[x][i]];big_child=G[x][i];}
    for(int i=0;i<G_size;i++)if(G[x][i]!=fa&&G[x][i]!=big_child)dfs2(G[x][i],x,0);
    if(big_child!=-1)dfs2(big_child,x,1);
    for(int i=0;i<G_size;i++)
    {
        int xn=G[x][i];
        if(xn!=fa&&xn!=big_child)
        for(int k=dfn[xn];k<Ft[xn];k++)
        CG(vc[k],1);
    }
    CG(x,1);
    //printf("Point:%d\n",x);
    //for(int i=1;i<=4;i++)printf("color::%d cnt::%d\n",i,cnt[i]);
    ans[x]=A_cn;
    if(keep==0)
    {
        for(int k=dfn[x];k<Ft[x];k++)CG(vc[k],-1);
        A_cn=MAX=0;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&color[i]);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        G[u].PB(v);G[v].PB(u);
    }
    MAX=A_cn=0;
    dfs1(1,0);
    dfs2(1,0,1);
    for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
}

[POI2014]HOT-Hotels加强版
哲学指针优化…
f[u][i]f[u][i]f[u][i]表示uuu子树里距离uuuiii的节点个数
g[u][i]g[u][i]g[u][i]表示uuu子树里两个点x,yx,yx,y到其LCALCALCA的距离都是ddd,LCALCALCAuuu的距离是d−id−idi的方案数

g[u][i]=∑vg[v][j+1]+f[u][i]∗f[v][i−1]g[u][i]=\sum_vg[v][j+1]+f[u][i]*f[v][i-1]g[u][i]=vg[v][j+1]+f[u][i]f[v][i1]
f[u][i]=∑vf[v][i−1]f[u][i]=\sum_vf[v][i-1]f[u][i]=vf[v][i1]
注意到当一个节点只有一个时:
g[u][i]=g[v][j+1]g[u][i]=g[v][j+1]g[u][i]=g[v][j+1]
f[u][i]=f[v][i−1]f[u][i]=f[v][i-1]f[u][i]=f[v][i1]
可以用指针转移实现O(1)O(1)O(1)
每个节点选择深度最大的那一个点转移即可。
注意到每一个点都属于一个长链,而一条长链中的所有节点都可以O(1)O(1)O(1)转移,只有在链顶才会O(该链长度)O(该链长度)O()地暴力转移。
因此复杂度:O(2∑链长度)=O(n)O(2\sum 链长度)=O(n)O(2)=O(n)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<ctime>
using namespace std;
#define MAXM 50000
#define MAXK 1000
#define LL long long
int n,x,y,ecnt;
struct edge
{int nxt,to;
}e[MAXM*2+5];
LL space[MAXM*10+1];
LL *f[MAXM+5],*dp[MAXM+5],*tot=space+MAXM;
int dep[MAXM+5],id[MAXM+5],pnt;
int head[MAXM+5],fa[MAXM+5];
LL ans=0;
inline int read()  
{  
    int x=0,f=1;char s=getchar();  
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}  
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}  
    return x*f;  
}
void add(int u,int v)
{
    e[++ecnt]=(edge){head[u],v};
    head[u]=ecnt;
    e[++ecnt]=(edge){head[v],u};
    head[v]=ecnt;
}
void new_node(int x)
{
    dp[x]=tot;tot=tot+dep[x]*2+1;pnt+=dep[x]*4+2;
    f[x]=tot;tot=tot+dep[x]*2+1;
}
void dfs1(int x)
{
    dep[x]=1;id[x]=0;
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int xnt=e[i].to;
        if(xnt==fa[x])continue;
        fa[xnt]=x;
        dfs1(xnt);
        if(dep[x]<dep[xnt]+1)
        dep[x]=dep[xnt]+1,id[x]=xnt;
    }
}
void dfs2(int x)
{
    dp[x][0]=1;
    if(id[x])
    {
        dp[id[x]]=dp[x]+1;
        f[id[x]]=f[x]-1;//神奇指针
        dfs2(id[x]);
        ans+=f[id[x]][1];
    }
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int xnt=e[i].to;
        if(xnt==fa[x]||xnt==id[x])continue;
        new_node(xnt);
        dfs2(xnt);
        for(int j=dep[xnt];j>=0;j--)
        {
            ans+=dp[x][j]*f[xnt][j+1];
            if(j>0)
            {
                ans+=f[x][j]*dp[xnt][j-1];
                f[x][j]+=dp[x][j]*dp[xnt][j-1];
                dp[x][j]+=dp[xnt][j-1];
            }
            f[x][j]+=f[xnt][j+1];
        }
    }
}
int main()
{
    while(~scanf("%d",&n)&&n)
    {
        pnt=MAXM;
        tot=space+MAXM;
        ecnt=0;ans=0;
        for(int i=1;i<=n;i++)head[i]=-1;
        for(int i=1;i<n;i++)
        {
            x=read(),y=read();
            add(x,y);
        }
        dfs1(1);
        new_node(1);
        dfs2(1);
        printf("%lld\n",ans);
        for(int i=MAXM;i<=pnt;i++)
        space[i]=0;
    }
}

更多例题:CF 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths[将回文操作转为二进制,从而变成异或运算,启发式合并统计]

4.5 树状数组/CDQ分治/K-D树

显然这一部分要说的是二维数点问题
离线:CDQ分治,树状数组
强制在线:K-D树
小技巧:强制在线可加一维时间变成离线问题

4.5.1 树状数组

相信都会…这里就…

int lowbit(int x)
{return x&(-x);}
void Insert(int x,LL num)
{while(x<=n)C[x]+=num,x+=lowbit(x);}
LL Sum(int x)
{LL sum=0;while(x>0)sum+=C[x],x-=lowbit(x);return sum;}

4.5.2 CDQ分治

二维偏序是将第一位维好序,求逆序对。
三维偏序就是在判断时加个树状数组即可。
归并排序不用写了吧qwq…

4.5.3 K-D树

又一种玄学数据结构.
复杂度O(nn)O(n\sqrt{n})O(nn)
然而很容易不平衡,一旦不平衡就要暴力重构。
然而自己太弱只有一个不重构的…

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define MAXN 1000000
#define MAXM 500000
#define MAXK 2
#define INF 1000000000
int n,Q,root,D;
struct data
{
    int val[MAXK],Min[MAXK],Max[MAXK],L,R;
    data(int x=0,int y=0)
    {
        L=R=0,val[0]=x,val[1]=y;
    }
}Qu[MAXM+5];
bool operator<(data A,data B)
{
    return A.val[D]<B.val[D];
}
struct KDtree
{
    int ans;
    data t[MAXN+5],T;
    void UP(data A,data B,int k)
    {
    	A.Min[k]=min(A.Min[k],B.Min[k]);
    	A.Max[k]=max(A.Max[k],B.Max[k]);
    }
    void EQ(int A)
    {
        for(int i=0;i<MAXK;i++)
        t[A].Max[i]=t[A].Min[i]=t[A].val[i];
    }
    void pushup(int k)
    {
        data l=t[t[k].L],r=t[t[k].R];         
        for(int i=0;i<MAXK;i++)
        {
            if (t[k].L) t[k].Min[i]=min(t[k].Min[i],l.Min[i]),t[k].Max[i]=max(t[k].Max[i],l.Max[i]);
            if (t[k].R) t[k].Min[i]=min(t[k].Min[i],r.Min[i]),t[k].Max[i]=max(t[k].Max[i],r.Max[i]);
        }
    }
    int build(int L,int R,int k)
    {
    	D=k;
        int mid=(L+R)>>1;
        nth_element(Qu+L,Qu+mid,Qu+R+1);
        t[mid]=Qu[mid];
        EQ(mid);
        if(L<mid)t[mid].L=build(L,mid-1,k^1);
        if(R>mid)t[mid].R=build(mid+1,R,k^1);
        pushup(mid);
        return mid;
    }
    void insert(data p)
    {
        T=p;
        INSERT(root,0);
    }
    void INSERT(int num,int k)
    {
        if(T.val[k]>=t[num].val[k])
        {
            if(t[num].R)INSERT(t[num].R,k^1);
            else
            {
                t[num].R=++n,t[n]=T;
                EQ(n);
            }
        }
        else
        {
            if(t[num].L)INSERT(t[num].L,k^1);
            else
            {
                t[num].L=++n,t[n]=T;
                EQ(n);
            }
        }
        pushup(num);
    }
    int get(data p)
    {
    	int cnt=0;
    	for(int i=0;i<MAXK;i++)
    	cnt+=max(p.Min[i]-T.val[i],0)+max(T.val[i]-p.Max[i],0);
    	return cnt;
    }
    int dis(data A,data B)
    {
        int cnt=0;
        for(int i=0;i<MAXK;i++)
        cnt+=abs(A.val[i]-B.val[i]);
        return cnt;
    }
    int query(data p)
    {
        ans=INF;T=p;
        QUERY(root);
        return ans;
    }
    void QUERY(int num)
    {
        int d,dl=INF,dr=INF;
        d=dis(t[num],T);
        ans=min(ans,d);
        if(t[num].L)dl=get(t[t[num].L]);
        if(t[num].R)dr=get(t[t[num].R]);
        if(dl<dr)
        {
            if(dl<ans)QUERY(t[num].L);
            if(dr<ans)QUERY(t[num].R);
        }
        else
        {
            if(dr<ans)QUERY(t[num].R);
            if(dl<ans)QUERY(t[num].L);
        }
    }
}Tree;

int main()
{
    scanf("%d%d",&n,&Q);
    for(int i=1;i<=n;i++)
    scanf("%d%d",&Qu[i].val[0],&Qu[i].val[1]);
    root=Tree.build(1,n,0);
    while(Q--)
    {
        int F,x,y;
        scanf("%d%d%d",&F,&x,&y);
        if(F==1)Tree.insert(data(x,y));
        else printf("%d\n",Tree.query(data(x,y)));
    }
}

再来一道‘简单题

对于修改操作,都可以视作插入一个点,同样,如果插入后导致KD-Tree不平衡,就要暴力重建.
对于查询操作,我们可以维护每一棵子树中所有点x和y坐标的最大/最小值,如果当前子树整棵树都在当前查询矩形内,就可以直接返回这棵子树中的权值和.如果当前子树整棵树都不在当前查询矩形内,就可以直接返回0.否则,先查询当前节点代表的点是否在查询矩形中,然后递归查询当前节点的两个子节点.

没时间打了qwq

5.字符串相关

5.1 hash

5.1.1 普通hash

int get_hash(int val)
{
    for(int i=0;i<Hs[val%MOD].size();i++)
    if(Hs[val%MOD][i].FR==val)return Hs[val%MOD][i].SE;
    return 0;
}
void hash_ins(int val)
{
    for(int i=0;i<Hs[val%MOD].size();i++)
    if(Hs[val%MOD][i].FR==val){Hs[val%MOD][i].SE++;return ;}
    Hs[val%MOD].push_back(make_pair(val,1));
}

5.1.2 RK-hash

又叫滚动hash,和二分套在一起可以解决很多问题。
技巧:找一个不太常见的大质数。
例如900000011900000011900000011,100000012310000001231000000123

LL prep[MAXN+5],rhash[MAXN+5];
LL get_hash(int l,int r)
{
    long long p=(rhash[l]-1LL*rhash[r+1]*prep[r-l+1]%MOD)%MOD;
	p=(p+MOD)%MOD;return p;
}
int main()
{
	prep[0]=1;
	for(int i=1;i<=MAXN;i++)
	prep[i]=(1LL*prep[i-1]*CHSIZ)%MOD;
	rhash[n+1]=0;
	for(int i=n;i>=0;i--)
	rhash[i]=(rhash[i+1]*CHSIZ+s[i]-'a'+1)%MOD;
}

经典例题:[NOI2016]优秀的拆分

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define MOD 900000011
#define MAXN 30000
#define CHSIZ 29
#define LL long long
int T,n;
char s[MAXN+5];
LL prep[MAXN+5],rhash[MAXN+5],A[MAXN+5],B[MAXN+5];
LL get_hash(int l,int r)
{
    long long p=(rhash[l]-1LL*rhash[r+1]*prep[r-l+1]%MOD)%MOD;
	p=(p+MOD)%MOD;return p;
}
int main()
{
    scanf("%d",&T);
    prep[0]=1;
    for(int i=1;i<=MAXN;i++)
    prep[i]=(1LL*prep[i-1]*CHSIZ)%MOD;
    while(T--)
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n+1;i++)A[i]=B[i]=0;
        rhash[n+1]=0;
        for(int i=n;i>=0;i--)
        rhash[i]=(rhash[i+1]*CHSIZ+s[i]-'a'+1)%MOD;
        for(int L=1;L*2<=n;L++)
            for(int i=L*2;i<=n;i+=L)
            {
                if(s[i-L]!=s[i])continue;
                int l=1,r=L,p=0,lst=i-L;
                while(l<=r)
                {
                    int mid=(l+r)>>1;
                    if(get_hash(lst-mid+1,lst)==get_hash(i-mid+1,i))p=mid,l=mid+1;
                    else r=mid-1;
                }
                int ml=i-p+1;
                l=1,r=L,p=0;
                while(l<=r)
                {
                    int mid=(l+r)>>1;
                    if(get_hash(lst,lst+mid-1)==get_hash(i,i+mid-1))p=mid,l=mid+1;
                    else r=mid-1;
                }
                int mr=i+p-1;
                ml=max(ml+L-1,i);
                mr=min(mr,i+L-1);
                if(ml<=mr)
                {
                    B[ml-2*L+1]++,B[mr-2*L+2]--;
                    A[ml]++,A[mr+1]--;
                }
            }
        LL ans=0;
        for(int i=1;i<=n;i++)A[i]+=A[i-1],B[i]+=B[i-1];
        for(int i=1;i<n;i++)ans+=A[i]*B[i+1];
        printf("%lld\n",ans);
    }
}

5.2 manacher算法

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXN 100000
int T;
int n,p[MAXN*2+5];
char s[MAXN*2+5],Ls[MAXN*2+5];
void Init()
{
    s[0]='#',s[1]='$';
    for(int i=2;i<=n*2;i+=2)
    s[i]=Ls[i/2],s[i+1]='$';
    s[n*2+2]='@';
}
int mancher()
{
	Init();
	n=n*2+2;
	int pos,mr=0,maxL=0;
	for(int i=1;i<n;i++)
	{
		if(i<mr)p[i]=min(p[pos*2-i],mr-i);
		else p[i]=1;
		while(s[i-p[i]]==s[i+p[i]])p[i]++;
		if(mr<p[i]+i)mr=p[i]+i,pos=i;
		maxL=max(maxL,p[i]-1);
	}
	return maxL;
}
int main()
{
	while(~scanf("%s",Ls+1))
	{
		n=strlen(Ls+1);
		memset(p,0,sizeof(p));
		memset(s,0,sizeof(s));
		printf("%d\n",mancher());
		memset(Ls,0,sizeof(Ls));//
	}
}

5.3 KMP算法

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXM 10000
#define MAXN 1000000
int T,n,m;
char a[MAXN+5],b[MAXM+5];
int nxt[MAXN+5];
void prepare()
{
	int i=1,j=0;
	nxt[0]=-1,nxt[1]=0;
	while(i<m)
	if(j==-1||b[i]==b[j])nxt[++i]=++j;
	else j=nxt[j];
}
int cal()
{
	int i=0,j=0,cnt=0;
	while(i<n)
	if(j==-1||a[i]==b[j])i++,j++;
	else if(j==m)cnt++,j=nxt[j];
	else j=nxt[j];
	if(j==m)cnt++;
	return cnt;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(nxt,0,sizeof(nxt));
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		scanf("%s%s",b,a);
		n=strlen(a),m=strlen(b);
		prepare();
		printf("%d\n",cal());
	}
}

有关KMP的结论:len-next[i]为此字符串的最小循环节(i为字符串的结尾),另外如果len≡0(mod&ThinSpace;&ThinSpace;len−nexti)len\equiv 0(\mod len-next_i)len0(modlennexti),此字符串的最小周期就为lenlen−nexti\frac{len}{len-next_i}lennextilen

5.4 AC自动机

模板:Keywords Search

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
#define MAXN 1000000
#define MAXCH 26
int n,T;
char Ls[MAXN+5];
struct AC_automaton
{
	int tr[MAXN+5][MAXCH],cnt;
	int endp[MAXN+5],fail[MAXN+5];
	void Clear(){cnt=0;memset(tr[0],0,sizeof(tr[0]));}
	void Insert(char *s)
	{
	    int p=0,len=strlen(s);
	    for(int i=0;i<len;i++)
	    {
	        int num=s[i]-'a';
	        if(!tr[p][num])
	        {
	            tr[p][num]=++cnt;
	            memset(tr[cnt],0,sizeof(tr[cnt]));
	            endp[cnt]=0,fail[cnt]=0;
	        }
	        p=tr[p][num];
	    }
	    endp[p]++;
	}
	void Build()
	{
	    queue<int> Q;
	    for(int i=0;i<MAXCH;i++)
	    if(tr[0][i])Q.push(tr[0][i]);
	    while(!Q.empty())
	    {
	        int k=Q.front();
	        Q.pop();
	        for(int i=0;i<MAXCH;i++)
	        if(tr[k][i])
	        {
	            fail[tr[k][i]]=tr[fail[k]][i];
	            Q.push(tr[k][i]);
	        }
	        else tr[k][i]=tr[fail[k]][i];
	    }
	}
	int Query(char *s)
	{
	    int p=0,res=0,len=strlen(s);
	    for(int i=0;i<len;i++)
	    {
	        int num=s[i]-'a';
	        p=tr[p][num];
	        for(int j=p;j&&~endp[j];j=fail[j])
	        res+=endp[j],endp[j]=-1;
	    }
	    return res;
	}
}ac;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
	    scanf("%d",&n);
	    ac.Clear();
	    for(int i=1;i<=n;i++)
	    {scanf("%s",Ls);ac.Insert(Ls);}
	    ac.Build();
	    scanf("%s",Ls);
	    printf("%d\n",ac.Query(Ls));
    }
}

经典例题:[HDU]考研路茫茫――单词情结
将AC自动机所构成的Tire图化为邻接矩阵。kkk次行走相当于矩阵的kkk次方。矩阵快速幂即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define MAXN 27
#define MAXM 5
#define CHSIZ 26
#define LL long long
#define UL unsigned long long
#define RI register int
int T,n,cnt,k;
char Ls[MAXM+1];
struct matrix
{
	int siz;
	LL mat[MAXN][MAXN];
	inline void Init(int xsiz)
	{
		siz=xsiz;
		for(RI i=0;i<=siz;i++)
			for(RI j=0;j<=siz;j++)mat[i][j]=0;
	}
	inline void Init_r()
	{
		for(RI i=0;i<=siz;i++)mat[i][i]=1;
	}
};
struct AC_automaton
{
    int tr[MAXN][CHSIZ];
    int C_end[MAXN],fail[MAXN];
    inline void Clear()
    {
        cnt=0;
		fail[0]=0;
		memset(tr[0],0,sizeof(tr[0]));
    }
    inline void Insert(char *s)
    {
        RI p=0,len=strlen(s);
        for(RI i=0;i<len;i++)
        {
            int num=s[i]-'a';
            if(!tr[p][num])
			{
				tr[p][num]=++cnt;
				C_end[cnt]=0;
				memset(tr[cnt],0,sizeof(tr[cnt]));
				fail[cnt]=0;
			}
            p=tr[p][num];
        }
        C_end[p]=1;
    }
    inline void Build()
    {
        queue<int> Q;
        memset(fail,0,sizeof(fail));
        for(RI i=0;i<CHSIZ;i++)if(tr[0][i])Q.push(tr[0][i]);
        while(!Q.empty())
        {
            RI k=Q.front();
            Q.pop();
			C_end[k]|=C_end[fail[k]];//在这里就可以处理从k开始跳是否有endpos了
            for(RI i=0;i<CHSIZ;i++)
				if(tr[k][i])
				{
					fail[tr[k][i]]=tr[fail[k]][i];
					Q.push(tr[k][i]);
				}
				else tr[k][i]=tr[fail[k]][i];
        }
    }
}ac;
matrix C;
matrix operator *(const matrix &A,const matrix &B)
{
	C.siz=A.siz;
	for(RI i=0;i<=A.siz;i++)
		for(RI j=0;j<=A.siz;j++)
		{
			C.mat[i][j]=0;
			for(RI k=0;k<=A.siz;k++)
				C.mat[i][j]+=A.mat[k][j]*B.mat[i][k];
		}
	return C;
}
inline void M_pow(matrix &A,matrix &res,int k)
{
	while(k)
	{
		if(k&1)res=res*A;
		A=A*A;
		k>>=1;
	}
}
int main()
{
    while(~scanf("%d%d",&n,&k))
	{
		ac.Clear();
		for(RI i=1;i<=n;i++)
		{
			scanf("%s",Ls);
			ac.Insert(Ls);
		}
		ac.Build();
		matrix res,A;
		res.Init(cnt+1),A.Init(cnt+1);
		res.Init_r();
		for(RI i=0;i<=cnt;i++)
		{
			for(RI j=0;j<CHSIZ;j++)
			{
				if(ac.C_end[ac.tr[i][j]])continue;
				A.mat[i][ac.tr[i][j]]++;
			}
		}
		for(int i=0;i<=cnt+1;i++)A.mat[i][cnt+1]=1;
		M_pow(A,res,k);
		UL p1=0;
		for(RI i=0;i<=cnt+1;i++)p1=p1+res.mat[0][i];
		p1--;
		matrix B;
		B.Init(1),res.Init(1);
		res.Init_r();
		B.mat[0][0]=26,B.mat[1][0]=1,B.mat[1][1]=1;
		M_pow(B,res,k);
		UL p2=res.mat[1][0]+res.mat[0][0];
		p2--;
		p2-=p1;
		printf("%I64u\n",p2);
	}
}

2.Wireless Password [AC自动机上dp入门]
定义dp[i][j][S]dp[i][j][S]dp[i][j][S]为串长为iii,现在在AC自动机上匹配到位置jjj,已经有SSS状态的字符完成匹配的方案数。
直接dp即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define MAXN 26
#define MAXM 101
#define MAXK 10
#define CHSIZ 26
#define MOD 20090717
#define LL long long
#define RI int
int n,m,k;
char Ls[MAXK+1];
int pop_cnt[1<<MAXK];
struct AC_automaton
{
    int tr[MAXM][CHSIZ],cnt;
    int C_end[MAXM],fail[MAXM];
    int dp[MAXN][MAXM][1<<MAXK];
    void Clear()
    {
        cnt=0;
        fail[0]=0;
        memset(tr[0],0,sizeof(tr[0]));
    }
    void Insert(char *s,int id)
    {
        RI p=0,len=strlen(s);
        for(RI i=0;i<len;i++)
        {
            int num=s[i]-'a';
            if(!tr[p][num])
            {
                tr[p][num]=++cnt;
                C_end[cnt]=0;
                memset(tr[cnt],0,sizeof(tr[cnt]));
                fail[cnt]=0;
            }
            p=tr[p][num];
        }
        C_end[p]|=(1<<id);
    }
    void Build()
    {
        queue<int> Q;
        memset(fail,0,sizeof(fail));
        for(RI i=0;i<CHSIZ;i++)if(tr[0][i])Q.push(tr[0][i]);
        while(!Q.empty())
        {
            RI k=Q.front();
            Q.pop();
            C_end[k]|=C_end[fail[k]];
            for(RI i=0;i<CHSIZ;i++)
                if(tr[k][i])
                {
                    fail[tr[k][i]]=tr[fail[k]][i];
                    Q.push(tr[k][i]);
                }
                else tr[k][i]=tr[fail[k]][i];
        }
    }
    int DP()
    {
        memset(dp,0,sizeof(dp));
        dp[0][0][0]=1;
        for(int i=0;i<n;i++)
            for(int j=0;j<=cnt;j++)
                for(int S=0;S<(1<<m);S++)
                {
                    if(dp[i][j][S]==0)continue;
                    for(int p=0;p<CHSIZ;p++)
                    {
                        int &tmp=dp[i+1][tr[j][p]][S|C_end[tr[j][p]]];
                        tmp=tmp+dp[i][j][S];
                        tmp%=MOD;
                    }
                }
        int ans=0;
        for(int S=0;S<(1<<m);S++)
        {
            if(pop_cnt[S]<k)continue;
            for(int j=0;j<=cnt;j++)
            {
                ans=ans+dp[n][j][S];
                ans%=MOD;
            }
        }
        return ans;
    }
}ac;
int main()
{
	for(int S=0;S<(1<<MAXK);S++)
	{
		int cnt=0,ps=S;
		while(ps)ps-=ps&(-ps),cnt++;
		pop_cnt[S]=cnt;
	}
    while(~scanf("%d%d%d",&n,&m,&k)&&n)
    {
        ac.Clear();
        for(int i=0;i<m;i++)
        {
            scanf("%s",Ls);
            ac.Insert(Ls,i);
        }
        ac.Build();
        printf("%d\n",ac.DP());//
    }
}

5.5 后缀自动机

考虑到题目都几乎不可做就弃掉了

6.计算几何相关

并没有深入地学过…
写点最基础的公式就跑吧…
感觉三年初中废了

6.1 叉积与点积

叉积:对于所有 u,v∈R3u, v ∈ \R^3u,vR3,定义:u×v=(∣u∣∣v∣sin⁡θ)u × v = (|u||v|\sin θ)u×v=(uvsinθ)其中 θ 为 u v 之间的夹角,n 为垂直于 u v 所在平面的单位。
点积:对于任意两个向量 u, v,定义:u⋅v=∣u∣∣v∣cosθu · v = |u||v| cos θuv=uvcosθ

6.2 点,直线与圆的位置

两线段 ABABAB,CDCDCD
规范相交:两线段的交点严格在线段内部(与线段端点不相交)
(CB⃗×CD⃗)(CA⃗×CD⃗)&lt;0(\vec{CB} ×\vec{CD})(\vec{CA} ×\vec{CD})&lt;0(CB×CD)(CA×CD)<0
(AD⃗×AB⃗)(AC⃗×AB⃗)&lt;0(\vec{AD} ×\vec{AB})(\vec{AC} ×\vec{AB})&lt;0(AD×AB)(AC×AB)<0
非规范相交:两线段交点在端点上,在规范相交的基础上判断某条线
段的端点是否在另一线段上。
PPP 与直线 ABABAB
距离:dist(P,AB)=∣AB⃗×AP⃗∣∣AB∣dist(P, AB) = \frac{|\vec{AB}×\vec{AP}|}{|AB|}dist(P,AB)=ABAB×AP
其它都没学过…
求公切线:如下图,先求出旋转角度,然后移动向量。
qwq
于是就有了…「THUSC2017」宇宙广播
[感觉自己菜爆了]

7.非传统题相关

7.1 提交答案型

今年估计又有两道提交答案…

7.1.1 最优型问题(NP)

大多数情况都是问你一个NP问题,求较优解。
写过一道简单提答的题解[NOI2008]赛程安排
也写过「THUWC 2017」大葱的神力算是THU中最简单的一道提交答案题吧…然而搞了将近3个小时的8−108-10810还是只有83分…[我好菜啊…]
这两道都属于NP问题,总体来说这类问题都相对简单,常常用来送分,但是满分却又是不可能的事。
常见骗分方法:

  1. 输出一个符合题意的答案,通常得1分。
  2. 伪造一个看起来十分不靠谱的贪心,通常得3~7分不等。
  3. 观察数据的特性,或者使用电脑跑程序分析。
  4. 将一些问题的限制调小,转化成原来的数据,通常得8~10分不等。
    常见算法:模拟退火,爬山算法,网络流,图论相关算法等。

7.1.2 构造型问题

常见的问法有:

  1. 给你exe文件,写出源文件。给你输出,还原输入。
  2. 给你几种操作,通过计算或下发的文件,构造出能够让任务成功的方案,通常会限制操作次数。
    [蒟蒻做的题太少了qwq]

例如[NOI2016]旷野大计算,就是一道经典的构造型问题。
许多构造题都有逆向思维,只有多练才可能有进步。
这种题一般是只能够按照题目要求来骗分的。
当然也要更加仔细的观察数据特点了。
所以也只能凭rp了qwq.

7.1.3 计算型问题

「THUSC2017」宇宙广播一道经典的计算型提交答案。
一般都和提交答案本身没什么关系。唯一的特点就是它的输入文件我们是可以看见的。而这种题目大多会有多个测试数据,或许我们无法解决整个测试点,但是我们可以观察一小个测试点的特性从而骗分。

7.2 交互型

一类非常有趣的题型。
在OI中,交互题的难度一般不会太高。
通常涉及概率,随机化,或数据结构等。
然而调试起来特别麻烦,所以一定要注意。
例题:硬币翻转

老虎最近沉迷一个翻硬币游戏。
在游戏中,老虎有 100 枚硬币,每次老虎可以选择一枚硬币,并改变其状态——在正面朝上和背面朝上间发生变化。显然,老虎和蒜头的关注点并不一样。蒜头在思考这样的一个游戏:蒜头会持有 100 枚不同的硬币,老虎每次可以给蒜头一个硬币的正反序列来猜测硬币的状态,随后蒜头会告诉老虎他猜对了多少枚硬币,并在 100 枚硬币中等概率选择一枚硬币。若老虎这一轮猜对了这枚硬币,它就会被翻转。老虎的目标是,在某一次完全猜对蒜头的硬币序列,也就是蒜头告诉老虎的答案会是 100。
你能帮助老虎完成这个问题吗?
询问次数限制:200次

一道简单题。
一个显然的想法就是先将答案缩减为0,然后整体翻转一下,就达到了目标。
由于蒜头每次都会翻转一个猜对的硬币,因此不断问同一个硬币序列,总能够使答案为0。注意到题目说蒜头是在全部硬币中选一个翻转,因此在猜对硬币较少时概率会很小。
考虑利用概率小这个性质,我们每次改变一个位置iii,如果它现在这个位置猜对,那么它会有1n\frac{1}{n}n1被翻过来,否则就不会猜对,且猜对个数减1。那么这样的概率就非常小了。

#include "coin.h"
#include <string>
#include<cstdio>
#include<ctime>
#include<iostream>
using namespace std;
void guess(){
	srand(666233);
	std::string s = "";
	for (int x = 0; x < 100; ++x) {
		s+=rand()%2+'0';
	}
	int pts=ask(s);
	for(int i=0;i<100;i++)
	{
		while(1)
		{
			s[i]=((s[i]-'0')^1)+'0';
			int nval=ask(s);
			if(nval<pts){pts=nval;break;}
			pts=nval;
		}
	}
	for (int x = 0; x < 100; ++x)
	s[x]=((s[x]-'0')^1)+'0';
	ask(s);
}

8.细节相关

  1. 注意全局变量在递归函数时使用时最要用一个临时变量存起来。
  2. register int,inline大法好。
  3. 一些较为难拼读的单词:
    priority_queue,operator,unordered_map(不过我认为不会用…),Dijkstra(不明白我为什么会觉得它难拼…),automaton,sterling
    万一哪天脑抽忘掉了呢qwq
  4. 二分注意边界问题。
  5. [待填]

END

终于没了,一口气写完居然没猝死…
三次考试总分能上200就好…

转载于:https://www.cnblogs.com/Panda-hu/p/11145748.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值