1 图论
1.1 lca
洛谷 P3379 【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数
N
N
N、
M
M
M、
S
S
S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N − 1 N-1 N−1行每行包含两个正整数 x x x、 y y y,表示 x x x结点和 y y y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M M M行每行包含两个正整数 a a a、 b b b,表示询问 a a a结点和 b b b结点的最近公共祖先。
输出格式
输出包含
M
M
M行,每行包含一个正整数,依次为每一个询问的结果。
1.1.1 tarjan求lca
#include <bits/stdc++.h>
using namespace std;
const int MAXN=500005;
const int MAXM=500005;
int n,m,s;
int f[MAXN],ans[MAXN];
bool used[MAXN];
int size1=0,size2=0;
int head1[MAXN],head2[MAXN];
struct node
{
int id,u,v;
int next;
}edge[MAXN<<1],que[MAXM<<1];
inline void addedge(int u,int v)
{
edge[size1].u=u;
edge[size1].v=v;
edge[size1].next=head1[u];
head1[u]=size1++;
}
inline void addque(int u,int v,int id)
{
que[size2].id=id;
que[size2].u=u;
que[size2].v=v;
que[size2].next=head2[u];
head2[u]=size2++;
}
int find(int x)
{
return x==f[x] ? x : f[x]=find(f[x]);
}
void tarjan(int u)
{
f[u]=u; used[u]=1;
for(int i=head1[u];~i;i=edge[i].next)
{
if(used[edge[i].v]) continue;
tarjan(edge[i].v);
f[edge[i].v]=u;
}
for(int i=head2[u];~i;i=que[i].next)
if(used[que[i].v]) ans[que[i].id]=find(que[i].v);
}
int main()
{
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
addque(u,v,i);
addque(v,u,i);
}
tarjan(s);
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
1.1.2 倍增求lca
#include <bits/stdc++.h>
using namespace std;
const int MAXN=500005;
const int MAXK=22;
int n,m,root;
int fa[MAXN][MAXK],dep[MAXN];
int size=0;
int head[MAXN];
struct EDGE
{
int u,v;
int next;
}edge[MAXN<<1];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void addedge(int u,int v)
{
edge[size].u=u;
edge[size].v=v;
edge[size].next=head[u];
head[u]=size++;
}
void dfs(int u,int deep)
{
dep[u]=deep;
for(int j=1;(1<<(j-1))<=deep;++j) fa[u][j]=fa[fa[u][j-1]][j-1];
for(int i=head[u];~i;i=edge[i].next)
{
if(edge[i].v==fa[u][0]) continue;
fa[edge[i].v][0]=u;
dfs(edge[i].v,deep+1);
}
}
inline int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int j=20;j>=0;--j) if(dep[fa[u][j]]>=dep[v]) u=fa[u][j];
if(u==v) return u;
for(int j=20;j>=0;--j) if(fa[u][j]!=fa[v][j]) u=fa[u][j],v=fa[v][j];
return fa[u][0];
}
int main()
{
//freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read(); m=read(); root=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
addedge(u,v);
addedge(v,u);
}
dfs(root,1);
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
printf("%d\n",lca(u,v));
}
return 0;
}
1.1.3 简化树链剖分
#include <bits/stdc++.h>
using namespace std;
const int MAXN=500005;
int n,m,root;
int size[MAXN],fa[MAXN],son[MAXN],dep[MAXN],top[MAXN];
int esize=0;
int head[MAXN];
struct EDGE
{
int u,v;
int next;
}edge[MAXN<<1];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void addedge(int u,int v)
{
edge[esize].u=u;
edge[esize].v=v;
edge[esize].next=head[u];
head[u]=esize++;
}
void dfs1(int u)
{
size[u]=1;
for(int i=head[u];~i;i=edge[i].next)
{
if(edge[i].v==fa[u]) continue;
fa[edge[i].v]=u;
dep[edge[i].v]=dep[u]+1;
dfs1(edge[i].v);
size[u]+=size[edge[i].v];
if(size[edge[i].v]>size[son[u]]) son[u]=edge[i].v;
}
}
void dfs2(int u,int topf)
{
top[u]=topf;
if(!son[u]) return;
dfs2(son[u],topf);
for(int i=head[u];~i;i=edge[i].next)
{
if(edge[i].v==fa[u]) continue;
if(edge[i].v==son[u]) continue;
dfs2(edge[i].v,edge[i].v);
}
}
inline int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]>dep[v]?v:u;
}
int main()
{
freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read(); m=read(); root=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
addedge(u,v);
addedge(v,u);
}
dfs1(root); dfs2(root,root);
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
printf("%d\n",lca(u,v));
}
return 0;
}
1.2 最小生成树
洛谷 P3366 【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz
输入格式
第一行包含两个整数
N
N
N、
M
M
M,表示该图共有
N
N
N个结点和
M
M
M条无向边。
(
N
<
=
5000
,
M
<
=
200000
)
(N<=5000,M<=200000)
(N<=5000,M<=200000)
接下来M行每行包含三个整数 X i X_i Xi、 Y i Y_i Yi、 Z i Z_i Zi,表示有一条长度为Zi的无向边连接结点 X i X_i Xi、 Y i Y_i Yi
输出格式
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
1.2.1 kruskal生成树
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5005;
const int MAXM=200005;
int n,m;
int f[MAXN];
struct EDGE
{
int u,v,w;
bool operator <(const EDGE &cir) const
{
return w<cir.w;
}
}edge[MAXM];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline int find(int x)
{
return x==f[x]?x:f[x]=find(f[x]);
}
inline int kruskal()
{
int sum=0,cnt=0;
for(int i=1;i<=m;++i)
{
if(find(edge[i].u)==find(edge[i].v)) continue;
f[find(edge[i].u)]=find(edge[i].v);
sum+=edge[i].w; ++cnt;
if(cnt==n-1) break;
}
return cnt==n-1?sum:-1;
}
int main()
{
freopen("input.txt","r",stdin);
n=read(); m=read();
for(int i=1;i<=n;++i) f[i]=i;
for(int i=1;i<=m;++i)
{
edge[i].u=read();
edge[i].v=read();
edge[i].w=read();
}
sort(edge+1,edge+m+1);
int ans=kruskal();
if(ans==-1) printf("orz");
else printf("%d\n",ans);
return 0;
}
1.2.2 prime
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5005;
const int MAXM=200005;
const int INF=1e9+7;
int n,m,ans=0,dis[MAXN];
bool used[MAXN];
int size=0;
int head[MAXN];
struct EDGE
{
int u,v,w;
int next;
}edge[MAXM<<1];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void addedge(int u,int v,int w)
{
edge[size].u=u;
edge[size].v=v;
edge[size].w=w;
edge[size].next=head[u];
head[u]=size++;
}
inline int prime()
{
for(int i=1;i<=n;++i) dis[i]=INF,used[i]=0;
dis[1]=0; int ans=0;
for(int i=1;i<=n;++i)
{
int pos=-1,Min=INF;
for(int i=1;i<=n;++i)
if(!used[i]&&Min>dis[i]) Min=dis[i],pos=i;
used[pos]=true; ans+=Min;
for(int i=head[pos];~i;i=edge[i].next)
dis[edge[i].v]=min(dis[edge[i].v],edge[i].w);
if(pos==-1) return -1;
}
return ans;
}
int main()
{
freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read(); m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read(),w=read();
addedge(u,v,w);
addedge(v,u,w);
}
int ans=prime();
if(ans==-1) printf("orz\n");
else printf("%d\n",ans);
return 0;
}
1.2.3 LCT 求最小生成树
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1000005;
int n,m,idx,ans=0;
int ch[MAXN][2],id[MAXN],mx[MAXN],par[MAXN],st[MAXN],val[MAXN],w[MAXN];
bool r[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
return X*f;
}
inline bool notroot(int x)
{
return ch[par[x]][0]==x||ch[par[x]][1]==x;
}
inline void pushup(int x)
{
id[x]=x; mx[x]=w[x];
if(mx[ch[x][0]]>mx[x]) mx[x]=mx[ch[x][0]],id[x]=id[ch[x][0]];
if(mx[ch[x][1]]>mx[x]) mx[x]=mx[ch[x][1]],id[x]=id[ch[x][1]];
}
inline void pushr(int x)
{
swap(ch[x][0],ch[x][1]);
r[x]^=1;
}
inline void pushdown(int x)
{
if(r[x])
{
if(ch[x][0]) pushr(ch[x][0]);
if(ch[x][1]) pushr(ch[x][1]);
r[x]=0;
}
}
inline bool chk(int x)
{
return ch[par[x]][1]==x;
}
inline void rotate(int x)
{
int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;
if(w) par[w]=y;
if(notroot(y)) ch[z][chk(y)]=x;
par[x]=z;
ch[x][k^1]=y;
par[y]=x;
pushup(y);
pushup(x);
}
inline void splay(int x)
{
int y=x,top=0;
st[++top]=y;
while(notroot(y)) st[++top]=y=par[y];
while(top) pushdown(st[top--]);
while(notroot(x))
{
y=par[x];
if(notroot(y))
{
if(chk(x)==chk(y))
rotate(y);
else
rotate(x);
}
rotate(x);
}
pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=par[y=x])
splay(x),ch[x][1]=y,pushup(x);
}
inline void makeroot(int x)
{
access(x); splay(x);
pushr(x);
}
inline int findroot(int x)
{
access(x); splay(x);
while(ch[x][0]) pushdown(x),x=ch[x][0];
splay(x);
return x;
}
inline void split(int x,int y)
{
makeroot(x);
access(y); splay(y);
}
inline void link(int x,int y)
{
makeroot(x);
if(findroot(y)!=x) par[x]=y;
}
inline void cut(int x,int y)
{
makeroot(x);
if(findroot(y)==x&&par[y]==x&&!ch[y][0])
{
par[y]=ch[x][1]=0;
pushup(x);
}
}
inline bool check(int x,int y)
{
makeroot(x);
if(findroot(y)==x) return true;
else return false;
}
int main()
{
// freopen("input.txt","r",stdin);
n=read(); m=read(); idx=n;
for(int i=1;i<=m;++i)
{
int x=read(),y=read(),z=read();
++idx; w[idx]=z;
if(!check(x,y))
link(x,idx),link(y,idx),ans+=z;
else
{
split(x,y); int now=id[y];
if(mx[now]>z)
{
ans+=(z-mx[now]); splay(now);
par[ch[now][0]]=par[ch[now][1]]=0;
link(x,idx); link(y,idx);
}
}
}
printf("%d",ans);
return 0;
}
1.3 单源最短路径
洛谷 P4779 【模板】单源最短路径(标准版)
题目描述
给定一个
n
n
n 个点,
m
m
m 条有向边的带非负权图,请你计算从
s
s
s 出发,到每个点的距离。
数据保证你能从 s s s 出发到任意点。
输入格式
第一行为三个正整数
n
,
m
,
s
n, m, s
n,m,s。 第二行起
m
m
m 行,每行三个非负整数
u
i
,
v
i
,
w
i
u_i, v_i, w_i
ui,vi,wi,表示从
u
i
u_i
ui到
v
i
v_i
vi有一条权值为
w
i
w_i
wi的有向边。
输出格式
输出一行
n
n
n 个空格分隔的非负整数,表示
s
s
s 到每个点的距离。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
const int MAXM=2e5+5;
int n,m,s;
int dis[MAXN];
bool used[MAXN];
int size=0;
int head[MAXN];
struct EDGE
{
int u,v,w;
int next;
}edge[MAXM];
struct HeapNode
{
int dis,pos;
bool operator <(const HeapNode &cir) const
{
return dis>cir.dis;
}
};
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void addedge(int u,int v,int w)
{
edge[size].u=u;
edge[size].v=v;
edge[size].w=w;
edge[size].next=head[u];
head[u]=size++;
}
void dijkstra()
{
priority_queue <HeapNode> q;
memset(used,0,sizeof(used));
memset(dis,0x3f,sizeof(dis));
q.push((HeapNode){0,s}); dis[s]=0;
while(!q.empty())
{
HeapNode tmp=q.top(); q.pop();
if(used[tmp.pos]) continue;
used[tmp.pos]=true;
for(int i=head[tmp.pos];~i;i=edge[i].next)
{
if(dis[edge[i].v]>dis[tmp.pos]+edge[i].w)
{
dis[edge[i].v]=dis[tmp.pos]+edge[i].w;
q.push((HeapNode){dis[edge[i].v],edge[i].v});
}
}
}
}
int main()
{
// freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read(); m=read(); s=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read(),w=read();
addedge(u,v,w);
}
dijkstra();
for(int i=1;i<=n;++i) printf("%d ",dis[i]);
return 0;
}
1.4 割点(割顶)
P3388 【模板】割点(割顶)
题目描述
给出一个
n
n
n个点,
m
m
m条边的无向图,求图的割点。
输入格式
第一行输入
n
,
m
n,m
n,m
下面 m m m行每行输入 x , y x,y x,y表示 x x x到 y y y有一条边
输出格式
第一行输出割点个数
第二行按照节点编号从小到大输出节点,用空格隔开
#include <bits/stdc++.h>
using namespace std;
const int MAXN=20005;
const int MAXM=100005;
int n,m,ans=0,idx=0;
int dfn[MAXN],low[MAXN];
bool yes[MAXN];
int size=0;
int head[MAXN];
struct EDGE
{
int u,v;
int next;
}edge[MAXM<<1];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void addedge(int u,int v)
{
edge[size].u=u;
edge[size].v=v;
edge[size].next=head[u];
head[u]=size++;
}
void tarjan(int u,int fa)
{
int child=0;
dfn[u]=low[u]=++idx;
for(int i=head[u];~i;i=edge[i].next)
{
if(!dfn[edge[i].v])
{
tarjan(edge[i].v,u);
low[u]=min(low[u],low[edge[i].v]);
++child;
if(u!=fa&&low[edge[i].v]>=dfn[u]) yes[u]=true;
}
else low[u]=min(low[u],dfn[edge[i].v]);
}
if(u==fa&&child>=2) yes[u]=true;
}
int main()
{
// freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read(); m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
addedge(u,v);
addedge(v,u);
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,i);
for(int i=1;i<=n;++i) if(yes[i]) ++ans;
printf("%d\n",ans);
for(int i=1;i<=n;++i) if(yes[i]) printf("%d ",i);
return 0;
}
1.5 缩点
洛谷 P3387 【模板】缩点
题目描述
给定一个
n
n
n 个点
m
m
m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数
n
,
m
n,m
n,m
第二行 n n n 个整数,依次代表点权
第三至 m + m+ m+ 行,每行两个整数 u , v u,v u,v,表示一条 u → v u\rightarrow v u→v 的有向边。
输出格式
共一行,最大的点权之和。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=10005;
const int MAXM=100005;
int n,m,top=0,idx=0,Bcnt=0,ans=0;
int Stack[MAXN],dfn[MAXN],low[MAXN],belong[MAXN];
int x[MAXM],y[MAXM],w[MAXN],size[MAXN],dp[MAXN];
int esize=0;
int head[MAXN];
struct EDGE
{
int u,v;
int next;
}edge[MAXM];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void addedge(int u,int v)
{
edge[esize].u=u;
edge[esize].v=v;
edge[esize].next=head[u];
head[u]=esize++;
}
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
Stack[++top]=u;
for(int i=head[u];~i;i=edge[i].next)
{
if(!dfn[edge[i].v])
{
tarjan(edge[i].v);
low[u]=min(low[u],low[edge[i].v]);
}
else if(!belong[edge[i].v]) low[u]=min(low[u],dfn[edge[i].v]);
}
if(dfn[u]==low[u])
{
++Bcnt; int v;
do
{
belong[v=Stack[top]]=Bcnt;
--top; size[Bcnt]+=w[v];
}while(u!=v);
}
}
int dfs(int u)
{
if(dp[u]) return dp[u];
int maxsum=0;
for(int i=head[u];~i;i=edge[i].next)
maxsum=max(maxsum,dfs(edge[i].v));
return dp[u]=maxsum+size[u];
}
int main()
{
// freopen("input.txt","r",stdin);
memset(head,-1,sizeof(head));
n=read(); m=read();
for(int i=1;i<=n;++i) w[i]=read();
for(int i=1;i<=m;++i)
{
x[i]=read(); y[i]=read();
addedge(x[i],y[i]);
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
memset(head,-1,sizeof(head)); esize=0;
for(int i=1;i<=m;++i)
if(belong[x[i]]!=belong[y[i]]) addedge(belong[x[i]],belong[y[i]]);
for(int i=1;i<=Bcnt;++i) ans=max(ans,dfs(i));
printf("%d",ans);
return 0;
}
2 数据结构
2.1 并查集
洛谷 P3367 【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数
N
N
N、
M
M
M,表示共有
N
N
N个元素和
M
M
M个操作。
接下来 M M M行,每行包含三个整数 Z i Z_i Zi、 X i X_i Xi、 Y i Y_i Yi
当 Z i = 1 Z_i=1 Zi=1时,将 X i X_i Xi与 Y i Y_i Yi所在的集合合并
当 Z i = 2 Z_i=2 Zi=2时,输出 X i X_i Xi与 Y i Y_i Yi是否在同一集合内,是的话输出 Y Y Y;否则话输出 N N N
输出格式
如上,对于每一个
Z
i
=
2
Z_i=2
Zi=2的操作,都有一行输出,每行包含一个大写字母,为
Y
Y
Y或者
N
N
N
#include <bits/stdc++.h>
using namespace std;
const int MAXN=10005;
int n,m;
int f[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline int find(int x)
{
return x==f[x]?x:f[x]=find(f[x]);
}
inline void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx==fy) return;
f[fx]=fy;
}
int main()
{
// freopen("input.txt","r",stdin);
n=read(); m=read();
for(int i=1;i<=n;++i) f[i]=i;
for(int i=1;i<=m;++i)
{
int op=read(),x=read(),y=read();
if(op==1) merge(x,y);
else if(op==2) printf(find(x)==find(y)?"Y\n":"N\n");
}
return 0;
}
2.2 ST表
P3865 【模板】ST表
题目背景
这是一道ST表经典题——静态区间最大值
请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O ( 1 ) O(1) O(1)
题目描述
给定一个长度为
N
N
N 的数列,和
M
M
M 次询问,求出每一次询问的区间内数字的最大值。
输入格式
第一行包含两个整数
N
,
M
N, M
N,M ,分别表示数列的长度和询问的个数。
第二行包含 N N N 个整数(记为 a i a_i ai),依次表示数列的第 i i i 项。
接下来
M
M
M行,每行包含两个整数
l
i
,
r
i
l_i, r_i
li,ri,表示查询的区间为
[
l
i
,
r
i
]
[ l_i, r_i]
[li,ri]
输出格式
输出包含
M
M
M行,每行一个整数,依次表示每一次询问的结果。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=100005;
const int MAXK=30;
int n,m;
int st[MAXN][MAXK],lg[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline int query(int l,int r)
{
int k=lg[r-l+1];
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
freopen("input.txt","r",stdin);
n=read(); m=read();
for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;++i) st[i][0]=read();
for(int j=1;j<=lg[n];++j)
for(int i=1;i+(1<<j)-1<=n;++i)
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
for(int i=1;i<=m;++i)
{
int l=read(),r=read();
printf("%d\n",query(l,r));
}
return 0;
}
2.3 普通平衡树
洛谷 P3369 【模板】普通平衡树
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入
x
x
x 数
删除
x
x
x 数(若有多个相同的数,因只删除一个)
查询
x
x
x 数的排名(排名定义为比当前数小的数的个数
+
1
+1
+1 )
查询排名为
x
x
x 的数
求
x
x
x 的前驱(前驱定义为小于
x
x
x,且最大的数)
求
x
x
x 的后继(后继定义为大于
x
x
x,且最小的数)
输入格式
第一行为
n
n
n,表示操作的个数,下面
n
n
n 行每行有两个数
opt
\text{opt}
opt 和
x
x
x,
opt
\text{opt}
opt 表示操作的序号(
1
≤
opt
≤
6
1 \leq \text{opt} \leq 6
1≤opt≤6 )
输出格式
对于操作
3
,
4
,
5
,
6
3,4,5,6
3,4,5,6 每行输出一个数,表示对应答案
2.3.1 Splay 实现
#include <bits/stdc++.h>
using namespace std;
const int MAXN=200005;
const int INF=1e9+7;
int n,root,Ncnt=0;
int ch[MAXN][2],par[MAXN],size[MAXN],cnt[MAXN],val[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void pushup(int x) {size[x]=size[ch[x][0]]+size[ch[x][1]]+cnt[x];}
inline bool chk(int x) {return ch[par[x]][1]==x;}
inline void rotate(int x)
{
int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;
par[w]=y;
ch[z][chk(y)]=x;
par[x]=z;
ch[x][k^1]=y;
par[y]=x;
pushup(y);
pushup(x);
}
inline void splay(int x,int goal=0)
{
while(par[x]!=goal)
{
int y=par[x],z=par[y];
if(z!=goal)
{
if(chk(x)==chk(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
if(!goal) root=x;
}
inline void insert(int x)
{
int cur=root,p=0;
while(val[cur]!=x&&cur)
{
p=cur;
cur=ch[cur][x>val[cur]];
}
if(cur) ++cnt[cur];
else
{
cur=++Ncnt;
val[cur]=x;
if(p) ch[p][x>val[p]]=cur;
par[cur]=p;
ch[cur][0]=ch[cur][1]=0;
size[cur]=cnt[cur]=1;
}
splay(cur);
}
inline void find(int x)
{
int cur=root;
while(val[cur]!=x&&ch[cur][x>val[cur]])
cur=ch[cur][x>val[cur]];
splay(cur);
}
inline int kth(int k)
{
int cur=root;
while(1)
{
if(k<=size[ch[cur][0]]) cur=ch[cur][0];
else if(k<=size[ch[cur][0]]+cnt[cur]) return cur;
else k-=size[ch[cur][0]]+cnt[cur],cur=ch[cur][1];
}
}
inline int pre(int x)
{
find(x);
if(val[root]<x) return root;
int cur=ch[root][0];
while(ch[cur][1]) cur=ch[cur][1];
return cur;
}
inline int suc(int x)
{
find(x);
if(val[root]>x) return root;
int cur=ch[root][1];
while(ch[cur][0]) cur=ch[cur][0];
return cur;
}
inline void remove(int x)
{
int last=pre(x),next=suc(x);
splay(last); splay(next,last);
int del=ch[next][0];
if(cnt[del]>1) --cnt[del],splay(del);
else ch[next][0]=0;
}
int main()
{
// freopen("input.txt","r",stdin);
n=read(); insert(INF); insert(-INF);
while(n--)
{
int op=read(),x=read();
switch(op)
{
case 1: insert(x); break;
case 2: remove(x); break;
case 3: find(x); printf("%d\n",size[ch[root][0]]); break;
case 4: printf("%d\n",val[kth(x+1)]); break;
case 5: printf("%d\n",val[pre(x)]); break;
case 6: printf("%d\n",val[suc(x)]); break;
default: assert(false); break;
}
}
return 0;
}
2.3.2 vector 模拟
#include <bits/stdc++.h>
using namespace std;
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
int main()
{
freopen("input.txt","r",stdin);
vector <int> ve;
for(int T=read();T;--T)
{
int op=read(),x=read();
if(op==1) ve.insert(lower_bound(ve.begin(),ve.end(),x),x);
else if(op==2) ve.erase(lower_bound(ve.begin(),ve.end(),x));
else if(op==3) printf("%d\n",lower_bound(ve.begin(),ve.end(),x)-ve.begin()+1);
else if(op==4) printf("%d\n",ve[x-1]);
else if(op==5) printf("%d\n",*--lower_bound(ve.begin(),ve.end(),x));
else if(op==6) printf("%d\n",*lower_bound(ve.begin(),ve.end(),x+1));
}
return 0;
}
2.4 Link-Cut-Tree
洛谷 P1501 [国家集训队]Tree II
题目描述
一棵
n
n
n个点的树,每个点的初始权值为
1
1
1。对于这棵树有
q
q
q个操作,每个操作为以下四种操作之一:
+ u v c +\ u\ v\ c + u v c:将u到v的路径上的点的权值都加上自然数 c c c;
− u 1 v 1 u 2 v 2 -\ u_1\ v_1\ u_2\ v_2 − u1 v1 u2 v2:将树中原有的边 ( u 1 , v 1 ) (u_1,v_1) (u1,v1)删除,加入一条新边 ( u 2 , v 2 ) (u_2,v_2) (u2,v2),保证操作完之后仍然是一棵树;
∗ u v c *\ u\ v\ c ∗ u v c:将u到v的路径上的点的权值都乘上自然数c;
/ u v /\ u\ v / u v:询问u到v的路径上的点的权值和,求出答案对于 51061 51061 51061的余数。
输入格式
第一行两个整数
n
n
n,
q
q
q
接下来 n − 1 n-1 n−1行每行两个正整数 u u u, v v v,描述这棵树
接下来 q q q行,每行描述一个操作
输出格式
对于每个
/
/
/对应的答案输出一行
#include <bits/stdc++.h>
#define int unsigned int
using namespace std;
const int MAXN=100009;
const int mod=51061;
int n,m;
int par[MAXN],ch[MAXN][2],val[MAXN],sum[MAXN],size[MAXN],mul[MAXN],add[MAXN],st[MAXN];
bool rev[MAXN];
char op[10];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
return X*f;
}
inline bool notroot(int x)
{
return ch[par[x]][0]==x||ch[par[x]][1]==x;
}
inline void pushup(int x)
{
sum[x]=(sum[ch[x][0]]+sum[ch[x][1]]+val[x])%mod;
size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
}
inline void pushr(int x)
{
swap(ch[x][0],ch[x][1]);
rev[x]^=1;
}
inline void pushm(int x,int k)
{
sum[x]*=k; sum[x]%=mod;
mul[x]*=k; mul[x]%=mod;
add[x]*=k; add[x]%=mod;
val[x]*=k; val[x]%=mod;
}
inline void pusha(int x,int k)
{
sum[x]+=k*size[x]; sum[x]%=mod;
add[x]+=k; add[x]%=mod;
val[x]+=k; val[x]%=mod;
}
inline void pushdown(int x)
{
if(mul[x]!=1) pushm(ch[x][0],mul[x]),pushm(ch[x][1],mul[x]),mul[x]=1;
if(add[x]!=0) pusha(ch[x][0],add[x]),pusha(ch[x][1],add[x]),add[x]=0;
if(rev[x]!=0) {if(ch[x][0])pushr(ch[x][0]);if(ch[x][1])pushr(ch[x][1]);rev[x]=0;}
}
inline bool chk(int x)
{
return ch[par[x]][1]==x;
}
inline void rotate(int x)
{
int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;
if(w) par[w]=y;
if(notroot(y)) ch[z][chk(y)]=x;
par[x]=z;
ch[x][k^1]=y;
par[y]=x;
pushup(y);
pushup(x);
}
inline void splay(int x)
{
int y=x,top=0;
st[++top]=y;
while(notroot(y)) st[++top]=y=par[y];
while(top) pushdown(st[top--]);
while(notroot(x))
{
y=par[x];
if(notroot(y))
{
if(chk(x)==chk(y))
rotate(y);
else
rotate(x);
}
rotate(x);
}
pushup(x);
}
inline void access(int x)
{
for(int y=0;x;x=par[y=x])
splay(x),ch[x][1]=y,pushup(x);
}
inline void makeroot(int x)
{
access(x); splay(x);
pushr(x);
}
inline int findroot(int x)
{
access(x); splay(x);
while(ch[x][0]) pushdown(x),x=ch[x][0];
splay(x);
return x;
}
inline void split(int x,int y)
{
makeroot(x);
access(y); splay(y);
}
inline void link(int x,int y)
{
makeroot(x);
if(findroot(y)!=x) par[x]=y;
}
inline void cut(int x,int y)
{
makeroot(x);
if(findroot(y)==x&&par[y]==x&&!ch[y][0])
{
par[y]=ch[x][1]=0;
pushup(x);
}
}
signed main()
{
// freopen("input.txt","r",stdin);
// freopen("output.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;++i)
val[i]=size[i]=mul[i]=1;
for(int i=1;i<n;++i)
{
int x=read(),y=read();
link(x,y);
}
for(int i=1;i<=m;++i)
{
scanf("%s",op); int x,y,k;
switch(op[0])
{
case '+':
x=read(); y=read(); k=read();
split(x,y); pusha(y,k);
break;
case '-':
x=read(); y=read(); cut(x,y);
x=read(); y=read(); link(x,y);
break;
case '*':
x=read(); y=read(); k=read();
split(x,y); pushm(y,k);
break;
case '/':
x=read(); y=read();
split(x,y);
printf("%d\n",sum[y]);
break;
}
}
return 0;
}
3 数论
3.1 线性筛素数
洛谷 P3383 【模板】线性筛素数
题目描述
如题,给定一个范围
N
N
N,你需要处理
M
M
M个某数字是否为质数的询问(每个数字均在范围
1
−
N
1-N
1−N内)
输入格式
第一行包含两个正整数
N
N
N、
M
M
M,分别表示查询的范围和查询的个数。
接下来 M M M行每行包含一个不小于 1 1 1且不大于 N N N的整数,即询问该数是否为质数。
输出格式
输出包含
M
M
M行,每行为Yes或No,即依次为每一个询问的结果。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=10000005;
const int MAXP=1000005;
int n,m,cnt=0;
int prime[MAXP];
bool not_prime[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void get_prime()
{
not_prime[0]=not_prime[1]=true;
for(int i=2;i<=n;++i)
{
if(!not_prime[i]) prime[++cnt]=i;
for(int j=1;j<=cnt&&prime[j]*i<=n;++j)
{
not_prime[prime[j]*i]=true;
if(i%prime[j]==0) break;
}
}
}
int main()
{
freopen("input.txt","r",stdin);
n=read(); m=read();
get_prime();
for(int i=1;i<=m;++i)
{
if(!not_prime[read()]) printf("Yes\n");
else printf("No\n");
}
return 0;
}
3.2 快速幂||取余运算
洛谷 P1226 【模板】快速幂||取余运算
题目描述
给你三个整数
b
,
p
,
k
b,p,k
b,p,k,求
b
p
m
o
d
k
b^p \bmod k
bpmodk
输入格式
一行三个整数
b
,
p
,
k
b,p,k
b,p,k
输出格式
输出 b^p mod k =s,s 为运算结果
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline ll fpow(ll a,ll k,ll mod)
{
ll res=1;
while(k)
{
if(k&1) res=res*a%mod;
a=a*a%mod; k>>=1;
}
return res%mod;
}
int main()
{
freopen("input.txt","r",stdin);
ll a=read(),k=read(),mod=read();
printf("%lld^%lld mod %lld=%lld\n",a,k,mod,fpow(a,k,mod));
return 0;
}
3.3 矩阵加速
洛谷 P1939 【模板】矩阵加速(数列)
题目描述
a
[
1
]
=
a
[
2
]
=
a
[
3
]
=
1
a[1]=a[2]=a[3]=1
a[1]=a[2]=a[3]=1
a
[
x
]
=
a
[
x
−
3
]
+
a
[
x
−
1
]
(
x
>
3
)
a[x]=a[x-3]+a[x-1] (x>3)
a[x]=a[x−3]+a[x−1](x>3)
求
a
a
a数列的第
n
n
n项对
1000000007
(
1
0
9
+
7
)
1000000007(10^9+7)
1000000007(109+7)取余的值。
输入格式
第一行一个整数
T
T
T,表示询问个数。
以下T行,每行一个正整数 n n n。
输出格式
每行输出一个非负整数表示答案。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5;
const int mod=1e9+7;
struct Matrix
{
ll a[MAXN][MAXN];
Matrix() {memset(a,0,sizeof(a));}
inline void build()//构建单位矩阵
{
for(int i=1;i<=3;++i) a[i][i]=1;
}
};
Matrix operator *(const Matrix &x,const Matrix &y)
{
Matrix res;
for(int k=1;k<=3;++k)
for(int i=1;i<=3;++i)
if(x.a[i][k]==0) continue;
else for(int j=1;j<=3;++j)
res.a[i][j]=(res.a[i][j]+x.a[i][k]*y.a[k][j]%mod)%mod;
return res;
}
Matrix fpow(Matrix a,int k)
{
Matrix res; res.build();
while(k)
{
if(k&1) res=res*a;
a=a*a; k>>=1;
}
return res;
}
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
int main()
{
// freopen("input.txt","r",stdin);
int T=read(); while(T--)
{
int n=read();
if(n<=3) {printf("1\n"); continue;}
Matrix ans,base;
base.a[1][1]=base.a[1][3]=base.a[2][1]=base.a[3][2]=1;
ans.a[1][1]=ans.a[2][1]=ans.a[3][1]=1;
ans=ans*fpow(base,n-1);
printf("%lld\n",ans.a[1][1]);
}
return 0;
}
3.4 乘法逆元
P3811 【模板】乘法逆元
题目描述
给定
n
,
p
n,p
n,p求
1
n
1~n
1 n中所有整数在模
p
p
p意义下的乘法逆元。
输入格式
一行
n
,
p
n,p
n,p,输入保证
p
p
p 为质数
输出格式
n
n
n行,第
i
i
i行表示
i
i
i在模
p
p
p意义下的逆元。
3.4.1 exgcd求乘法逆元
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
if(!b) {d=a; x=1; y=0;}
else {exgcd(b,a%b,d,y,x); y-=x*(a/b);}
}
inline ll inv(ll a,ll n)
{
ll d,x,y;
exgcd(a,n,d,x,y);
return d==1?(x+n)%n:-1;
}
int main()
{
// freopen("input.txt","r",stdin);
int n=read(),mod=read();
for(int i=1;i<=n;++i)
printf("%lld\n",inv(i,mod));
return 0;
}
3.4.2 费马小定理求乘法逆元
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
inline int fpow(ll a,int k,int mod)
{
ll res=1;
while(k)
{
if(k&1) res=res*a%mod;
a=a*a%mod; k>>=1;
}
return res;
}
inline int inv(int a,int mod)
{
return fpow(a,mod-2,mod);
}
int main()
{
freopen("input.txt","r",stdin);
int n=read(),mod=read();
for(int i=1;i<=n;++i)
printf("%d\n",inv(i,mod));
return 0;
}
3.4.3 线性时间复杂度求乘法逆元
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=20000530;
int inv[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
return X*f;
}
int main()
{
freopen("input.txt","r",stdin);
int n=read(),p=read(); inv[1]=1;
for(int i=2;i<=n;++i) inv[i]=(ll)(p-p/i)*inv[p%i]%p;
for(int i=1;i<=n;++i) printf("%d\n",inv[i]);
return 0;
}
4 其他
4.1 01分数规划
MZOJ #16. 01 分数规划
题目描述
给你
n
n
n 个物品,每个物品有两个属性
a
i
a_i
ai和
b
i
b_i
bi,求一组解
x
i
(
1
≤
i
≤
n
,
x
i
=
0
或
1
)
xi(1≤i≤n,x_i=0或1)
xi(1≤i≤n,xi=0或1)使
a
i
a_i
ai和
b
i
b_i
bi,求一组解
x
i
(
1
≤
i
≤
n
,
x
i
=
0
或
1
)
xi(1≤i≤n,xi=0或1)
xi(1≤i≤n,xi=0或1)使
∑
i
=
1
n
a
i
×
x
i
∑
i
=
1
n
b
i
×
x
i
\frac{∑_{i=1}^{n}a_i×x_i}{∑_{i=1}^{n}b_i×x_i}
∑i=1nbi×xi∑i=1nai×xi
最大,且恰好有
k
k
k 个
x
i
x_i
xi 为
1
1
1。
请求出这个最大值。
输入格式
第一行两个数
n
n
n,
k
k
k。
第二行 n n n 个数,依次表示 a 1 , a 2 , . . . a n a_1,a_2,...a_n a1,a2,...an。
第三行 n n n 个数,依次表示 b 1 , b 2 , . . . b n b_1,b_2,...b_n b1,b2,...bn。
输出格式
一行,一个实数,精确到小数点后
4
4
4 位。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
const double eps=1e-7;
int n,k,a[MAXN],b[MAXN];
double l=0.0,r=0.0,ans,d[MAXN];
inline int read()
{
int X=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
return X*f;
}
double check(double mid)
{
double res=0;
for(int i=1;i<=n;++i) d[i]=(double)a[i]-mid*b[i];
sort(d+1,d+n+1);
for(int i=n-k+1;i<=n;++i) res+=d[i];
return res;
}
int main()
{
//freopen("input.txt","r",stdin);
n=read(); k=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read(),r=max(r,(double)a[i]/b[i]);
while(r-l>eps)
{
double mid=(l+r)/2;
if(check(mid)>0) l=mid,ans=mid;
else r=mid;
}
printf("%.4lf",ans);
return 0;
}