Noip常用模板复习

Noip的时间越来越近了,作为一只蒟蒻,复习的方法当然是写模板咯(雾),骗分过样例,暴力出奇迹,下面请看我一波操作(若有错误请大家指正)。

最小生成树Kruskal

Kruskal适用于处理稀疏图,将边从小到大排序,加边过程用并查集辅助完成
此处以洛谷P3366为例,并查集路径压缩是对Kruskal最基本的优化

#include 
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_N=5007;
const int MAX_M=200007;

int n,m;
int ans,edge;
int fa[MAX_N];
struct node{
	int x,y,w;
}e[MAX_M];


bool cmp(node p,node q)
{
	return p.w<q.w;
}

void init()
{
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
	ans=edge=0;
	mem(e,0);
}

int get(int x)
{
	if(fa[x]==x)
	{
		return x;
	}
	return fa[x]=get(fa[x]);//路径压缩
}

void merge(int rootx,int rooty)
{
	fa[rootx]=rooty;
}

int main()
{
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
	}
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int rootx=get(e[i].x);
		int rooty=get(e[i].y);
		if(rootx!=rooty)
		{
			ans+=e[i].w;
			edge++;
			merge(rootx,rooty);
		}
	}
	if(edge!=n-1)
	{
		printf("orz\n");
	}
	else
	{
		printf("%d\n",ans);
	}
	return 0;
}

最小生成树Prim

Prim算法更适合处理稠密图,基本思想是将已生成的点逐步扩展成为一个集合,根据已有点,刷新计算其余点到整个集合的距离

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_N=5007;
const int MAX_M=200007;
const int Inf=0x3f3f3f3f;

int n,m;
int eid,ans;

struct node{
	int v,w,next;
}e[MAX_M*2];
int dist[MAX_N],p[MAX_N];
bool vst[MAX_N];

void init()
{
	mem(e,0);
	mem(p,-1);
	mem(vst,0);
	mem(dist,0x3f);
	eid=ans=0;
}

void insert(int u,int v,int w)
{
	e[eid].v=v;
	e[eid].w=w;
	e[eid].next=p[u];
	p[u]=eid++;
}

void insert2(int u,int v,int w)
{
	insert(u,v,w);
	insert(v,u,w);
}

int main()
{
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		insert2(u,v,w);
	}
	dist[1]=0;
	for(int i=1;i<=n;i++)
	{
		int min_n=Inf,u=0;
		for(int j=1;j<=n;j++)
		{
			if(!vst[j]&&dist[j]<min_n)
			{
				min_n=dist[j];
				u=j;
			}
		}
		vst[u]=1;
		if(u==0) break;
		ans+=min_n;
		for(int j=p[u];~j;j=e[j].next)
		{
			int v=e[j].v;
			if(!vst[v]&&dist[v]>e[j].w)
			{
				dist[v]=e[j].w;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

最短路径Dijkstra

最短路径同样也是图论中的经典,Dijkstra与Prim的思想较为相近,选择的是加点的方式,但不同点在于Djikstra刷新的是新点到起点的距离。
此处以洛谷3371为例

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int Inf=0x3f3f3f3f;
const int MAX_N=1e4+7;
const int MAX_M=5e5+7;
struct node{
	int v,w,next;
}e[MAX_M*2];

int p[MAX_N];
int dist[MAX_M];
bool vst[MAX_N];

int n,m,s;
int eid;

void init()
{
	mem(e,0);
	mem(p,-1);
	mem(vst,0);
	mem(dist,0x3f);
	eid=0;
}

void insert(int u,int v,int w)
{
	e[eid].v=v;
	e[eid].w=w;
	e[eid].next=p[u];
	p[u]=eid++;
}


int main()
{
	scanf("%d%d%d",&n,&m,&s);
	init();
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		insert(u,v,w);
	}
	dist[s]=0;
	for(int i=1;i<=n;i++)
	{
		int min_n=Inf,u=0;
		for(int j=1;j<=n;j++)
		{
			if(!vst[j]&&dist[j]<min_n)
			{
				min_n=dist[j];
				u=j;
			}
		}
		vst[u]=1;
		for(int j=p[u];~j;j=e[j].next)
		{
			int v=e[j].v;
			if(dist[v]>dist[u]+e[j].w)
			{
				dist[v]=dist[u]+e[j].w;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(dist[i]==0x3f3f3f3f)
		{
			printf("2147483647 ");continue;
		}
		printf("%d ",dist[i]);
	}
	return 0;
}

最短路径SPFA

使用一种类似于宽搜的思路,将队首节点去除并扩展新结点,没有入队的将其入队,有两种优化SLF和LLL,理论上两种方法并用可将效率提高50%。

#include
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int Inf=0x3f3f3f3f;
const int MAX_N=1e4+7;
const int MAX_M=5e5+7;
struct node{
	int v,w,next;
}e[MAX_M*2];
int p[MAX_N];
int dist[MAX_M];
bool inq[MAX_N];

int n,m,s;
int eid;

void init()
{
	mem(e,0);
	mem(p,-1);
	mem(inq,0);
	mem(dist,0x3f);
	eid=0;
}

void insert(int u,int v,int w)
{
	e[eid].v=v;
	e[eid].w=w;
	e[eid].next=p[u];
	p[u]=eid++;
}

int SPFA(int s) 
{
	queue<int> q;
	q.push(s);
	inq[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int j=p[u];~j;j=e[j].next)
		{
			int v=e[j].v;
			if(dist[v]>dist[u]+e[j].w)
			{
				dist[v]=dist[u]+e[j].w;
				if(!inq[v])
				{
					inq[v]=1;
					q.push(v);
				}
			}
		}
	}
	return 0;
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	init();
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		insert(u,v,w);
	}
	dist[s]=0;
	SPFA(s);
	for(int i=1;i<=n;i++)
	{
		if(dist[i]==0x3f3f3f3f)
		{
			printf("2147483647 ");continue;
		}
		printf("%d ",dist[i]);
	}
	return 0;
}

最近公共祖先Lca

给出一棵树和两个结点,求它们的公共祖先,由于数据量较大,所以一般用类似于稀疏表,倍增的思路完成,fa[u][i]代表从结点u开始向上找2^i个的结点的结点。
此处以洛谷3379为例

#include 
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
const int MAX_N=5e5+7;
const int MAX_M=5e5+7;
const int Lg2=25;

int n,m,s;
int eid;

struct node{
	int v,next;
}e[MAX_M*2];
int d[MAX_N];
int p[MAX_N];
int fa[MAX_N*2][Lg2];

void init()
{
	mem(e,0);
	mem(p,-1);
	mem(d,-1);
	mem(fa,0);
	d[s]=1;
	eid=0;
}

void insert(int u,int v)
{
	e[eid].v=v;
	e[eid].next=p[u];
	p[u]=eid++;
}

void insert2(int u,int v)
{
	insert(u,v);
	insert(v,u);
}

void dfs(int u)
{
	for(int i=p[u];~i;i=e[i].next)
	{
		if(d[e[i].v]==-1)
		{
			int v=e[i].v;
			d[v]=d[u]+1;
			fa[v][0]=u;
			dfs(v);
		}
	}
}

int Lca(int u,int v)
{
	int i;
	if(d[u]<d[v])
	{
		swap(u,v);
	}
	for(i=0;(1<<i)<=d[u];i++);
	--i;
	for(int j=i;j>=0;j--)
	{
		if(d[u]-(1<<j)>=d[v])
		{
			u=fa[u][j];
		}
	}
	if(u==v) return u;
	for(int j=i;j>=0;j--)
	{
		if(fa[u][j]!=fa[v][j])
		{
			u=fa[u][j];
			v=fa[v][j];
		}
	}
	return fa[u][0];
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	init();
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		insert2(u,v);
	}
	dfs(s);
	for(int level=1;(1<<level)<=n;level++)
	{
		for(int i=1;i<=n;i++)
		{
			fa[i][level]=fa[fa[i][level-1]][level-1];
		}
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		printf("%d\n",Lca(u,v));
	}
	return 0;
}

拓扑排序

完成一个任务就要首先完成其先决任务,问完成任务编号的序列,思想是建一个有向图,找到入度为0的点,删除,作为第一层,然后继续删除剩下的图。
此处以Poj2367为例

#include
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

int n;
int ind[107];
struct node{
	int v,next;
}e[20007];
int p[107];
bool vst[107];
queue<int> q;
int eid;

void init()
{
	eid=0;
	mem(ind,0);
	mem(e,0);
	mem(p,-1);
	mem(vst,0);
}

void insert(int u,int v)
{
	e[eid].v=v;
	e[eid].next=p[u];
	p[u]=eid++;
}

int main()
{
	scanf("%d",&n);
	init();
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		while(x)
		{
			ind[x]++;
			insert(i,x);
			scanf("%d",&x);
		}
	}
	int ans=n;
	while(ans)
	{
		for(int i=1;i<=n;i++)
		{
			if(!ind[i]&&!vst[i])
			{
				q.push(i);
				vst[i]=1;
				ans--;
			}
		}
		while(!q.empty())
		{
			int u=q.front();
			printf("%d ",u);
			q.pop();
			for(int i=p[u];~i;i=e[i].next)
			{
				int v=e[i].v;
				ind[v]--;
			}
		}
	}
	return 0;
}

树的直径

求一棵树中最长的路径,用两次DFS或BFS,任选一个点搜索出离它最远的那个点,再从这个点开始,在搜索一次,找出的路径便是树的直径,此程序使用了DFS,BFS请读者自行完成
此处以Codevs1814为例

#include
#include 
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_N=1e5+7;
const int MAX_M=1e5+7;
struct node{
	int v,w,next;
}e[MAX_M*2];
int p[MAX_M];
bool vst[MAX_N];
int l[MAX_N],r[MAX_N];
int eid,pnt,start;

int n,ans;

void init()
{
	mem(e,0);
	mem(vst,0);
	mem(p,-1);
	eid=0;
}

void insert(int u,int v,int w)
{
	e[eid].v=v;
	e[eid].w=1;
	e[eid].next=p[u];
	p[u]=eid++;
}

void insert2(int u,int v,int w)
{
	insert(u,v,w);
	insert(v,u,w);
}

void dfs(int u,int cnt)
{
	if(cnt>ans)
	{
		ans=cnt;
		pnt=u;
	}
	for(int i=p[u];~i;i=e[i].next)
	{
		int v=e[i].v;
		if(!vst[v])
		{
			vst[v]=1;
			dfs(v,cnt+1);
			vst[v]=0;
		}
	}
}

int main()
{
	scanf("%d",&n);
	init();
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&l[i],&r[i]);
		if(l[i]&&r[i])
		{
			start=i;
		}
		if(l[i])
		{
			insert2(i,l[i],1);
		}
		if(r[i])
		{
			insert2(i,r[i],1);
		}
	}
	ans=0;
	dfs(start,0);
	ans=0;
	dfs(pnt,0);
	printf("%d\n",ans);
	return 0;
	
}

堆排序

一般的常用堆是小根堆和大根堆,也有比较强的斐波那契堆等,对于一个小根堆来说,一个结点的左右子树比自身都要大
此处以洛谷1177为例

#include
#include
#include
using namespace std;

const int MAX_N=1e5+7;
int n;
int len;
int heap[MAX_N];

void put(int x)
{
	int son;
	len++;
	heap[len]=x;
	son=len;
	while(son!=1&&heap[son/2]>heap[son])
	{
		swap(heap[son],heap[son/2]);
		son/=2;
	}
}

int get()
{
	int fa,son,tmp;
	tmp=heap[1];
	heap[1]=heap[len];
	len--;
	fa=1;
	while((fa*2<=len)||(fa*2+1<=len))
	{
		if(fa*2+1>len||heap[fa*2]<heap[fa*2+1])
		{
			son=fa*2;
		}
		else
		{
			son=fa*2+1;
		}
		if(heap[fa]>heap[son])
		{
			swap(heap[fa],heap[son]);
			fa=son;
		}
		else break;
	}
	return tmp;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		put(x);
	}
	for(int i=1;i<=n;i++)
	{
		printf("%d ",get());
	}
	printf("\n");
	return 0;
}

并查集

判断连通块的一种经典算法,分为并和查两个部分,主要思路是找到两个待查询的结点,搜索其根节点。路径压缩是常用的优化。
此处以洛谷3367为例

#include
#include
using namespace std;

const int MAX_N=1e4+7;

int n,m;
int fa[MAX_N];

int find(int x)
{
	if(fa[x]==x)
	{
		return x;
	}
	return fa[x]=find(fa[x]);
}

void merge(int rootx,int rooty)
{
	fa[rootx]=rooty;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int flag,x,y;
		scanf("%d%d%d",&flag,&x,&y);
		if(flag==1)
		{
			int rootx=find(x);
			int rooty=find(y);
			merge(rootx,rooty);
		}
		else
		{
			if(find(x)==find(y))
			{
				printf("Y\n");
			}
			else
			{
				printf("N\n");
			}
		}
	}
	return 0;
}

带权并查集

由不同并查集衍生成的一类数据结构,关键是在并与查的过程中求出每个点与根节点的关系,从而达到解决问题的效果,常与路径压缩连用。
此处以洛谷1196为例

#include
#include
using namespace std;

const int MAX_N=3e4+7;

int T;
int fa[MAX_N];
int dist[MAX_N];
int size[MAX_N];

int find(int x)
{
	if(fa[x]==x)
	{
		return x;
	}
	int y=fa[x];
	fa[x]=find(fa[x]);
	dist[x]+=dist[y];
	return fa[x];
}

void merge(int x,int y)
{
	int rootx=find(x);
	int rooty=find(y);
	fa[rootx]=rooty;
	dist[rootx]=size[rooty];
	size[rooty]+=size[rootx];
}

int main()
{
	cin>>T;
	for(int i=1;i<=MAX_N;i++)
	{
		fa[i]=i;
		dist[i]=0;
		size[i]=1;
	}
	while(T--)
	{
		char flag;
		int x,y;
		cin>>flag>>x>>y;
		if(flag=='C')
		{
			int rootx=find(x);
			int rooty=find(y);
			if(rootx!=rooty) cout<<-1<<endl;
			else
			{
				cout<<abs(dist[x]-dist[y])-1<<endl;
			}
		}
		else
		{
			merge(x,y);
		}
	}
	return 0;
}

单调队列

是一种速度很快的数据结构,可以求连续区域内的最值,并保证其中元素的单调性,入队十分简单,出队有两种情况,一是队首已经被移出了所需区间,将队首删除,二是当前入队元素比队尾元素更优,将队尾删除。
此处以洛谷1886为例

#include
#include
#include
using namespace std;

const int MAX_N=1e6+7;
int n,k;
int head,tail;
int a[MAX_N];
int q[MAX_N],num[MAX_N],ans_max[MAX_N],ans_min[MAX_N];

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	head=1;tail=0;
	for(int i=1;i<=n;i++)
	{
		while(num[head]<i-k+1&&head<=tail) head++;
		while(a[i]>q[tail]&&head<=tail) tail--;
		q[++tail]=a[i];
		num[tail]=i;
		ans_max[i]=q[head];
	}
	memset(q,0,sizeof q);
	head=1;tail=0;
	for(int i=1;i<=n;i++)
	{
		while(num[head]<i-k+1&&head<=tail) head++;
		while(a[i]<q[tail]&&head<=tail) tail--;
		q[++tail]=a[i];
		num[tail]=i;
		ans_min[i]=q[head];
	}
	for(int i=k;i<=n;i++)
	{
		printf("%d ",ans_min[i]);
	}
	printf("\n");
	for(int i=k;i<=n;i++)
	{
		printf("%d ",ans_max[i]);
	}
	return 0;
}

优先队列

和单调队列的功能比较类似,调用了C++了的STL,每次自动调整队首元素为当前最值,有入队和出队两种操作,还能调用当前队首,成员函数分别是push、pop、top。注意:考试时可能会在时间上卡STL(如上一题)。

#include
#include
#include
#include
#include
using namespace std;

const int MAX_N=1e6+7;

int n,k;
int ans_Max[MAX_N],ans_Min[MAX_N];
int a[MAX_N];

struct cmp_Max
{
	bool operator() (int x,int y)
	{
		return a[x]<a[y];
	}
};

struct cmp_Min
{
	bool operator() (int x,int y)
	{
		return a[x]>a[y];
	}
};

priority_queue <int,vector<int>,cmp_Max> q_Max;
priority_queue <int,vector<int>,cmp_Min> q_Min;

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(i<=k)
		{
			q_Max.push(i);
			q_Min.push(i);			
		}
	}
	int p=0;
	ans_Max[++p]=a[q_Max.top()];
	ans_Min[p]=a[q_Min.top()];
	for(int i=k+1;i<=n;i++) 
	{
		q_Max.push(i);
		q_Min.push(i);
		while(i-q_Max.top()>=k) q_Max.pop();
		while(i-q_Min.top()>=k) q_Min.pop();
		ans_Max[++p]=a[q_Max.top()];
		ans_Min[p]=a[q_Min.top()];
	}
	for(int i=1;i<=n-k+1;i++)
	{
		printf("%d ",ans_Min[i]);
	}
	printf("\n");
	for(int i=1;i<=n-k+1;i++)
	{
		printf("%d ",ans_Max[i]);
	}
	return 0;
}

ST表

也被称作稀疏表,以倍增作为主要思路,是解决静态RMQ类问题的绝佳利器,
此处以洛谷3865为例

#include
#include
#include
using namespace std;

const int MAX_N=1e5+7;
const int Lg2=20;

int n,m;
int st[2*MAX_N][Lg2];

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&st[i][0]);
	}
	for(int level=1;(1<<level)<=n;level++)
	{
		for(int i=1;i<=n;i++)
		{
			st[i][level]=max(st[i][level-1],st[i+(1<<(level-1))][level-1]);
		}
	}
	for(int i=1;i<=m;i++)
	{
		int l,r; 
		scanf("%d%d",&l,&r);
		int len=floor(log(r-l+1)/log(2));
		printf("%d\n",max(st[l][len],st[r-(1<<len)+1][len]));
	}
	return 0;
}

树状数组

解决动态RMQ问题的经典在线算法,其核心是利用位运算巧妙解决数组间的关系
此处以洛谷3374为例

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_N=5e5+7;
int c[MAX_N];
int n,m;

int lowbit(int x)
{
	return x&(-x);
}

void modify(int x,int k)
{
	for(;x<=n;x+=lowbit(x))
	{
		c[x]+=k;
	}
}

int getsum(int x)
{
	int sum=0;
	for(;x;x-=lowbit(x))
	{
		sum+=c[x];
	}
	return sum;
}

int main()
{
	scanf("%d%d",&n,&m);
	mem(c,0);
	for(int i=1;i<=n;i++)
	{
		int k;
		scanf("%d",&k);
		modify(i,k);
	}
	for(int i=1;i<=m;i++)
	{
		int flag,x,y;
		scanf("%d%d%d",&flag,&x,&y);
		if(flag==1)
		{
			modify(x,y);
		}
		else
		{
			printf("%d\n",getsum(y)-getsum(x-1));
		}
	}
	return 0;
}

线段树单点修改+区间求和

和树状数组一样能解决动态RMQ,建树,树中记录线段的左端点右端点以及区间和等信息,这是线段树最基本的一种形式。

#include
#include
using namespace std;

const int MAX_N=5e5+7;
int s[MAX_N*4];
int n,q;
 
void up(int p)
{
	s[p]=s[2*p]+s[2*p+1];
}

void modify(int p,int l,int r,int x,int v)
{
	if(l==r)
	{
		s[p]+=v;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid)
	{
		modify(p*2,l,mid,x,v);
	}
	else
	{
		modify(p*2+1,mid+1,r,x,v);
	}
	up(p);
}

int query(int p,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return s[p];
	int mid=(l+r)/2,res=0;
	if(x<=mid) res+=query(p*2,l,mid,x,y);
	if(y>mid) res+=query(p*2+1,mid+1,r,x,y);
	return res;
}

int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		int d;
		scanf("%d",&d);
		modify(1,1,n,i,d);
	}
	for(int i=1;i<=q;i++)
	{
		int d,x,y;
		scanf("%d%d%d",&d,&x,&y);
		if(d==1)
		{
			modify(1,1,n,x,y);
		}
		else
		{
			printf("%d\n",query(1,1,n,x,y));
		}
	}
	return 0;
}

线段树懒惰标记

主要应用于区间修改和区间查询,功能较为强大,如果当前区间被所需区间包含,那么修改这部分的值,将这个地方置上懒惰标记,然后暂停往下更新,当要用到这部分以下的端点,再把该结点的懒惰标记下传(否则浪费时间),并消除这个点的懒惰标记
此处以Hdu1698为例

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_N=1e5+7;

int s[MAX_N*4],col[MAX_N*4];
int T,n,m,num=0;

void init()
{
	mem(s,0);
	mem(col,0);
}

void up(int p)
{
	s[p]=s[p*2]+s[p*2+1];
}

void down(int p,int l,int r)
{
	if(col[p])
	{
		int mid=(l+r)/2;
		s[p*2]=col[p]*(mid-l+1);
		s[p*2+1]=col[p]*(r-mid);
		col[p*2]=col[p];
		col[p*2+1]=col[p];
		col[p]=0;
	}
}

void modify(int p,int l,int r,int x,int y,int c)
{
	if(x<=l&&r<=y)
	{
		s[p]=(r-l+1)*c;
		col[p]=c;
		return;
	}
	down(p,l,r);
	int mid=(l+r)/2;
	if(x<=mid)
	{
		modify(p*2,l,mid,x,y,c);
	}
	if(y>mid) 
	{
		modify(p*2+1,mid+1,r,x,y,c);
	}
	up(p);
}

int query(int p,int l,int r,int x,int y)
{
	int res=0;
	if(x<=l&&r<=y)
	{
		return s[p];
	}
	int mid=(l+r)/2;
	if(x<=mid)
	{
		res+=query(p*2,l,mid,x,y);
	}
	if(y>mid)
	{
		res+=query(p*2+1,mid+1,r,x,y);
	}
	up(p);
	return res;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		num++;
		scanf("%d",&n);
		init();
		modify(1,1,n,1,n,1);
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			modify(1,1,n,x,y,z);
		}
		printf("Case %d: The total value of the hook is %d.\n",num,query(1,1,n,1,n));
	}
	return 0;
}

莫队

很实用的一种离线算法,在一定条件下处理区间问有由很大用处,莫队算法将查询数据离线,以分块排序和指针移动为主要思路,减少时间上的损耗
此处以洛谷1972为例
注:该题加大了数据量,莫队无法通过全部测试数据。

#include
#include
#include
#include 
using namespace std;

const int MAX_N=5e5+7;
const int MAX_M=5e5+7;
const int MAX_V=1e6+7;

struct node{
	int l,r,num;
}q[MAX_M];

int n,m,ans;
int a[MAX_N];
int res[MAX_M],cnt[MAX_V];
int part;

bool cmp(node x,node y)
{
	if(x.l/part==y.l/part)
	{
		return x.r<y.r;
	}
	return x.l<y.l;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].num=i;
	}
	part=floor(sqrt(n*1.0+0.5));
	sort(q+1,q+m+1,cmp);
	int l,r;
	l=r=1;
	cnt[a[1]]=1;
	ans=1;
	for(int i=1;i<=m;i++)
	{
		while(l<q[i].l)
		{
			cnt[a[l]]--;
			if(!cnt[a[l]]) ans--;
			l++;
		}
		while(l>q[i].l)
		{
			l--;
			cnt[a[l]]++;
			if(cnt[a[l]]==1) ans++;
		}
		while(r>q[i].r)
		{
			cnt[a[r]]--;
			if(!cnt[a[r]]) ans--;
			r--;
		}
		while(r<q[i].r)
		{
			r++;
			cnt[a[r]]++;
			if(cnt[a[r]]==1) ans++;
		}
		res[q[i].num]=ans;
	}
	for(int i=1;i<=m;i++)
	{
		printf("%d\n",res[i]);
	}
	return 0;
}

巴什博弈

较为简单的一类博弈,平衡态构造较为容易,根据石子的总数来确定先手或后手赢。
此处以Loj10241为例

#include
#include
using namespace std;

int n,k;
int main()
{
	scanf("%d%d",&n,&k);
	if(n%(k+1)==0)
	{
		printf("2\n");
	}
	else
	{
		printf("1\n");
	}
	return 0;
}

尼姆博弈

经典的博弈问题,主要思路是将数字转换为二进制计算,可以使用亦或运算符计算,当先手可以将状态转为平衡态时,后手必败。
此处以洛谷2197为例

#include 
#include
using namespace std;

int T;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d",&n);
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			int x;
			scanf("%d",&x);
			ans^=x;
		}
		if(!ans)
		{
			printf("No\n");
		}
		else
		{
			printf("Yes\n");
		}
	}
	return 0;
}

斐波那契博弈

经典的博弈问题,当石子数为斐波那契数时,先手必败。
此处以Nyoj358为例

#include 
#include
#define LL long long

const int N=100;

LL n;
LL f[N];

int main()
{
	f[1]=1;f[2]=2;
	for(int i=3;i<=N;i++)
	{
		f[i]=f[i-1]+f[i-2];
	}
	while(~scanf("%lld",&n))
	{
		bool judge=1;
		for(int i=1;i<=N;i++)
		{
			if(f[i]==n)
			{
				judge=0;
				break;
			}
		}
		if(!judge)
		{
			printf("No\n");
		}
		else
		{
			printf("Yes\n");
		}
	}
	return 0;
}

威佐夫博弈

经典的博弈问题,重点是奇异态的寻找,在探究过程中可以找规律,最后能够获得奇异态的推导公式。
此处以Poj1067为例

#include 
#include
#include
using namespace std;

int x,y;

int main()
{
	while(~scanf("%d%d",&x,&y))
	{
		if(x>y)
		{
			swap(x,y);
		}
		int k=y-x;
		if(floor(k*(sqrt(5)+1)/2)==x)
		{
			printf("0\n");
		}
		else
		{
			printf("1\n");
		}
	}
	return 0;
}

快速幂

快速幂+取模运算是用来求解大数据乘方的算法,利用a2b=(ab)2巧妙得出结果,取模运算防止不爆大数据。
此处以洛谷1226为例

#include
#include
#define LL long long
using namespace std;

LL b,p,k;

LL pow(LL b,LL p)
{
	if(p==0) return 1;
	if(p%2)
	{
		LL x=pow(b,p/2)%k;
		return (((x*x)%k)*b)%k;
	}
	else
	{
		LL x=pow(b,p/2)%k;
		return (x*x)%k;
	}
}

int main()
{
	scanf("%lld%lld%lld",&b,&p,&k);
	printf("%lld^%lld mod %lld=%lld",b,p,k,pow(b,p)%k);
	return 0;
}

深度优先搜索

简称DFS,以搜索深度作为第一条件,被誉为骗分神器的搜索,在一定情况下可以展现自己优异的偏分性能,在加上合适的优化后效果更佳,从总体上说,搜索也有有利的一面。
此处以对一个有向图进行深度优先搜索为例

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_M=1e5+7;
const int MAX_N=1e5+7;

struct node{
	int v,w,next;
}e[MAX_M];
int p[MAX_N];
bool vst[MAX_N];

int n,m,s,eid;

void init()
{
	mem(e,0);
	mem(vst,0);
	mem(p,-1);
	eid=0;
}

void insert(int u,int v,int w)
{
	e[eid].v=v;
	e[eid].w=w;
	e[eid].next=p[u];
	p[u]=eid++;
}

void dfs(int u)
{
	printf("%d ",u);
	for(int i=p[u];~i;i=e[i].next)
	{
		int v=e[i].v;
		if(!vst[v])
		{
			vst[v]=1;
			dfs(v);
		}
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	init();
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		insert(u,v,w) ;
	}
	vst[s]=0;
	dfs(s);
	return 0;
}

宽度优先搜索

简称BFS,与深搜正好相对,从起点开始向各个方向扩展,以宽度为扩展任务,使用队列辅助完成,理解BFS的基本原理,因为单独考察的题目比较少,所以它的几种变形显得尤为重要。
此处以宽度优先搜索遍历图为例。

#include
#include
#include
using namespace std;

const int MAX_N=100;
const int MAX_M=10000;

struct edge{
	int v,w,next;
}e[MAX_M];
int p[MAX_N],eid;
int n,m,s;

void init()
{
	memset(p,-1,sizeof(p));
	eid=0;
}

void insert(int u,int v,int w)
{
	e[eid].v=v;
	e[eid].w=w;
	e[eid].next=p[u];
	p[u]=eid++;
}

bool vst[MAX_N];

void bfs(int v)
{
	memset(vst,false,sizeof(vst));
	queue<int> q;
	q.push(v);
	vst[v]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		printf("%d ",u);
		for(int i=p[u];~i;i=e[i].next)
		{
			if(!vst[e[i].v])
			{
				vst[e[i].v]=1;
				q.push(e[i].v);
			}
		}
	}
}

int main()
{
	init();
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		insert(u,v,w);
	}
    bfs(s);
    return 0;
}

双端队列宽度优先搜索

很明显宽搜无法处理权重不同的图,但或许边权只有0或1的图是个特例,善用宽度优先搜索的两个性质,两段性和单调性,可将扩展中边权为0的点插入队首,边权为1的点插入队尾,从而不破坏Bfs的两个性质。
此处以洛谷2243为例

#include
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int cap=5e5+10;
const int L=507;

int dist[L][L];
char map[L][L];
int r,c;
int li,ri;
pair<int,int> queue[cap*2];

bool valid(int x,int y)
{
	return (x>=0&&x<=r&&y>=0&&y<=c);
}

void que_add(int x,int y,int v)
{
	if(dist[x][y]<0||v<dist[x][y])
	{
		dist[x][y]=v;
		if(li==ri||v>dist[queue[li].first][queue[li].second])
		{
			queue[ri++]=make_pair(x,y);
		}
		else
		queue[--li]=make_pair(x,y);
	}
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&r,&c);
		for(int i=0;i<r;i++)
		{
			scanf("%s",map[i]);
		}
		if((r+c)%2)
		{
			printf("NO SOLUTION\n");
			continue;
		}
		li=ri=cap;
		mem(queue,0);
		mem(dist,-1);
		dist[0][0]=0;
		queue[ri++]=make_pair(0,0);
		while(li!=ri)
		{
			int cx=queue[li].first,cy=queue[li].second;
			++li;
			if(valid(cx-1,cy-1))
			{
				if(map[cx-1][cy-1]=='\\') que_add(cx-1,cy-1,dist[cx][cy]);
				else que_add(cx-1,cy-1,dist[cx][cy]+1);
			}
			if(valid(cx-1,cy+1))
			{
				if(map[cx-1][cy]=='/') que_add(cx-1,cy+1,dist[cx][cy]);
				else que_add(cx-1,cy+1,dist[cx][cy]+1);
			}
			if(valid(cx+1,cy-1))
			{
				if(map[cx][cy-1]=='/') que_add(cx+1,cy-1,dist[cx][cy]);
				else que_add(cx+1,cy-1,dist[cx][cy]+1);
			}
			if(valid(cx+1,cy+1))
			{
				if(map[cx][cy]=='\\') que_add(cx+1,cy+1,dist[cx][cy]);
				else que_add(cx+1,cy+1,dist[cx][cy]+1);
			}
		}
		printf("%d\n",dist[r][c]);
	}
	return 0;
}

双向宽度优先搜索

可以用于优化宽搜,从起点和终点两个方向交替搜索,可以节省很多时间,q[0][].x/.y代表从起点开始搜的路径上坐标,q[1][].x/.y代表从终点开始搜坐标,l[0],[1],r[0],[1]分别代表起点终点搜索的头尾指针,直到正向搜索和反向搜索有重合时,结束,在搜索中,已扩展节点数少的一边优先扩展。
此处以Poj1915为例

#include
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int MAX_W=1e5+7;
const int MAX_L=307;

struct node{
	int x,y;
}q[2][MAX_W];
int l[2],r[2];
int dist[2][MAX_L][MAX_L];
bool v[2][MAX_L][MAX_L];
int dx[8]={1,1,-1,-1,2,2,-2,-2};
int dy[8]={2,-2,2,-2,1,-1,1,-1};
int n,L,ans;

bool expand(int k)
{
	int x,y,d;
	x=q[k][l[k]].x;
	y=q[k][l[k]].y;
	d=dist[k][x][y];
	for(int i=0;i<8;i++)
	{
		int tx,ty;
		tx=x+dx[i];ty=y+dy[i];
		if(tx>=0&&tx<L&&ty>=0&&ty<L&&!v[k][tx][ty])
		{
			v[k][tx][ty]=1;
			r[k]++;
			q[k][r[k]].x=tx;q[k][r[k]].y=ty;
			dist[k][tx][ty]=d+1;
			if(v[1-k][tx][ty])
			{
				ans=dist[k][tx][ty]+dist[1-k][tx][ty];
				return 1;
			}
		}
	}
	return 0;
}

void Bfs()
{
	v[0][q[0][1].x][q[0][1].y]=1;
	v[1][q[1][1].x][q[1][1].y]=1;
	l[0]=r[0]=1;l[1]=r[1]=1;
	while(l[0]<=r[0]&&l[1]<=r[1])
	{
		if(r[0]-l[0]<r[1]-l[1])
		{
			if(expand(0)) return;
			l[0]++;
		}
		else
		{
			if(expand(1)) return;
			l[1]++;
		}
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&L);
		ans=0;
		int sx,sy,ex,ey;
		mem(q,0);mem(v,0);mem(dist,0);mem(l,0);mem(r,0);
		scanf("%d%d%d%d",&sx,&sy,&ex,&ey);
		q[0][1].x=sx;q[0][1].y=sy;
		q[1][1].x=ex;q[1][1].y=ey;
		if(q[0][1].x==q[1][1].x&&q[0][1].y==q[1][1].y)
		{
			printf("0\n");
			continue;
		}
		Bfs();
		printf("%d\n",ans);
	}
	return 0;
}

二分

在一个有序序列中查找给定的值,速度上优于顺序查找
此处以在一个递增序列中,查找给定值为例

#include
#include
using namespace std;

const int MAX_N=1e5+7;

int n,k;
int a[MAX_N];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int l=1,r=n;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid]==k)
		{
			printf("%d\n",mid);
			return 0;
		}
		if(a[mid]>k) r=mid-1;
		else l=mid+1;
	}
	return 0;
}

三分

对象主要是单峰区间,将所需区间划分为三段,可以证明最优点和好点在坏点的同侧,依据此方法缩小区间,注意三分判断时的精确度应该是保留小数位数的下一位
此处以洛谷3382为例

#include
#include
#include
#include
using namespace std;
int n;
double l,r;
double a[15];

double cal(double x)
{
	double sum=0;
	double pow=1;
	for(int i=n+1;i>=1;i--)
	{
		sum+=pow*a[i];
		pow*=x;
	}
	return sum; 
}

int main()
{
	scanf("%d%lf%lf",&n,&l,&r);
	for(int i=1;i<=n+1;i++)
	{
		scanf("%lf",&a[i]);
	}
	while(r-l>1e-6)
	{
		double lmid,rmid;
		lmid=l+(r-l)/3;
		rmid=r-(r-l)/3;
		if(cal(lmid)>cal(rmid)) r=rmid;
		else l=lmid;
	}
	printf("%.5lf\n",l);
	return 0;
}

哈希优化

是一种优化,如处理二维数据时,原来冗余的部分在于判重,而将各种类型的数据转换为数字,所以只要构造一个哈希函数和标记数组就可以了,常见方法有二进制状态压缩,直接取余以及平方取中。
此处以洛谷4289为例,典型宽搜+二进制压缩优化

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

const int n=4;
const int MAX_L=1e6+7;
const int MAX_N=1e5+7;
const int dx[4]={0,1,0,-1};
const int dy[4]={-1,0,1,0};

struct form{
	int a[n][n];
}q[MAX_L],start,goal;

int num[n*n+3];
int cnt[MAX_L];
bool flag[MAX_N];
char map[n+3][n+3];
int s,g;

void init()
{
	mem(num,0);
	mem(q,0);
	mem(flag,0);
	mem(cnt,0);
}

bool valid(int x,int y)
{
	return(x>=0&&x<n&&y>=0&&y<n);
}

int turn(form x)
{
	int m=0,hash=0,pow=1;
	for(int i=n-1;i>=0;i--)
	{
		for(int j=n-1;j>=0;j--)
		{
			num[++m]=x.a[i][j];
		}
	}
	for(int i=1;i<=m;i++)
	{
		hash+=pow*num[i];
		pow*=2;
	}
	return hash;
}

void Bfs()
{
	int head,tail;
	q[1]=start;
	head=tail=1;
	while(head<=tail)
	{
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				for(int k=0;k<4;k++)
				{
					int ti=i+dx[k];
					int tj=j+dy[k];
					if(valid(ti,tj)&&q[head].a[i][j])
					{
						form tmp=q[head];
						int temp_val,t;
						temp_val=tmp.a[i][j];
						tmp.a[i][j]=tmp.a[ti][tj];
						tmp.a[ti][tj]=temp_val;
						t=turn(tmp);
						if(!flag[t])
						{
							//printf("%d\n",t);
							flag[t]=1;
							q[++tail]=tmp;
							cnt[tail]=cnt[head]+1;
							if(t==g)
							{
								printf("%d\n",cnt[tail]);
								return;
							}
						}
					}
				}
			}
		}
		head++;
	}
}

int main()
{
	for(int i=0;i<n;i++)
	{
		scanf("%s",map[i]);
		for(int j=0;j<n;j++)
		{
			start.a[i][j]=map[i][j]-48;
		}		
	}
	for(int i=0;i<n;i++)
	{
		scanf("%s",map[i]);
		for(int j=0;j<n;j++)
		{
			goal.a[i][j]=map[i][j]-48;
		}
	}
	s=turn(start);
	g=turn(goal);
	if(s==g) 
	{
		printf("0\n");
		return 0;
	}
	Bfs();
	return 0;
}

01背包

背包,动态规划的基础问题,其中以01背包最为容易,重点掌握二维到一维数组的空间压缩过程,以及转移方程的思想。
此处以洛谷1048为例

#include
#include
using namespace std;

const int MAX_T=1007;
const int MAX_M=107;
int t,m;
int w[MAX_M],c[MAX_M];
int f[MAX_T];

int main()
{
    scanf("%d%d",&t,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&w[i],&c[i]);
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=t;j>=w[i];j--)
        {
            f[j]=max(f[j],f[j-w[i]]+c[i]);
        }
    }
    printf("%d\n",f[t]);
    return 0;
}

完全背包

完全背包相对于01背包来说唯一的区别在于每种物品的个数没有限制,而且程序只需在01背包的基础上稍微改动一下。
此处以求n个物品完全背包的最大价值为例

#include
#include
using namespace std;

const int MAX_T=1007;
const int MAX_M=107;
int t,m;
int w[MAX_M],c[MAX_M];
int f[MAX_T];

int main()
{
    scanf("%d%d",&t,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&w[i],&c[i]);
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=w[i];j<=t;j++)
        {
            f[j]=max(f[j],f[j-w[i]]+c[i]);
        }
    }
    printf("%d\n",f[t]);
    return 0;
}

多重背包优化

多重背包有朴素做法,即将其拆分为01背包,但由于拆分之后数据过大,时间空间都有风险,优化做法是将一个种类的个数拆分成二的幂次方,然后再用01背包计算,因为二的幂次方可以表示任何正整数
此处以将多重背包分为价值相等的两组为例

#include
#include
#include
#include 
#define mem(a,b)  memset(a,b,sizeof a)
using namespace std;
 
const int MAX_N=6*2e5+7;
const int MAX_T=6*2e5+7;
int p[10];
int a[MAX_N];
bool f[MAX_T];
int n=0,s=0;

void init()
{
	mem(a,0);
	mem(p,0);
	mem(f,0);
}

void pack()
{
	f[0]=true;
	for(int i=1;i<=n;i++)
	{
		for(int j=s;j>=0;j--)
		{
			if(f[j]&&j+a[i]<=s)
			{
				f[j+a[i]]=true;
			}
		}
	}
}

int main()
{
	init();
	for(int i=1;i<=6;i++)
	{
		scanf("%d",&p[i]);
		s+=p[i]*i;
		if(p[i]==0) continue;
		int m=floor(1.0*log(p[i])/log(2));
		int cnt=1; 
		for(int j=1;j<=m;j++)
		{
			n++;
			a[n]=i*cnt;
			cnt*=2;
		}
		n++;
		a[n]=(p[i]-cnt/2)*i;
	}
	pack();
	if(s%2==0&&f[s/2])
	{
		printf("Can be divided.\n");
	}
	else
	{
		printf("Can't be divided.\n");
	}
	return 0;
}

一维差分

基于前缀和的一种思路,主要能够解决区间修改加单点查询的问题,可以和树状数组,线段树等联合使用
此处给出一维差分的核心程序段

for(int i=1;i<=m;i++)
{
	scanf("%d%d%d",&x,&y,&z);
	s[y+1]-=z;
	s[x]+=z;
}

二维差分

思路类比一维差分,使用一个差分数组记录内容
此处给出二维差分核心程序段(以d数组作为差分数组,(a,b)(c,d)分别为起终点坐标)

for(int i=1;i<=m;i++) 
{
	scanf("%d%d%d%d",&a,&b,&c,&d);
	change(a,b,1);
	change(a,d+1,-1);
	change(c+1,b,-1);
	change(c+1,d+1,-1);
}

树上点差分

没什么好说的,注意特殊处理,要处理到Lca的父节点
此处给出树上点差分的核心程序段

for(int i=1;i<=m;i++)
{
	scanf("%d%d%d",&x,&y,&d);
	w[x]+=d;
	w[y]+=d;
	w[lca(x,y)]-=d;
	w[fa[lca(x,y)][0]]-=d;
}

树上边差分

边差分与点差分稍微有些不一样,更加方便,实质上也是点储存边信息
此处给出树上边差分的核心程序段

for(int i=1;i<=m;i++)
{
	scanf("%d%d%d",&x,&y,&d);
	w[x]+=d;
	w[y]-=d;
	w[lca(x,y)]-=2*d;
}

裴蜀定理

n个整数之间的裴蜀定理
设a1,a2,a3…an为n个整数,d是它们的最大公约数,那么存在整数x1…xn使得x1a1+x2a2+…xn*an=d
此处以洛谷4549为例

#include
using namespace std;

int n,x,ans;

int gcd(int x,int y)
{
	int r=x%y;
	while(r)
	{
		x=y,y=r,r=x%y;
	}
	return y;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		if(!x) continue;
		if(x<0) x=-x;
		if(i==1)
		{
			ans=x;
		}
		else
		{
			ans=gcd(ans,x);
		}
	}
	printf("%d\n",ans);
	return 0;
}

你可能感兴趣的:(Noip常用模板复习)