启发式合并、DSU on Tree

目录

梦幻布丁

好序列

CF 600E, Lomsat gelral

IOI2011, Race


梦幻布丁

启发式合并 

point 

        pos[i] :  存储颜色为i的点的坐标,   那为什么后面代码中有 int col = a[pos[y][0]], 而不直接  int col = y   呢?, 因为在启发式合并过程中,要判断两个集合的大小,有个swap操作,两个vector进行swap,pos[x] 和 pos[y] 里的元素交换,但x还是x,y还是y,所以刚开始时,   pos[i]  存储的的确是颜色为i的点的坐标,但后面就不一定了。

        查询答案时,并不是再遍历一遍a[]数组,而是在修改单点的颜色时,就修改ans

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
const int N = 101000;
const int M = 1010000;
int n, m, a[N], ans;
void modify(int p, int col) {
    ans -= (a[p] != a[p - 1]) + (a[p] != a[p + 1]);
    a[p] = col;
    ans += (a[p] != a[p - 1]) + (a[p] != a[p + 1]);
}
std::vector<int> pos[M];
int main(){
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        pos[a[i]].push_back(i); // 记录每一种颜色对应的下标
    }   
    for (int i = 1; i <= n; ++i)
        ans += (a[i] != a[i - 1]);
    for (int i = 1; i <= m; ++i) {
        int op;
        scanf("%d", &op);
        if (op == 2) {
            printf("%d\n", ans); // 只要之前遍历一遍就好了, 我还想这每一次查询都  O(n)
        } else {
            int x, y;
            scanf("%d %d", &x, &y);
            if (x == y) continue;
            if (pos[x].size() > pos[y].size())
                swap(pos[x], pos[y]);

            if (pos[y].empty()) continue;
            int col = a[pos[y][0]];
            for (int p : pos[x]) {
                modify(p, col);
                pos[y].push_back(p);
            }
            pos[x].clear();
        }
    }
    return 0;
}

好序列

如果一个点只出现一次,那么包含这个点的序列就都是“好的”, 可以用分治的思想来解决这个问题。

所以问题就指向了如何解决 判断某一个点是否只出现一次

还有一个问题需要解决:如果给定一个序列,而且只出现一次的点一直在末尾,那么我们就算分治,时间复杂度也是O(n^2) 

可以从首尾同时遍历,如果在中间,那么虽然O(n)遍历,但分治的区间均匀。如果在首尾,虽然分治的区间不均匀,但是遍历是O(1)

 

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
const int N = 201000;
int a[N], pre[N], nxt[N];

bool solve(int l, int r) {
   if (l >= r) return true;
   for (int pl = l, pr = r; pl <= pr; pl++, pr--) {
      if (pre[pl] < l && nxt[pl] > r)
         return solve(l, pl - 1) && solve(pl + 1, r);
      if (pre[pr] < l && nxt[pr] > r)
         return solve(l, pr - 1) && solve(pr + 1, r);
   }
   return false;
}

bool solve() {
   int n; scanf("%d", &n);
   for (int i = 1; i <= n; ++i)
      scanf("%d", &a[i]);
   map<int, int> pos;
   for (int i = 1; i <= n; ++i) {
      if(pos.count(a[i])) pre[i] = pos[a[i]];
      else pre[i] = 0;
      pos[a[i]] = i;
   }
   pos.clear();
   for (int i = n; i >= 1; --i) {
      if (pos.count(a[i])) nxt[i] = pos[a[i]];
      else nxt[i] = n + 1;
      pos[a[i]] = i;
   }
   return solve(1, n);
}
int main(){
   int tc; scanf("%d", &tc);
   for (int i = 1; i <= tc; ++i) {
      puts(solve() ? "non-boring" : "boring");
   }

   return 0;
}

CF 600E, Lomsat gelral

You are given a rooted tree with root in vertex 11. Each vertex is coloured in some colour.

Let's call colour cc dominating in the subtree of vertex vv if there are no other colours that appear in the subtree of vertex vv more times than colour cc. So it's possible that two or more colours will be dominating in the subtree of some vertex.

The subtree of vertex vv is the vertex vv and all other vertices that contains vertex vv in each path to the root.

For each vertex vv find the sum of all dominating colours in the subtree of vertex vv.

Input

The first line contains integer n(1 ≤ n ≤ 105)n(1 ≤ n ≤ 105) — the number of vertices in the tree.

The second line contains nn integers ci(1 ≤ ci ≤ n)ci(1 ≤ ci ≤ n), cici — the colour of the ii-th vertex.

Each of the next n − 1n − 1 lines contains two integers xj, yj(1 ≤ xj, yj ≤ n)xj, yj(1 ≤ xj, yj ≤ n) — the edge of the tree. The first vertex is the root of the tree.

Output

Print nn integers — the sums of dominating colours for each vertex.

Examples

input

4
1 2 3 4
1 2
2 3
2 4

output

10 9 3 4

input

15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13

output

6 5 4 3 2 3 3 1 1 3 2 2 1 2 3

 

给定一棵有根的树,根在顶点1。每个顶点都有颜色。

如果在顶点v的子树中没有其他颜色比颜色c出现的次数更多,我们就称颜色c在顶点v的子树中占主导地位,所以有可能在某个顶点的子树中占主导地位的颜色有两种或两种以上。

顶点v的子树是顶点v和所有其他包含顶点v的顶点在到根结点的每条路径上。

对于每个顶点v,求顶点v子树中所有主要颜色的和。

DSU on Tree    DSU的意思是并查集,但好笑的是这个跟并查集没有一点关系。 反而是跟启发式合并的关系很大。 

dfs_solve()中的参数  keep 是保留 ,   保留的是cnt数组中的内容,如果是轻儿子的话,贡献的cnt是会被撤回的。

主要思想就是:将u和u的轻儿子都并到u的重儿子。   重儿子因为节点个数多,所以少的并到多的里,可以节省时间(启发式合并)

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
int n;
const int N = 101000;
std::vector<int> e[N];
int l[N], r[N], id[N], sz[N], hs[N], tot, c[N];
int cnt[N], maxcnt;
ll sumcnt, ans[N];

void dfs_init(int u, int f) {
   l[u] = ++tot;
   id[tot] = u;
   sz[u] = 1;
   hs[u] = -1;
   for (auto v : e[u]) {
      if (v == f) continue;
      dfs_init(v, u);
      sz[u] += sz[v];
      if (hs[u] == -1 || sz[v] > sz[hs[u]])
         hs[u] = v;
   }
   r[u] = tot;
}


void add(int x) {
   x = c[x];
   cnt[x]++;
   if (cnt[x] > maxcnt) maxcnt = cnt[x], sumcnt = 0;
   if (cnt[x] == maxcnt) sumcnt += x;
}
 
void del(int x) {
   x = c[x];
   cnt[x]--;
}
void dfs_solve(int u, int f, bool keep) {
   for (auto v : e[u]) {
      if (v != f && v != hs[u]) {
         dfs_solve(v, u, false);
      }
   }
   if (hs[u] != -1)
      dfs_solve(hs[u], u, true);

   for (auto v : e[u]) {
      if (v != f && v != hs[u]) { // v 是轻儿子
         // 把v子树里所有点加入到重儿子的集合里
         for (int x = l[v]; x <= r[v]; ++x)
            add(id[x]);
      }
   }
   add(u);
   ans[u] = sumcnt;
   if (!keep) {
      maxcnt = 0;
      sumcnt = 0;
      // 清空
      for (int x = l[u]; x <= r[u]; ++x)
         del(id[x]);
   }
}
int main(){
   scanf("%d", &n);
   for (int i = 1; i <= n; ++i)
      scanf("%d", &c[i]);
   for (int i = 1; i < n; ++i) {
      int u, v;
      scanf("%d %d", &u, &v);
      e[u].push_back(v);
      e[v].push_back(u);
   }
   dfs_init(1, 0);
   dfs_solve(1, 0, false);
   for (int i = 1; i <= n; ++i)
      printf("%lld ", ans[i]);
   printf("\n");

   return 0;
}

 

IOI2011, Race

 也是DSU,基于上一题的代码有所修改。但这题的标程不是这种解决方法,不过思想还是比较重要的。

注意点:

        将轻儿子中的点加入重儿子的过程中,query和add不能同时(一个for)进行。原因是:如果查询一个点,就加入这个点,那么之后查询的时候,可以两个点都是在轻儿子里的,逻辑上就已经不满足我们写的代码了。

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
int n, k;
const int N = 201000;
std::vector<PII> e[N];
map<int, int> val;
int l[N], r[N], id[N], sz[N], hs[N], tot;
ll ans;
int dep1[N];
ll dep2[N];
void dfs_init(int u, int f) {
   l[u] = ++tot;
   id[tot] = u;
   sz[u] = 1;
   hs[u] = -1;
   for (auto [v, w] : e[u]) {
      if (v == f) continue;
      dep1[v] = dep1[u] + 1;
      dep2[v] = dep2[u] + w;
      dfs_init(v, u);
      sz[u] += sz[v];
      if (hs[u] == -1 || sz[v] > sz[hs[u]])
         hs[u] = v;
   }
   r[u] = tot;
}


void dfs_solve(int u, int f, bool keep) {

   for (auto [v, w] : e[u]) {
      if (v != f && v != hs[u]) {
         dfs_solve(v, u, false);
      }
   }
   if (hs[u] != -1)
      dfs_solve(hs[u], u, true);

   auto query = [&] (int w) {
        // d2 + dep2[w] - 2 * dep2[u] = k
        ll d2 = k + 2 * dep2[u] - dep2[w];
        if (val.count(d2)){
            ll tmp = val[d2] + dep1[w] - 2 * dep1[u];
            ans = min(ans, tmp);
        }
   };

   auto add = [&] (int w) {
        if (val.count(dep2[w]))
            val[dep2[w]] = min(val[dep2[w]], dep1[w]);
        else val[dep2[w]] = dep1[w];
   };

   for (auto [v, w] : e[u]) {
      if (v != f && v != hs[u]) {
         for (int x = l[v]; x <= r[v]; ++x)
            query(id[x]);
         for (int x = l[v]; x <= r[v]; ++x)
            add(id[x]);
      }
   }
   query(u), add(u);
   if (!keep) {
      val.clear();
   }
}
int main(){
   scanf("%d %d", &n, &k);
   
   for (int i = 1; i < n; ++i) {
      int u, v, w;
      scanf("%d %d %d", &u, &v, &w);
      u++, v++;
      e[u].push_back({v, w});
      e[v].push_back({u, w});
   }
   ans = n + 1;
   dfs_init(1, 0);
   dfs_solve(1, 0, false);
   if (ans == n + 1) printf("-1\n");
   else printf("%lld\n", ans);

   return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xingxg.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值