洛谷P8972 『GROI-R1』 一切都已过去(树上前缀和+运算符重载)

2024-03-17 21:36

本文主要是介绍洛谷P8972 『GROI-R1』 一切都已过去(树上前缀和+运算符重载),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

『GROI-R1』 一切都已过去

题目背景

悦关上窗,拉上帘布。

果然还是想不起来啊。

隐约记得曾和什么人一起做过这样的事。

仰面躺下,手执一只木笺。

「究竟如何,才能拥有“过去”啊……」

她闭上双眼。

「6 岁前的记忆……究竟如何才能寻回?」

题目描述

悦正在寻找她的记忆。忽然,她来到了有 n n n 个节点的一棵树上。树上每一条边都有各自边权,每一个点都有各自的点权。

「把经历都聚拢起来,能完整地复原吗……」

悦从树上的一个点,慢慢地走到了另一个点,可是她什么也没找到。但是,她不知道,玘一直在远处望着她走过的道路。

玘发现,悦全程没有走回头路。他想把悦走过的每一条边的边权乘起来,可惜他发现他遇到了一个这一生未曾见到过的数字。

「为什么会这样呢?」

玘想到悦是突然出现在树上的,最初的点一定有蹊跷!他把最初那个点的点权乘上……

突然,一束彼岸花的红光亮起!世界重新安静了下来。

悦看到了玘留下的字样,可惜她不能从中看出任何过去的记忆。现在,你要帮她判断:把经历都聚拢起来,能完整地复原过去吗?我们称悦的一条路径能“复原过去”,当且仅当玘留下的乘积是一个整数

形式化题面

给定一棵 n n n 个节点的树和 q q q 次询问。每次询问给出两个整数 x , y x,y x,y,表示询问树上以 x x x y y y 为端点的简单路径上边权乘积与点 x x x 的点权相乘是否为整数。

输入格式

第一行两个正整数 n n n q q q,表示树上有 n n n 个节点编号为 1 ∼ n 1\sim n 1n,悦在树上走了 q q q 条路径。

接下来一行 n n n 个非负整数表示每个点的点权 a i a_i ai

接下来 n − 1 n-1 n1 行每行两个正整数 u , v u,v u,v 和一个非负实数 w w w 表示 u , v u,v u,v 间有一条边权为 w w w 的边。

接下来 q q q 行,每行两个正整数 x , y x,y x,y,表示悦从点 x x x 开始走到了点 y y y

输出格式

对于悦的每一次询问,你需要输出一行一个字符串。如果悦能够成功复原她的过去,请输出 Yes,否则请输出 No

样例 #1

样例输入 #1

5 3
1 2 3 4 5
1 2 0.1
2 3 0.20
3 4 0.5
2 5 0.99
1 5
1 4
4 3

样例输出 #1

No
No
Yes

提示

样例解释

根据输入可以得到下图:

对于第一个询问 ( 1 , 5 ) (1,5) (1,5) 可以发现悦经过的边的边权分别是 0.1 0.1 0.1 0.99 0.99 0.99,她出发的 1 1 1 号点的点权为 1 1 1 1 × 0.1 × 0.99 = 0.099 1\times0.1\times0.99=0.099 1×0.1×0.99=0.099 不是整数。所以输出 No

对于后面两次询问同理。

数据范围

本题采用捆绑测试。

子任务编号数据范围特殊性质分值
Subtask1 \text{Subtask1} Subtask1 n , q ≤ 3 × 1 0 3 n,q\le3\times 10^3 n,q3×103 15 15 15
Subtask2 \text{Subtask2} Subtask2 n ≤ 500 n\le500 n500 q ≤ 1 0 5 q\le10^5 q105 10 10 10
Subtask3 \text{Subtask3} Subtask3 n , q ≤ 1 0 5 n,q\le10^5 n,q105 BE \text{BE} BE 10 10 10
Subtask4 \text{Subtask4} Subtask4 n , q ≤ 1 0 5 n,q\le10^5 n,q105 A \text{A} A 5 5 5
Subtask5 \text{Subtask5} Subtask5 n , q ≤ 1 0 5 n,q\le10^5 n,q105 B \text{B} B 10 10 10
Subtask6 \text{Subtask6} Subtask6 n , q ≤ 1 0 5 n,q\le10^5 n,q105 C \text{C} C 5 5 5
Subtask7 \text{Subtask7} Subtask7 n , q ≤ 1 0 5 n,q\le10^5 n,q105 D \text{D} D 10 10 10
Subtask8 \text{Subtask8} Subtask8 n , q ≤ 2 × 1 0 5 n,q\le2×10^5 n,q2×105 35 35 35

特殊性质 A \text{A} A:保证树退化成一条链。

特殊性质 B \text{B} B:保证树随机生成(即对于每一个节点随机选择它的父亲节点)。

特殊性质 C \text{C} C:保证 w ∈ { 0.1 , 0.3 , 0.5 , 0.7 , 0.9 } w\in\{0.1,0.3,0.5,0.7,0.9\} w{0.1,0.3,0.5,0.7,0.9}

特殊性质 D \text{D} D:保证 w ∈ { 0.1 , 0.2 , 0.3 , 0.4 , 0.6 , 0.7 , 0.8 , 0.9 } w\in\{0.1,0.2,0.3,0.4,0.6,0.7,0.8,0.9\} w{0.1,0.2,0.3,0.4,0.6,0.7,0.8,0.9}

特殊性质 E \text{E} E:保证 w ≤ 2 w\le2 w2 w w w 小数位数不超过 1 1 1 位。

对于 100 % 100\% 100% 的数据满足 1 ≤ n , q ≤ 2 × 1 0 5 1\le n,q\le2\times10^5 1n,q2×105 0 ≤ a i ≤ 1 0 9 0\le a_i\le10^9 0ai109 0 ≤ w ≤ 1 0 4 0\le w\le10^4 0w104 1 ≤ u , v , x , y ≤ n 1\le u,v,x,y\le n 1u,v,x,yn x ≠ y x\ne y x=y w w w 小数位数不超过 4 4 4 位。

涉及知识:树上前缀和(LCA),运算符重载。

思路:对于树上两节点间距离或者权值和之类的问题很容易想到树上前缀和的思路,但是按照表面意思进行前缀和之间的乘法的话精度完全不够所以需要转换一下思维,为了让一个小数转换成整数,我们可以枚举一下看看。
得到的可以完成整数转换的其实只有一类:

//0.5*2 -> 0.1 * 5 * 2  --
//0.2*5	-> 0.1 * 5 * 2  ---> 0.1 * 10
//0.4*2.5 -> 0.01 * 5^2 * 2^2  ---> 0.01 * 10^2

因此,只需要统计一下2和5的次幂数以及所有小数点后位数之和,那么2和5组成的是的次幂数即 n u m 10 = m i n ( n u m 2 , n u m 5 ) num_{10} = min(num_2,num_5) num10=min(num2,num5) 。并比较其与小数点后位数之和的大小即得答案。
其中有两个坑点:
如果你的代码TLE了,可能是因为你没有考虑点权为0的情况。
如果你的代码WA了,可能是以为你没有考虑边权出现0的情况。
因此我们只需要特判一下以上两种情况就差不多解决这个问题了。

#include <bits/stdc++.h>
using namespace std;#define all(x) x.begin(), x.end()
#define bit1(x) __builtin_popcount(x)
#define Pqueue priority_queue
#define lc p << 1
#define rc p << 1 | 1
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#define fi first
#define se secondtypedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<ll, ll> PII;const ll mod = 1000000007;
const ll N = 1e6 + 10;
const ld eps = 1e-9;
const ll inf = 1e18;
const ll P = 131;
const ll dir[8][2] = {1, 0, 0, 1, -1, 0, 0, -1, 1, 1, 1, -1, -1, 1, -1, -1};struct node
{int a, b, c;
} g[N];//定义一个结构体a,b,c依次对应小数点后位数、2的次幂数、5的次幂数
//即由1到第i个节点的树上前缀和
node operator*(node a, int b)
{return node({a.a * b, a.b * b, a.c * b});
}//重载运算符 *
node operator+(node a, node b)
{return node({a.a + b.a, a.b + b.b, a.c + b.c});
}//重载运算符 +
node operator-(node a, node b)
{return node({a.a - b.a, a.b - b.b, a.c - b.c});
}//重载运算符 -
ostream &operator<<(ostream &out, node a)
{out << a.a << " " << a.b << " " << a.c << "\n";return out;
}//重载运算符 << (用于Debug)
node check(double x)
{node tmp({0, 0, 0});if (x == 0)return tmp;while (x != (ll)x){x *= 10;tmp.a++;}//取小数点后位数ll t = x;while (t % 2 == 0){t /= 2;tmp.b++;}//取2的次幂数while (t % 5 == 0){t /= 5;tmp.c++;}//取5的次幂数return tmp;
}//返回一个实数x包含的node信息int fa[N][21];//父节点数组i的第2^j位父亲
int dep[N];	//深度数组
int Log2[N + 10];//预处理Log2数组
int zero[N];	//0的树上前缀和数组,由1到第i个节点路径上0的个数
int n, m, u, v;
double w;
vector<pair<int, double>> G[N];	//邻接表存图void Dfs(int u, int f)		//DFS求LCA并构建树上前缀和
{dep[u] = dep[f] + 1;	//求深度fa[u][0] = f;			//u的第一个父亲是ffor (int i = 1; i <= Log2[dep[u]]; i++)fa[u][i] = fa[fa[u][i - 1]][i - 1];	//u的第2^i个父亲是 (u的第2^{i-1}个父亲) 的第 2^{i-1} 个父亲for (auto [v, w] : G[u])	//遍历子节点{if (v == f)continue;zero[v] = zero[u] + (w==0);	//统计0的个数g[v] = g[u] + check(w);		//统计其他树上前缀和Dfs(v, u);}
}
int Lca(int a, int b)				//倍增法求LCA
{if (dep[a] > dep[b])swap(a, b);while (dep[a] != dep[b])b = fa[b][Log2[dep[b] - dep[a]]];if (a == b)return a;for (int i = Log2[dep[a]]; i >= 0; i--)if (fa[a][i] != fa[b][i])a = fa[a][i], b = fa[b][i];return fa[a][0];
}void solve()
{// node a({1, 2, 3}), b({3, 2, 1});// cout << a * 2;// cout << a + b;// cout << a - b;cin >> n >> m;vector<int> val(n + 10);for (int i = 1; i <= n; i++)cin >> val[i];for (int i = 1; i < n; i++){cin >> u >> v >> w;G[u].push_back({v, w});G[v].push_back({u, w});}Dfs(1, 0);// for (int i = 1; i <= n; i++)//     cout << g[i];for (int i = 1; i <= m; i++){cin >> u >> v;node tmp = g[u] + g[v] - g[Lca(u, v)] * 2 + check(val[u]);//由 u 到 v 路径上的树上前缀和 + 点权中的信息// cout << check(cal[u]) << tmp;int z = zero[u] + zero[v] - zero[Lca(u, v)] * 2;	//0的树上前缀和if (!val[u] || z > 0 || min(tmp.b, tmp.c) >= tmp.a)//特判两个坑点cout << "Yes\n";elsecout << "No\n";}
}int main()
{Log2[0] = -1;for (int i = 1; i <= N; i++)Log2[i] = Log2[i / 2] + 1;//预处理Log2数组IOS int T = 1;// cin>>T;while (T--)solve();return 0;
}/*
oxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxox
x                                                                                      o
o       _/_/_/_/                                                              _/       x
x      _/                                                                              o
o     _/_/_/_/ _/  _/_/   _/_/   _/_/_/ _/_/   _/_/_/     _/_/    _/_/_/    _/ _/   _/ x
x    _/       _/_/     _/    _/ _/   _/   _/  _/    _/ _/    _/  _/    _/  _/   _/ _/  o
o   _/       _/       _/    _/ _/   _/   _/  _/    _/ _/    _/  _/    _/  _/    _/_/   x
x  _/       _/         _/_/   _/   _/   _/  _/_/_/     _/_/ _/ _/    _/  _/      _/    o
o                                          _/                           _/      _/     x
x                                         _/                        _/_/       _/      o
o                                                                                      x
xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo
*/

这篇关于洛谷P8972 『GROI-R1』 一切都已过去(树上前缀和+运算符重载)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/820243

相关文章

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

【重学 MySQL】十九、位运算符的使用

【重学 MySQL】十九、位运算符的使用 示例检查权限添加权限移除权限 在 MySQL 中,位运算符允许你直接在整数类型的列或表达式上进行位级操作。这些操作对于处理那些需要在二进制表示上进行直接修改或比较的场景特别有用,比如权限管理、状态标记等。 &(位与) 对两个数的二进制表示进行位与操作。只有两个相应的二进制位都为 1 时,结果的该位才为 1,否则为 0。 |(位

C语言程序设计(数据类型、运算符与表达式)

一、C的数据类型 C语言提供的数据类型: 二、常量和变量 2.1常量和符号常量 在程序运行过程中,其值不能被改变的量称为常量。 常量区分为不同的类型: 程序中用#define(预处理器指令)命令行定义变量将代表常量,用一个标识符代表一个常量,称为符合常量。 2.2变量 变量代表内存中具有特定属性的一个存储单元,用来存放数据,在程序运行期间,这些值是可以 改变的。 变

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际

C++可以被重载的操作符Overloadable operators

C++允许绝大多数操作符被重载,也就是重新定义操作符实现的功能,这样它们的行为可以被设计出来以适应所有的数据类型,包括类。 以下是C++可以被重载的操作符(Overloadable operators): //四则运算符+ - * / %+= -= *= /= %=//比较运算符> >= == != //赋值运算符= //位操作

c++/《重载操作符》

为什么要对运算符进行重载:         C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。 <返回类型说明符> operator <运算符符号>(<参数表>) { <函数体> }

【LeetCode热题100】前缀和

这篇博客共记录了8道前缀和算法相关的题目,分别是:【模版】前缀和、【模版】二维前缀和、寻找数组的中心下标、除自身以外数组的乘积、和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和。 #include <iostream>#include <vector>using namespace std;int main() {//1. 读取数据int n = 0, q = 0;ci

《C++中的移动构造函数与移动赋值运算符:解锁高效编程的最佳实践》

在 C++的编程世界中,移动构造函数和移动赋值运算符是提升程序性能和效率的重要工具。理解并正确运用它们,可以让我们的代码更加高效、简洁和优雅。 一、引言 随着现代软件系统的日益复杂和对性能要求的不断提高,C++程序员需要不断探索新的技术和方法来优化代码。移动构造函数和移动赋值运算符的出现,为解决资源管理和性能优化问题提供了有力的手段。它们允许我们在不进行不必要的复制操作的情况下,高效地转移资源

Java基础--基本运算符介绍

Java运算符 用于指明对于操作数的运算方式。 分类: 按照操作数的数目来进行分类: 单目a++ 双目a+b 三目(a>b)?x:y; 按照运算符的功能来进行分类: 算术运算:+ - * / %(取余)++ – 如: int x=1; x=x+1;//x空间内的值,自己增加了一个 x++;//x空间内的值,自增一个 ++x;//对于x空间内的值来讲都是一致,最终的结果都自

前缀和 — 利用前缀信息解决子数组问题

【前缀和的核心思想是预先处理数组来快速计算任意子数组的和,基本上用于数组和序列问题。】 前缀和算法具体步骤 构造前缀和数组: 给定一个数组nums,其前缀和数组prex定义为prex[i]表示为数组nums从起始位置到第i个位置的元素累加和。构建前缀和公式: p r e x [ i ] = n u m s [ i ] ( i = = 0 ) p r e x [ i ] = p r e x