边防军
未知来源(雾)
来自NK20191016考试T3
题面
问题描述
有一个边防要塞,由n个碉堡和n-1条道路连接而成,任意两个碉堡间都可以相互到达,碉堡编号1到n。
由于处于和平时期,该要塞并没满员,只有20个值班士兵,他们的战斗力分别是20,21,22,23… 。他们分布于各个碉堡中,由于士兵人数较少,有的碉堡是空的。士兵们经常接到上级的命令,命令分为两种:
0号命令.形如“0 A B”的三个整数,表示交换A碉堡和B碉堡里的士兵; 1号命令.形如“1 A B C”的四个整数,表示询问碉堡A到碉堡B的路径上,有多少条子路径满足路径上经过的碉堡里的士兵的战斗力异或和恰好为C。
对于每个询问,需要你快速做出回答。
输入格式
第一行两个整数n和m,分别表示碉堡数量和命令的条数。
接下来n-1行,每行两个整数A B ,表示编号A和编号B的碉堡间有道路直连
接下来一行,20个整数,分别表示初始时战斗力为 20,21,22,23的士兵所在碉堡编号。
接下来m行,每行表示一个命令。
输出格式
若干行,每行对于一次1号命令的询问结果。
数据范围
n,m<=500000,n>=20,C<220
心路历程&&题解思路
其实试题里还有一句话就是有40分保证n<=100,然后我就拿了这四十分。。。
首先,0号命令的“交换”乱搞就可以了。。
其实这道题你不难发现,它们的异或运算无非就是加法,因为二进制下每个有士兵的碉堡的编号里,有"1"的位置一定是不一样的。所以,如果满足条件,异或起来是C,那么:
满足条件的路径上的碉堡一定会“连在一起”,中间不会有其它带权值碉堡,只允许存在权值为0的碉堡。如下:
设从x到y会经过这些碉堡(用二进制的权值表示,括号内为点的编号):
10000(1) 00000(2) 00000(3) 00001(4) 00000(5) 00000(6) 00100(7) 00000(8) 00000(9) 00010(10) 00000(11) 01000(12) 00000(13)
而答案要求异或和C为:00111
显然,4号到10号这条路径是符合要求的,而如果在4号到10号的路径中混入了其它带权节点,就不符合条件了,这一次询问的结果就是0。
可是慢着,你们还记得一个数异或0还是等于自己么?看看2号,3号,以及后面的11号,他们权值都为零,而且他们之间没有其它带权的节点,那么计算方案数时为何不带上他们呢?这个时候,乘法原理用一下,算出方案数是6.
听完上述过程,想必大家对于怎么做已经清楚了吧。
而对于一次询问,假定从x点到y点,因为这是一颗树,所以路径一定会经过LCA(x,y),而这条路径上的点权不是0就是二进制中的某一位,所以我们只需要记录下路径中带权值的点以及他们离起点(设为x号点)的距离,以此来确定依次走过的带权值碉堡的顺序。
那么怎么判断一个带权点是不是在路径中呢?
首先带权的点最多只有20个,所以完全可以暴力扫一遍判断,而判断必须是O(1)的时间复杂度。其实在树重有一个很方便的判子树,亲戚关系等的东西:dfs序,它可以记录下一个点管辖子树的dfn值范围。令xy=LCA(x,y),当前讨论到第i个带权节点。如果i号节点在xy的子树上,并且x在i的子树上,那么i就存在于x到xy的路径上,距离x的距离就是dep[x]-dep[i];同理,如果i号节点在xy的子树上,并且y在i的子树上,那么i就存在于y到xy的路径上,距离x的距离就是dep[x]+dep[i]-2*dep[xy]; (dep[]表示节点在树中的深度)
那么,我说完了。(如果还不会做就是你自己的问题了)
说了那么久,上代码!
#include<stdio.h>
#include<bits/stdc++.h>
#define ll long long
#define intinf 1e9
#define llinf 1e17
using namespace std;
char buf[1<<20],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=GC;}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=GC;
return w?-X:X;
}
const int maxn=5e5+100;
int path[maxn*2],End[maxn*2],Next[maxn*2],Last[maxn*2],cnt,pn,dfn[maxn],ma[maxn],haha;
int dep[maxn],fa[maxn][22];
int n,m,man[maxn],bu[maxn],sum[maxn],hh[maxn];
int q[maxn],top;
void add(int x,int y)
{
End[++cnt]=y;
Next[cnt]=Last[x];
Last[x]=cnt;
}
void dfs(int x)
{
int i,k;
dfn[x]=++haha;
dep[x]=dep[fa[x][0]]+1;
k=ceil(log2(dep[x]));
for(i=1;i<=k;i++)
{
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(i=Last[x];i;i=Next[i])
{
if(End[i]!=fa[x][0])
{
int v=End[i];
fa[v][0]=x;
dep[v]=dep[x]+1;
dfs(v);
}
}
ma[x]=haha;
}
int LCA(int x,int y)
{
int i,k,s;
s=ceil(log2(n));
if(dep[x]<dep[y]) swap(x,y);
k=dep[x]-dep[y];
for(i=0;i<=s;i++)
{
if(k&(1<<i))
x=fa[x][i];
}
if(x==y) return x;
s=ceil(log2(dep[x]));
for(i=s;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];y=fa[y][i];
}
return fa[x][0];
}
ll work()
{
int x,y,xy,k,num;
ll res=0;
x=read();y=read();k=read();
xy=LCA(x,y);
num=dep[x]+dep[y]-2*dep[xy];
int l=num,r=0;
top=0;
for(int i=0;i<20;i++)
{
int pos=man[i],len=-1;//len记录pos节点到x的距离
if(dfn[xy]<=dfn[pos]&&ma[xy]>=dfn[pos])//pos节点在xy的子树上
{
if(dfn[pos]<=dfn[x]&&ma[pos]>=dfn[x]) len=dep[x]-dep[pos];//在x到cy上
if(dfn[pos]<=dfn[y]&&ma[pos]>=dfn[y]) len=dep[x]+dep[pos]-2*dep[xy];//在y到xy上
}
if(len==-1)
{
if(k&(1<<i)) return 0;
continue;
}
if(k&(1<<i)) l=min(l,len),r=max(r,len);
else q[++top]=len;
}
q[0]=-1;q[++top]=num+1;
sort(q,q+top+1);
if(l>r)
{
for(int i=1;i<=top;i++)
res+=1LL*(q[i]-q[i-1])*(q[i]-q[i-1]-1)/2;
}
else
{
for(int i=1;i<=top;i++)
{
if(l>q[i-1]&&r<q[i])
{
res=1LL*(l-q[i-1])*(q[i]-r);
break;
}
}
}
return res;
}
int main()
{
n=read();m=read();
int x,y,id,c;
for(int i=1;i<n;i++)
{
x=read();y=read();
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++) hh[i]=21;
for(int i=0;i<20;i++)
{
man[i]=read();//第i个士兵的位置
hh[man[i]]=i;//第man[i]个碉堡对应士兵编号
}
dfs(1);
while(m--)
{
id=read();
if(id==0)
{
x=read();y=read();
man[hh[x]]=y;man[hh[y]]=x;
swap(hh[x],hh[y]);
}
else printf("%lld\n",work());
}
return 0;
}
码量较大,小心驶得万年船啊。
最后祝大家AC愉快!