链接:https://www.nowcoder.com/acm/contest/90/B
来源:牛客网
题目描述
有一无限循环字母表:
现在有一棵n个节点(编号1-n)的有根树(1为根),树边边权为一个字母θ,在每一时刻θ会向前跳K步变为相应字母(即树边边权改变),如:
n每一时刻会向前跳3步,第1时刻变为q,第2时刻变为t,以此类推。
w每一时刻会向前跳2步,第1时刻变为y,第2时刻变为a,以此类推。
JK会给你Q个询问,让你判断两个节点在t时刻到根节点路径权值(路径权值为该节点到根节点的路径上字母按顺序拼成的字符串)的字典序大小关系。
输入描述:
第一行一个整数T ( 0 < T < 3),代表测试数据组数。
每一组测试数据第一行给出树的节点数n(1< n <=100000)。
接下去的n-1行的第i行给出一个整数P(1<=P<=n),一个字母θ([a-z])以及字母变换的步数K(0<=K<=10000),表示编号为i+1的节点的父亲节点编号为P,以及边的描述。(输入保证为一棵树)
下一行询问数Q(0 < Q<=10000),每个询问一行给出整数u(2<=u<=n),v(2<=v<=n),t(0<=t<=10000),判断编号为u,v两个节点在t时刻到根节点路径权值的字典序大小关系。
输出描述:
对每个询问输出一行答案,
编号u到根节点路径权值的字典序小于v的输出“<”,
相等输出“=”,
否则输出“>”。(不包含该引号)
示例1
输入
1
10
1 a 1
1 a 5
1 c 2
2 f 2
3 a 5
3 e 3
4 b 1
5 z 1
7 o 2
4
5 7 0
5 7 2
9 10 1
8 8 8
输出
>
<
<
=
解题思路:
1. 从最暴力的做法开始想起,可以暴力的一个一个往上跳,找到第一个不同的字符,然后进行比较即可。 复杂度O(Qn)。 题目只给了1s的时间 会超时。
2. 但这个字符串是否相同是有单调性的,所以想到可以二分查询两条路径上字符串相同的长度。最多会进行logn次查询,那么每次查询的复杂度就不能是O(n) 也就是说不能暴力的去查找。 开始想如何优化。
3. 由于是字符串 两个字符串是否相等 可以哈希预处理有在O(1)的时间内判断。 所以可以在O(n)的时间内处理处所有字符串前缀的哈希值。
4. 那么现在,假设当前二分查询的长度为len,那么只要知道当前节点往上跳len次的节点是什么了。直接开数组暴力存所有节点肯定不行。 联想到lca的树上倍增。 这样就可以在O(logn)的时间内查找到k代父节点的位置了。所以查询复杂度为O(Q*logn*logn) 因为树有26种状态,所以预处理的复杂度为O(26*n*logn) 。总复杂度达到了可以接受的范围。 不过牛客网的评测机不是很稳定,时间浮动范围可能达到400ms,所以代码常数比较大的话 可以尝试多交几发。。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cmath>
#include<iostream>
#include<sstream>
#include<iterator>
#include<algorithm>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<deque>
#include<queue>
#include<list>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int MAX=1e5+10;
const ULL seed=652525;
class Edge {
public:
int u,v,next;
char w;
};
int head[MAX];
int tot;
Edge edge[MAX<<2];
void add(int u,int v,char w) {
edge[tot].u=u,edge[tot].v=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot;
tot++;
}
ULL base[MAX];
ULL tree[30][MAX];
int fa[MAX][20];
int k[MAX];
char thew[MAX];
char lists[26];
int depth[MAX];
void build(int u,int per) {
fa[u][0]=per;
//cout<<u<<endl;
for(int i=1; i<20; i++) {
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u]; i!=-1; i=edge[i].next) {
build(edge[i].v,u);
}
}
void dfs1(int id,int u) {
for(int i=head[u]; i!=-1; i=edge[i].next) {
int v=edge[i].v;
depth[v]=depth[u]+1;
tree[id][v]=tree[id][u]*seed+lists[(edge[i].w-'a'+k[v]*id)%26];
dfs1(id,v);
}
}
ULL gethash(int len,int e,int id) {
int s=e;
for(int i=0; (1<<i)<=len; i++) {
if((1<<i)&len) {
s=fa[s][i];
}
}
return tree[id][e]-tree[id][s]*base[len];
}
bool check(int u,int v,int len,int id) {
//cout<<len<<","<<id<<","<<gethash(len,u,id)<<","<<gethash(len,v,id)<<endl;
return gethash(len,u,id)==gethash(len,v,id);
}
int bound(int u,int v,int t) {
int r=min(depth[u],depth[v]);
int l=0;
int ans=-1;
while(l<=r) {
int m=(l+r)>>1;
if(check(u,v,m,t)) {
l=m+1;
} else {
r=m-1;
ans=m;
}
}
//cout<<ans<<endl;
if(ans==-1) {
if(depth[u]==depth[v]) return -1;
else if(depth[u]>depth[v]) return 1;
else return 0;
} else {
ans--;
for(int i=0; (1<<i)<=ans; i++) {
if((1<<i)&ans) {
u=fa[u][i];
v=fa[v][i];
}
}
if((thew[u]-'a'+k[u]*t)%26>(thew[v]-'a'+k[v]*t)%26) return 1;
else return 0;
}
}
void init() {
tot=0;
memset(head,-1,sizeof head);
memset(tree,0,sizeof tree);
memset(depth,0,sizeof depth);
memset(k,0,sizeof k);
}
int main() {
base[0]=1;
for(int i=1; i<MAX; i++) {
base[i]=base[i-1]*seed;
}
for(int i=0; i<26; i++) {
lists[i]=i+'a';
}
int T;
scanf("%d",&T);
while(T--) {
int n;
scanf("%d",&n);
init();
for(int i=2; i<=n; i++) {
int v;
char str;
scanf("%d %c %d",&v,&str,&k[i]);
thew[i]=str;
add(v,i,str);
}
build(1,1);// 倍增父节点
for(int i=0; i<26; i++) {// 预处理出26种状态
dfs1(i,1);
}
int Q;
scanf("%d",&Q);
int u,v,t;
while(Q--) {
scanf("%d %d %d",&u,&v,&t);
t%=26;// 以26位循环节
int ans=bound(u,v,t);
if(ans==-1) puts("=");
else if(ans==1) puts(">");
else puts("<");
}
}
}
/*
1
10
1 a 1
1 a 5
1 c 2
2 f 2
3 a 5
3 e 3
4 b 1
5 z 1
7 o 2
4
5 7 0
5 7 2
9 10 1
8 8 8
*/