题意:
告诉你树上的点权, 问你是否存在一条路径,(u,v) 使得这条路上的点权乘积 % mod = K? 存在的话输出字典序最小的(u,v)不存在输出no solution
思路:
这种链上的题 基本就是树剖和分治。
树剖要枚举所有链把= = 不太好弄。
那就是分治了。
找到重心后, 肯定是要处理那些经过重心的链, 然后合并。 是否有解。
很容易想到的是 dfs每个点 到重心的乘积, 然后合并, 但是会出现不经过重心的链子。
其实我觉得 怎样处理不出现经过重心的链子才是难点。
其实也不难, 依次枚举重心的每一个孩子, 先把这个孩子的后代 完全检查完(看看能不能找到匹配点), 在更新这个孩子的后代。
这样保证了每次检查时候存在点 肯定不在这个孩子的后代里。(很巧妙)
然后就是怎么匹配的问题。
题目本身是 一堆点乘积 % mod = k, 现在分治的话 已经转换成了 a * b % mod == K的形式。
已知K 和 b 求a, 那么显然是 K * b对mod的逆元, 因为mod 是素数, 并且a和b 都小于mod 肯定和mod 互质, 那么逆元直接用快速幂求解好了。
注意:
存储匹配点时,用了一个map 和unordered_map 结果都是超时。
其实想一想, 用一个数组来判断这个点存不存在即可。
但是还要方便清空, 那就在离散化一下好了!!
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 100000 + 10;
const int mod = 1000000 + 3;
int n, K;
int a[maxn];
int inv[mod];
int pow(int a, int n){
int ans = 1;
while(n){
if (n & 1)
ans = int((ans * (long long)a) % mod);
a = int((a * (long long )a) % mod);
n >>= 1;
}
return ans;
}
vector<int>g[maxn];
bool mp[mod]; ///mp[i] = 1表示 i 这个点存在
int hs[mod]; /// hs[i] 表示值为i 的点是谁?
int ids[mod]; /// 离散化一下。方便清空。
int cr;
int mx[maxn], siz[maxn];
int vis[maxn];
void getsize(int cur,int fa){
mx[cur] = 0;
siz[cur] = 1;
for (int i = 0; i < g[cur].size(); ++i){
int v = g[cur][i];
if (v != fa && !vis[v]){
getsize(v, cur);
siz[cur] += siz[v];
if (siz[v] > mx[cur]){
mx[cur] = siz[v];
}
}
}
}
int root, mi;
const int inf = 0x3f3f3f3f;
void findroot(int rt, int cur,int fa){
mx[cur] = max(mx[cur], siz[rt] - siz[cur]);
if (mx[cur] < mi){
mi = mx[cur];
root = cur;
}
for (int i = 0; i < g[cur].size(); ++i){
int v = g[cur][i];
if (v != fa && !vis[v]){
findroot(rt, v, cur);
}
}
}
pair<int,int>ans;
void update(pair<int,int>& u){
if (u.first > u.second) swap(u.first, u.second);
if (u.first < ans.first){
ans = u;
}
else if (u.first == ans.first){
if (u.second < ans.second){
ans = u;
}
}
}
void check(int cur,int fa,int d){
int d2 = (int) (((long long)d * a[root]) % mod);
int o = (int)((K * (long long)inv[d2]) % mod);
int o2 = int((K * (long long)inv[o]) % mod);
// if (root == 2){
//
// printf("check %d %d\n", cur, d2);
// }
if (mp[o2]){
pair<int,int>u;
u.first = cur;
u.second = hs[o2];
update(u);
}
for (int i = 0; i < g[cur].size(); ++i){
int v = g[cur][i];
if ( v != fa && !vis[v]){
check(v, cur, int((d * (long long)a[v]) % mod) );
}
}
}
void Fill(int cur,int fa,int d){
int o = (int) ((K * (long long)inv[d]) % mod);
// if (root == 2){
// printf("fill %d %d\n", cur, d);
// }
if (!mp[o]){
ids[cr++] = o;
hs[o] = cur;
mp[o] = 1;
}
else {
hs[o] = min(hs[o], cur);
}
for (int i = 0; i < g[cur].size(); ++i){
int v = g[cur][i];
if (v != fa && !vis[v]){
Fill(v, cur, int( ((long long)d * a[v])%mod ));
}
}
}
void solve(int cur){ /// 分治的核心
int o = (int) ((K * (long long)inv[1 ]) % mod);
if (!mp[o]){
ids[cr++] = o;
hs[o] = cur;
mp[o] = 1;
}
else {
hs[o] = min(hs[o], cur);
}
for (int i = 0; i < g[cur].size(); ++i){
int v = g[cur][i];
if (!vis[v]){
check(v, cur, a[v]); /// 先检查一个孩子后代
Fill(v, cur, a[v] ); /// 在填充一个孩子后代
}
}
}
void dfs(int cur){
getsize(cur, 0);
mi = inf;
findroot(cur, cur, 0);
cr = 0;
solve(root);
int tmp = root;
vis[root] = 1;
for (int i = 0; i < cr; ++i){
mp[ids[i] ] = 0;
}
for (int i = 0; i < g[tmp].size(); ++i){
int v = g[tmp][i];
if (!vis[v]){
dfs(v);
}
}
}
int main(){
inv[0] = 1;
for (int i = 1; i < mod; ++i){
inv[i] = pow(i, mod-2);///处理逆元
}
// printf("%d\n", (8*inv[4]) % mod == 8*inv[(8*inv[2])%mod ] % mod);
while(~scanf("%d %d", &n, &K)){
ans.first = inf;
ans.second = inf;
for (int i = 1; i <= n; ++i){
g[i].clear();
scanf("%d",&a[i]);
vis[i] = 0;
}
for (int i = 1; i < n; ++i){
int x, y;
scanf("%d %d",&x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1);
if (ans.first == inf){
puts("No solution");
}
else {
printf("%d %d\n", ans.first, ans.second);
}
}
return 0;
}
D Tree
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 102400/102400 K (Java/Others)Total Submission(s): 4892 Accepted Submission(s): 995
Can you help them in solving this problem?
Each test case starts with a line containing two integers N(1 <= N <= 10 5) and K(0 <=K < 10 6 + 3). The following line contains n numbers v i(1 <= v i < 10 6 + 3), where vi indicates the integer on vertex i. Then follows N - 1 lines. Each line contains two integers x and y, representing an undirected edge between vertex x and vertex y.
For more information, please refer to the Sample Output below.
5 60 2 5 2 3 3 1 2 1 3 2 4 2 5 5 2 2 5 2 3 3 1 2 1 3 2 4 2 5
3 4 No solutionHint1. “please print the lexicographically smallest one.”是指: 先按照第一个数字的大小进行比较,若第一个数字大小相同,则按照第二个数字大小进行比较,依次类推。 2. 若出现栈溢出,推荐使用C++语言提交,并通过以下方式扩栈: #pragma comment(linker,"/STACK:102400000,102400000")