muban

1 图论

1.1 lca

洛谷 P3379 【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式
第一行包含三个正整数 N N N M M M S S S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N − 1 N-1 N1行每行包含两个正整数 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<=5000M<=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 uv 的有向边。

输出格式
共一行,最大的点权之和。

#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 1opt6 )

输出格式
对于操作 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 n1行每行两个正整数 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 1N内)

输入格式
第一行包含两个正整数 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[x3]+a[x1](x>3)
a a a数列的第 n n n项对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 1000000007109+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(1in,xi=01)使 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(1in,xi=01)使
∑ 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×xii=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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值