DTOJ3702 月读(tsukuyomi)

58 篇文章 0 订阅

题目

题目描述

有些时候,出题人真的不想写背景(???)
总而言之,月读现在有一棵大小为 N N N,树上每条边上有一个数字,月读有 M M M次询问,每次询问一对 ( x , y ) (x,y) (x,y),你需要回答从 x x x y y y的路径上的数字重新排列能否形成一个回文序列,若可行输出Yes,否则输出No
月读为了加快您的读入,每次询问的 x , y x,y x,y是通过某种方式生成的,为了加快您的输出,你只需要最后输出回答Yes的个数和即可

输入格式

第一行:两个整数 N , M N,M N,M
接下来 N − 1 N-1 N1行:三个整数 u i , v i , w i u_i,v_i,w_i ui,vi,wi​,描述一条边 ( u i , v i ) (u_i,v_i) (ui,vi)与边上的数字 w i w_i wi
接下来一行 A 1 , B 1 A_1,B_1 A1,B1
M M M个询问以如下方式生成
x i = A i % n + 1 , y i = B i % n + 1 x_i=A_i \% n+1,y_i=B_i \% n+1 xi=Ai%n+1,yi=Bi%n+1
A i = A i − 1 × 666073 % ( 1 0 9 + 7 ) A_i=A_{i-1} \times 666073 \% (10^9+7) Ai=Ai1×666073%(109+7)
B i = B i − 1 × 233 % 998244353 B_i=B_{i-1} \times 233 \% 998244353 Bi=Bi1×233%998244353

输出格式

一行一个数字,代表回答Yes的个数

样例

样例输入

4 1
1 2 1
2 3 1
1 4 2
2 3

样例输出

1

数据范围与提示

对于此题有六个测试点:
A( 10 10 10分) N , M ⩽ 2000 N,M \leqslant 2000 N,M2000
B( 23 23 23分) N , M ⩽ 1 0 5 , u i = v i + 1 N,M \leqslant 10^5,u_i=v_i+1 N,M105,ui=vi+1
C( 15 15 15分) N , M ⩽ 1 0 5 N,M \leqslant 10^5 N,M105
D( 15 15 15分) N , M ⩽ 1 0 6 , w i ⩽ 30 N,M \leqslant 10^6,w_i \leqslant 30 N,M106,wi30
E( 17 17 17分) N , M ⩽ 1 0 6 N,M \leqslant 10^6 N,M106
F( 20 20 20分) N ⩽ 1 0 6 , M ⩽ 1 0 7 N \leqslant 10^6,M \leqslant 10^7 N106,M107
对于所有的 w i ⩽ n wi \leqslant n win

题解

如果重新排列能形成一个回文序列的话,那么满足出现的次数是奇数的数字最多只能有一个
所以我们可以对每一种边权进行哈希赋值,和 0 0 0一起存入哈希表中
接着,我们记 X o r i Xor_i Xori为根到 i i i点的边权异或和
那么,如果询问的点为 x , y x,y x,y,只要 f x ∧ f y f_x\land f_y fxfy在哈希表里出现过,回答就是 Y e s Yes Yes
注意:如果你在DTOJ上提交,建议你不要使用C++11(NOI),使用C++11,否则可能会超时
附上代码:

#include<cstdio>
int n,m,Tot,tot,cnt,Head[1000010],Nxt[1000010],head[1000010],to[2000010],ver[2000010],nxt[2000010],dfn[1000010];
unsigned long long POW[1000010],pow[2000010],Ver[1000010];
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
} 
void Add(unsigned long long x)
{
	int y=x%1000007;
	Nxt[++Tot]=Head[y],Head[y]=Tot,Ver[Tot]=x;
}
int Find(unsigned long long x)
{
	int y=x%1000007;
	for(int i=Head[y];i;i=Nxt[i]) if(Ver[i]==x) return 1;
	return 0;
}
void add(int u,int v,int w)
{
	nxt[++tot]=head[u],head[u]=tot,to[tot]=v,ver[tot]=w;
}
void dfs(int x,int fa,unsigned long long Pow)
{
	dfn[x]=++cnt,pow[cnt]=Pow;
	for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa) dfs(to[i],x,POW[ver[i]]),pow[++cnt]=POW[ver[i]];
}
int main()
{
	n=read(),m=read(),POW[0]=1,Add(0);
	for(int i=1;i<=n;i++) POW[i]=POW[i-1]*793999,Add(POW[i]);
	for(int i=1,u,v,w;i<n;i++) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
	dfs(1,0,(unsigned long long)0);
	for(int i=2;i<=cnt;i++) pow[i]^=pow[i-1];
	int a,b,ans=0;
	a=read(),b=read();
	for(int i=1,x,y;i<=m;i++)
		x=a%n+1,y=b%n+1,a=666073ll*a%1000000007,b=233ll*b%998244353,ans+=Find(pow[dfn[x]]^pow[dfn[y]]);
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值