做了三天,终于改完了。
这场比赛三道难题,其中两道是码农题。
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)的。
总结:打这些复杂题时要多在纸上画一画,不要空想。否则会有很多情况处理错误。