点分治 && HDU - 4812

题目大意

给出一棵树,让你寻找一条路径,使得路径上的点相乘mod10^6+3等于k,输出路径的两个端点,按照字典序最小输出。

input

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

output

3 4
No solution

idea

这个题像是换了一种思想。
直接用个图来讲吧
这里写图片描述
紫色的是每个节点的权值,粉红色的是节点编号,k等于120
我们开一个set,用来存当前已有的值,命名为s
首先我们遍历1的子树,我们走到2号节点,去s里面找与120 / (5*3)发现没有,继续走5号节点,找120 / (4*3 5),发现没有,又走到6号节点,找120 / (3*5*3)发现没有,返回2号节点,这个时候这棵子树就遍历完了,我们把5,4*5,5*3丢进s。继续去遍历4,这个时候,我们找120/(3 4)发现没有,找120/(3 * 4 * 3)发现没有,返回到3号节点,把4, 4 * 3丢进s,这个时候我们走到了4号节点这个位置,去找120 /(3 * 2), 发现有,ok记录此时的编号,继续放下走,去找120/(3 * 2 * 1),发现没有, 继续放下走,去找120/(3 * 2 * 2),发现没有。返回4号节点,把2, 2 * 1, 2 * 2丢进去。1号节点子树结束 ,递归枚举其余子树。

code

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e6 + 100;
const int mod = 1e6 + 3;
int root, sonMax[maxn], sonNum[maxn], allNode, p[maxn];
ll k, data[maxn], inv[maxn];
int ans1, ans2;
bool vis[maxn];
set<ll> s;
set<ll> ::iterator it;
vector<int> Edge[maxn];

//inv用来存放逆元
//data是每个节点的值
//p是当前值所对应的节点编号

void Get_inv() { //线性处理逆元
   inv[0] = 0, inv[1] = 1;
   for(int i = 2; i < maxn; i++) {
      inv[i] = (mod - mod / i) % mod * inv[mod % i];
      inv[i] %= mod;
   }
}


void getroot(int now, int pre) {
   sonMax[now] = 0; sonNum[now] = 1;
   for(int i = 0; i < Edge[now].size(); i++) {
       int to = Edge[now][i];
       if(to == pre || vis[to]) continue;
       getroot(to, now);
       sonNum[now] += sonNum[to];
       sonMax[now] = max(sonMax[now], sonNum[to]);
   }
   sonMax[now] = max(sonMax[now], allNode - sonNum[now]);
   if(sonMax[root] > sonMax[now]) root = now;
}

void check_node (int now, int pre, ll val) {
   ll t = k * inv[val] % mod; //t是与当前点相对应的数

   it = s.find(t);

   if(it != s.end()) { //有值,就更新答案
       int start = min(p[t], now), End = max(p[t], now);
       if(!ans1) ans1 = start, ans2 = End;
       else {
          if(ans1 > start || (ans1 == start && ans2 > End))
          {
             ans1 = start; ans2 = End;
          }
       }
   }

   for(int i = 0; i < Edge[now].size(); i++) {
       int to = Edge[now][i], w = data[to];
       if(to == pre || vis[to]) continue;
       check_node(to, now, val * w % mod); //继续递归子树
   }
}

void push_in(int now, int pre, ll val) {
   it = s.find(val); //找当前值是否已经存在
   if(it != s.end()) { //存在的话就更新下最小编号
       p[val] = min(p[val], now);
   }
   else s.insert(val), p[val] = now; //否则就直接插入

   for(int i = 0; i < Edge[now].size(); i++) {
      int to = Edge[now][i], w = data[to];
      if(to == pre || vis[to]) continue;
      push_in(to, now, val * w % mod);
   }
}

void FindNode(int now) {
   s.clear();
   s.insert(1); //s用来存放现有的值 这里很重要 想一想为什么要把1也放进去
   p[1] = now;
   for(int i = 0; i < Edge[now].size(); i++) {
     int to = Edge[now][i];
     if(vis[to]) continue;
     check_node(to, now, data[to] * data[now] % mod); //用来遍历这个子树,看这个子树的每个值能否在之前
     //遍历过得点找到相对应的点满足要求
     push_in(to, now, data[to]); //将这棵子树里的点放进去
   }
}

void solve(int now) {

   FindNode(now); //处理以这个点为根的子树
   vis[now] = true;

   for(int i = 0; i < Edge[now].size(); i++) {
      int to = Edge[now][i];
      if(vis[to]) continue;
      sonMax[0] = allNode = sonNum[to];
      getroot(to, root = 0);
      solve(root);
   }
}

int main()
{
    int n;
    Get_inv(); //预处理逆元
    while(~scanf("%d %lld", &n, &k)) {
        for(int i = 0; i <= n; i++) {
            vis[i] = false;
            Edge[i].clear();
        }
        for(int i = 1; i <= n; i++)
            scanf("%lld", &data[i]);
        for(int i = 0; i < n - 1; i++) {
            int u, v;
            scanf("%d %d", &u, &v);
            Edge[u].push_back(v);
            Edge[v].push_back(u);
        }

        ans1 = ans2 = 0; //ans1代表编号小的那个端点 ans2表示这条路的另一个端点
        sonMax[0] = allNode = n;
        getroot(1, root = 0);
        solve(root);
        if(!ans1) printf("No solution\n");
        else printf("%d %d\n", ans1, ans2);
    }
}

猜你喜欢

转载自blog.csdn.net/deerly_/article/details/82595350