2019.08.10【省选组】模拟

做了三天,终于改完了。

这场比赛三道难题,其中两道是码农题。

T1:树上倍增即可。

我们首先把x和y的中点找出来,然后我们就分清楚了哪些点要到那个终点去。

接着我们要求答案。首先我们要预处理出几个数组:

dis1[i]、dis2[i]、dis3[i]:从i的后代走到i的最长距离、次长距离和第三长距离

up[i][j]:以从i开始往上走2^j步的点为根的子树走到i的最长距离。(注意这里不包含i的子树,否则会出错)

down[i][j]:以从i开始往上走2^j步的点为根的子树走到i的2^j级祖先的最长距离。(这里同样不包含i的子树)

在计算答案时,我们规定deep[x]>=deep[y],然后按如下方式计算:

1、x~mid用up 

2、mid~g用down 

3、y~g用up 

4、g~1用up 

5、x、y、g单独处理(用dis1、dis2和dis3)

这个画一下图就明白了。

最后贴一下全部代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define MAXN 200010
#define MAXM 200010
using namespace std;

struct map
{
	int x;
	int y;
};
map way[MAXM];
int first[MAXN],nxt[MAXM],fa[MAXN][20],up[MAXN][20],down[MAXN][20],dis1[MAXN],dis2[MAXN],n,m,T,x,y;
int ans,bz[MAXN],num1[MAXN],num2[MAXN],deep[MAXN],g,mid,be[MAXN],fi[MAXN],tim,dis3[MAXN],num3[MAXN];
int inf,c,d[MAXN][2];
int update(int y,int x)
{
	if(dis1[y]+1>dis1[x])
	{
		dis3[x]=dis2[x];num3[x]=num2[x];
		dis2[x]=dis1[x];num2[x]=num1[x];
		dis1[x]=dis1[y]+1;num1[x]=y;
	}
	else
	if(dis1[y]+1>dis2[x])
	{
		dis3[x]=dis2[x];num3[x]=num2[x];
		dis2[x]=dis1[y]+1;num2[x]=y;
	}
	else
	if(dis1[y]+1>dis3[x])
	{
		dis3[x]=dis1[y]+1;num3[x]=y;
	}
}
int bfs1()
{
	int i,j,t=1,tf;
	d[1][0]=1;d[1][1]=first[d[1][0]];
	bz[d[1][0]]=1;
	tim++;be[d[1][0]]=tim;
	while(t>=1)
	{
		tf=0;
		for(;d[t][1]>=1&&d[t][1]<=m;d[t][1]=nxt[d[t][1]])
		{
			i=d[t][1];
			if(bz[way[i].y]==0)
			{
				t++;d[t][0]=way[i].y;d[t][1]=first[d[t][0]];
				bz[way[i].y]=1;
				tim++;be[way[i].y]=tim;
				fa[way[i].y][0]=d[t-1][0];deep[way[i].y]=deep[d[t-1][0]]+1;
				tf=1;
				break;
			}
		}
		if(tf==0)
		{
			fi[d[t][0]]=tim;
			update(d[t][0],fa[d[t][0]][0]);
			t--;
		}
	}
}
int bfs2()
{
	int i,j,t=1,tf,z;
	d[1][0]=1;d[1][1]=first[d[1][0]];
	memset(bz,0,sizeof(bz));
	bz[1]=1;
	while(t>=1)
	{
		tf=0;
		for(;d[t][1]>=1&&d[t][1]<=m;d[t][1]=nxt[d[t][1]])
		{
			i=d[t][1];z=d[t][0];
			if(bz[way[i].y]==0)
			{
				t++;d[t][0]=way[i].y;d[t][1]=first[d[t][0]];
				bz[way[i].y]=1;
				down[way[i].y][0]=(num1[z]==way[i].y?(num2[z]==0?-inf:dis2[z]):dis1[z]);
				up[way[i].y][0]=down[way[i].y][0]+1;
				tf=1;
				break;
			}
		}
		if(tf==0)t--;
	}
}
int LCA(int x,int y)
{
	int z=x,j;
	while(true)
	{
		j=19;
		while(j>=0&&fa[z][j]==0)j--;
		while(j>=0&&be[fa[z][j]]<=be[y]&&fi[y]<=fi[fa[z][j]])j--;
		if(j<0)return fa[z][0];
		z=fa[z][j];
	}
}
int walkup(int x,int s)
{
	int z=x,v=s,j;
	while(v>=1)
	{
		j=19;
		while((1<<j)>v)j--;
		z=fa[z][j];
		v=v-(1<<j);
	}
	return z;
}
int work1(int sd,int td)
{
	int z=sd,s=0,j;
	if(sd==td)
		if(dis1[sd]>ans)ans=dis1[sd];
	while(z!=td)
	{
		j=19;
		while(j>=0&&fa[z][j]==0)j--;
		while(j>=0&&be[fa[z][j]]<=be[td]&&fi[td]<=fi[fa[z][j]]&&fa[z][j]!=td)j--;
		if(up[z][j]+s>ans)ans=up[z][j]+s;
		z=fa[z][j];s=s+(1<<j);
	}
}
int work2(int sd,int td)
{
	int z=sd,s=0,j;
	while(z!=td)
	{
		j=19;
		while(j>=0&&fa[z][j]==0)j--;
		while(j>=0&&be[fa[z][j]]<=be[td]&&fi[td]<=fi[fa[z][j]]&&fa[z][j]!=td)j--;
		s=s+(1<<j);
		if(down[z][j]+deep[sd]-deep[td]+1-s+c>ans)ans=down[z][j]+deep[sd]-deep[td]+1-s+c;
		z=fa[z][j];
	}
}
int work3(int sd,int td)
{
	int z=sd,s=0,j;
	while(z!=td)
	{
		j=19;
		while(j>=0&&fa[z][j]==0)j--;
		while(j>=0&&be[fa[z][j]]<=be[td]&&fi[td]<=fi[fa[z][j]]&&fa[z][j]!=td)j--;
		if(up[z][j]+s+c>ans)ans=up[z][j]+s+c;
		z=fa[z][j];
		s=s+(1<<j);
	}
}
int main()
{
int i,j,dx,dy,v;
scanf("%d",&n);m=n-1;
for(i=1;i<=m;i++)
{
	scanf("%d %d",&way[i].x,&way[i].y);
	way[i+m].x=way[i].y;way[i+m].y=way[i].x;
}
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
bfs1();
bfs2();
for(j=1;j<=19;j++)
	for(i=1;i<=n;i++)
	{
		fa[i][j]=fa[fa[i][j-1]][j-1];
		if(fa[i][j-1]!=0)
		{
			up[i][j]=max(up[i][j-1],up[fa[i][j-1]][j-1]+(1<<(j-1)));
			down[i][j]=max(down[i][j-1]+(1<<(j-1)),down[fa[i][j-1]][j-1]);
		}
	}
scanf("%d",&T);
while(T>=1)
{
	scanf("%d %d",&x,&y);
	if(deep[x]<deep[y]){i=x;x=y;y=i;}
	g=LCA(x,y);mid=walkup(x,(deep[y]-deep[g]+deep[x]-deep[g])/2);
	for(i=first[g];i>=1&&i<=m;i=nxt[i])
		if(fa[way[i].y][0]==g&&be[way[i].y]<=be[x]&&fi[x]<=fi[way[i].y]){dx=way[i].y;break;}
	if(y!=g)
		for(i=first[g];i>=1&&i<=m;i=nxt[i])
			if(fa[way[i].y][0]==g&&be[way[i].y]<=be[y]&&fi[y]<=fi[way[i].y]){dy=way[i].y;break;}
	ans=0;c=deep[y]-deep[g];
	///////x
	if(mid!=g)work1(x,mid);
	else work1(x,dx);
	///////y
	//g
	if(num1[g]!=dx&&(num1[g]!=dy||y==g))v=dis1[g];
	else if(num2[g]!=dx&&(num2[g]!=dy||y==g))v=dis2[g];
		 else v=dis3[g];
	if(v+deep[y]-deep[g]>ans)ans=v+deep[y]-deep[g];
	//mid~dx
	if(be[dx]<=be[mid]&&fi[mid]<=fi[dx])work2(mid,dx);
	//fa[g]~1
	work3(g,1);
	//y~dy
	if(y!=g)work1(y,dy);
	//1-y
	if(deep[y]-deep[1]>ans)ans=deep[y]-deep[1];
	//x
	if(dis1[x]>ans)ans=dis1[x];
	//y
	if(y!=g&&dis1[y]>ans)ans=dis1[y];
	printf("%d\n",ans);
	T--;
}
}

T2:splay维护括号序。

首先我们把这棵树的括号序建出来,设st为1、ed为-1,然后我们发现一个x的deep就是1~st[x]的前缀和。

接下来各种操作可以用splay维护:

1、查询x和y的距离:有一个性质deep(LCA)=min(deep(x),deep(y),st[x]~st[y]中最小的前缀和(这里不包含st[x]和st[y]))

2、求x和h级祖先并将x连向它:首先我们要找出x和h级祖先,这个就相当于找一个1~st[x]中最靠右的一个前缀和(深度)为depe(x)-h的点。

找出来的之后我们就要把x连向这个点。这个好办,将st[x]~ed[x]的这一段区间提出来,然后将它插在ed[p]的前一个和ed[p]之间(p为x的h级祖先)就可以了。这个用splay很好维护。

3、查深度为x的最后一个点:从root开始往下走,每次判断一下右子树的max是否>=k,是则走右子树,否则走左子树。

注意几个细节:

1、max和min维护的是一个子树的max和min,而这个并不是具体的前缀和,这只是当我们走到这个子树来时产生的最大和最小贡献。贴一下代码方便理解:

a[x].ma=max(a[ch[x][0]].ma,a[ch[x][0]].sum+a[x].v+max(a[ch[x][1]].ma,0));
a[x].mi=min(a[ch[x][0]].mi,a[ch[x][0]].sum+a[x].v+min(a[ch[x][1]].mi,0));

2、每次求一个点的deep时都要从它走到root一次,而这个过程中不要加错了。

最后贴一下全部代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define MAXN 300010
#define MAXM 200010
using namespace std;

struct map
{
	int x;
	int y;
};
map way[MAXM];
struct tree
{
	int v;
	int sum;
	int ma;
	int mi;
	int fa;
	int size;
};
tree a[MAXN];
int first[MAXN],nxt[MAXM],st[MAXN],ed[MAXN],ret[MAXN],bz[MAXN],ch[MAXN][2],n,m,T,ne,ans,u,v,type,h,k;
int root,tim,d[MAXN*2],inf=2000000000,father[MAXN];
int dfs(int z)
{
	int i;
	bz[z]=1;
	tim++;d[tim]=1;st[z]=tim;
	for(i=first[z];i>=1&&i<=m;i=nxt[i])
		if(bz[way[i].y]==0)dfs(way[i].y);
	tim++;d[tim]=-1;ed[z]=tim;
}
int get(int x)
{
	if(ch[a[x].fa][0]==x)return 0;
	else return 1;
}
int node(int v)
{
	a[ne].v=v;a[ne].sum=v;a[ne].ma=v;a[ne].mi=v;
	a[ne].size=1;
}
int update(int x)
{
	a[x].sum=a[ch[x][0]].sum+a[ch[x][1]].sum+a[x].v;
	a[x].ma=max(a[ch[x][0]].ma,a[ch[x][0]].sum+a[x].v+max(a[ch[x][1]].ma,0));
	a[x].mi=min(a[ch[x][0]].mi,a[ch[x][0]].sum+a[x].v+min(a[ch[x][1]].mi,0));
	a[x].size=a[ch[x][0]].size+a[ch[x][1]].size+1;
}
int rotate(int x)
{
	int y=a[x].fa,z=a[y].fa,k1=get(x),k2=get(y);
	ch[y][k1]=ch[x][k1^1];a[ch[y][k1]].fa=y;
	a[y].fa=x;ch[x][k1^1]=y;
	a[x].fa=z;
	if(z!=0){ch[z][k2]=x;}
	update(y);update(x);
}
int splay(int sd,int td)
{
	int x=sd,y,z;
	while(a[x].fa!=td)
	{
		y=a[x].fa;z=a[y].fa;
		if(a[y].fa==td)rotate(x);
		else
		{
			if(((ch[z][1]==y)^(ch[y][1]==x))==0)rotate(y);
			else rotate(x);
			rotate(x);
		}
	}
	if(a[x].fa==0)root=x;
}
int rank(int x)
{
	int z=x,s=a[ch[x][0]].size+1;
	while(a[z].fa!=0)
	{
		if(get(z)==1)s=s+a[ch[a[z].fa][0]].size+1;
		z=a[z].fa;
	}
	return s;
}
int front(int x)
{
	int z;
	splay(x,0);
	z=ch[x][0];
	while(ch[z][1]!=0)z=ch[z][1];
	return z;
}
int back(int x)
{
	int z;
	splay(x,0);
	z=ch[x][1];
	while(ch[z][0]!=0)z=ch[z][0];
	return z;
}
int deep(int x)
{
	int z=x,s=a[ch[x][0]].sum+a[x].v;
	while(a[z].fa!=0)
	{
		if(get(z)==1)s=s+a[ch[a[z].fa][0]].sum+a[a[z].fa].v;
		z=a[z].fa;
	}
	return s;
}
int up(int x)
{
	int z=x,s=0;
	while(a[z].fa!=0)
	{
		if(get(z)==1)s=s+a[ch[a[z].fa][0]].sum+a[a[z].fa].v;
		z=a[z].fa;
	}
	return s;
}
int find(int x,int k)
{
	int z=x,s=0;
	while(true)
	{
		if(s+a[ch[z][0]].sum+a[z].v+a[ch[z][1]].ma>=k
		 &&s+a[ch[z][0]].sum+a[z].v+a[ch[z][1]].mi<=k&&ch[z][1]!=0)
		 	{s=s+a[ch[z][0]].sum+a[z].v;z=ch[z][1];}
		else
		{
			if(s+a[ch[z][0]].sum+a[z].v==k)break;
			z=ch[z][0];
		}
	}
	if(a[z].v==-1)return st[father[ret[z]]];
	else return z;
}
int insert(int v)
{
	int p;
	if(root==0)
	{
		ne++;root=ne;
		node(v);
		return 0;
	}
	p=root;
	while(ch[p][1]!=0)p=ch[p][1];
	ne++;ch[p][1]=ne;a[ne].fa=p;
	node(v);
	splay(ne,0);
}
int main()
{
int i,j,s,dep,g,p,x1,x2;
scanf("%d %d",&n,&T);
for(i=1;i<=n;i++)
{
	scanf("%d",&s);
	while(s>=1)
	{
		scanf("%d",&j);
		m++;way[m].x=i;way[m].y=j;
		s--;
		father[j]=i;
	}
}
for(i=1;i<=m;i++)way[i+m].x=way[i].y,way[i+m].y=way[i].x;
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
dfs(1);
for(i=1;i<=n;i++)st[i]++,ed[i]++;
for(i=1;i<=n;i++)ret[st[i]]=i,ret[ed[i]]=i;
for(i=0;i<=n*2+1;i++)a[i].mi=inf,a[i].ma=-inf;
for(i=0;i<=n*2+1;i++)insert(d[i]);
while(T>=1)
{
	scanf("%d",&type);
	if(type==1)
	{
		scanf("%d %d",&u,&v);
		if(u==v){printf("0\n");T--;continue;}
		if(rank(st[u])>rank(st[v])){i=u;u=v;v=i;}
		splay(st[u],0);splay(st[v],st[u]);
		dep=deep(st[u]);
		if(deep(st[v])<dep)dep=deep(st[v]);
		if(ch[st[v]][0]!=0)dep=min(dep,up(ch[st[v]][0])+a[ch[st[v]][0]].mi);
		printf("%d\n",deep(st[u])-dep+deep(st[v])-dep);
	}
	if(type==2)
	{
		scanf("%d %d",&u,&h);
		//////////find h ancestor
		splay(1,0);splay(st[u],1);
		g=ret[find(ch[st[u]][0],deep(st[u])-h)];
		//////////move
		//cut
		x1=front(st[u]);x2=back(ed[u]);
		splay(x1,0);splay(x2,x1);
		p=ch[x2][0];
		a[p].fa=0;ch[x2][0]=0;
		i=x2;while(i>=1)update(i),i=a[i].fa;
		//connect
		x1=front(ed[g]);
		splay(x1,0);splay(ed[g],x1);
		a[p].fa=ed[g];ch[ed[g]][0]=p;
		i=ed[g];while(i>=1)update(i),i=a[i].fa;
		father[u]=g;
	}
	if(type==3)
	{
		scanf("%d",&k);
		p=find(root,k+1);
		printf("%d\n",ret[p]);
	}
	T--;
}
}

T3:

这题的代码还比较简单。

首先我们预处理出f1[i][j]表示在(1,1)~(i,j)这个矩形中只从左和上伸出象鼻子时的最大收益。

那么f1[i][j]=max(f1[i-1][j]+left[i][j],f1[i][j-1]+up[i][j])。left和up表示(i,j)左边/上边的最大格子的值。

同理处理出f2、f3、f4,它们分别表示的是从左下、右上和右下方向的最小值。

然后我们发现所有的方案无非就分为一下两种情况:

1、

2、

箭头表示每一个块的象鼻子伸出的方向。2情况中的中间矩形是不选的。

对于方案1,我们暴力枚举中间空着的列的区间,直接用预处理出的f1、f2、f3、f4算就可以了。

对于方案2,我们可以枚举中间的矩形,然后用预处理的数组算。

时间复杂度是O(n^4)的。

总结:打这些复杂题时要多在纸上画一画,不要空想。否则会有很多情况处理错误。

发布了149 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chiyankuan/article/details/99310105