zoukankan      html  css  js  c++  java
  • 【题解】$test0628$ 消息传递

    ybt 1773 消息传递

    题目描述

    巴蜀国的社会等级森严,除了国王之外,每个人均有且只有一个直接上级,当然国王没有上级。如果 (A)(B) 的上级,(B)(C) 的上级,那么 (A) 就是 (C) 的上级。绝对不会出现这样的关系:(A)(B) 的上级,(B) 也是 (A) 的上级。

    最开始的时刻是 (0),你要做的就是用 (1) 单位的时间把一个消息告诉某一个人,让他们自行散布消息。在任意一个时间单位中,任何一个已经接到消息的人,都可以把消息告诉他的一个直接上级或者直接下属。

    现在,你想知道:

    1.到底需要多长时间,消息才能传遍整个巴蜀国的所有人?

    2.要使消息在传递过程中消耗的时间最短,可供选择的人有哪些?

    (\)


    (\)

    (Solution)

    第一次要写换根 (dp) 居然是在考场 = =,一共就 (5) 道,三道换根一道期望全都懵逼了 (QAQ)

    所以啊,只有平时额外下了功夫才可能考试考得好,不能总是考试考了才去学,虽然说吃一堑长一智,但是堑吃多了你就没了啊 = =

    所以还是想写篇博客加深一下印象~顺带记录第一道换根(?这有啥好纪念的啊

    (\)

    基础 (dp)

    这个还是比较好想的,这个还没想出来的可以出门左拐了

    (dp[i]) 表示从 (i) 往子树内传需要的最大时间,转移方程为 $$dp[i] = max(dp[son] + num[son])$$

    其中 (num[son]) 表示是第几个传给这个儿子的,有一个明显的贪心就是,把 (dp[son]) 排个序,从最大的开始赋值 (1 ~ size[i] - 1)

    单次复杂度 (O(nlog n)) ,加上枚举根,总复杂度 (O(n^2log n))

    (\)

    换根

    (down[i]) 表示从 (i) 往子树内传需要的最大时间,也是就上述的 (dp)

    (up[i]) 表示从 (i) 往外走,也就是除掉 (i) 子树后需要的最大时间

    于是就有 $$up[i] = max(up[fa] + num[fa], max { down[k] + num[k] }), k in son_{fa}, k eq i$$

    这个方程最好画画图,意思就是, 它爸的外部,和它爸的其他儿子,都是 (i) 子树的外部,所以一起计算答案

    (\)

    实现

    这道题最难的就是实现啊 (kk) ,口糊方程谁不会啊233

    (1.) 上面那个求 (up) 的方程,如果每一个都扫一遍就是 (O(n^2)) 的,所以是在 (i) 那里就更新所有的 (up[son_i])

    把所有的 (dp[son_i])(up[i]) 都丢进数组,排个序,用前缀后缀最大值维护,再枚举儿子,用二分找到它的位置,由于它可以不传递了,所以它后面所有时间都可以减一

    for(int i = head[x]; i; i = nxt[i])
       if(ver[i] != fa)
          g[++ k] = dp[ver[i]] + 1;//所有的儿子的dp
       if(down[x]) g[++ k] = down[x];//对8起!我把所有up都写成down 把down都写成dp了 233
       sort(g + 1, g + 1 + k, cmp);//按照从大到小排,num直接等于i
       beh[0] = beh[k + 1] = pre[0] = pre[k + 1] = 0;//全部清零会T,其实只用把最前面最后面清零即可
       F(i, 1, k) beh[i] = max(beh[i - 1], g[i] + i);//由于是从大到小排,所以后缀最大值是前缀
       for(int i = k; i >= 1; -- i) pre[i] = max(pre[i + 1], g[i] + i);
       F(i, 1, k) g[i] = - g[i]; //二分用lower_bound,是找第一个大于等于的,所以要存负值
       for(int i = head[x]; i; i = nxt[i])
          if(ver[i] != fa)
          {
             int pos = lower_bound(g + 1, g + 1 + k, -dp[ver[i]] - 1) - g;
             down[ver[i]] = max(pre[pos + 1] - 1, beh[pos - 1]);
          }
    

    (\)

    (2.)要注意的是,取答案时,照理来说应该是 (max(up[x], down[x])) ,但是如果两个相等,就必须有个先后,所以还要加一

    ans[x] = max(down[x], dp[x]) + (down[x] == dp[x]);
    

    (\)

    写完发现其实貌似也不是很难(雾

    完结撒花✿✿ヽ(°▽°)ノ✿

    (\)


    (\)

    (Code)

    #include<bits/stdc++.h>
    #define F(i, x, y) for(int i = x; i <= y; ++ i)
    using namespace std;
    int read();
    const int N = 4e5 + 5;
    int n, x;
    int dp[N], down[N], minn = 0x3f3f3f3f, ans[N], pre[N], beh[N];
    int head[N], cnt, ver[N << 1], nxt[N << 1];
    void add(int x, int y){ver[++ cnt] = y, nxt[cnt] = head[x], head[x] = cnt;}
    bool cmp(int x, int y) {return x > y;}
    void dfs(int x, int fa)
    {
    	int g[N], k = 0;
    	F(i, 0, n) g[i] = 0;
    	for(int i = head[x]; i; i = nxt[i])
    		if(ver[i] != fa)
    			dfs(ver[i], x), g[++ k] = dp[ver[i]];
    	sort(g + 1, g + 1 + k);
    	F(i, 1, k) g[i] += k - i + 1, dp[x] = max(dp[x], g[i]);
    }
    void change_root(int x, int fa)
    {
    	ans[x] = max(down[x], dp[x]) + (down[x] == dp[x]);
    	int g[N], k = 0;
    	for(int i = head[x]; i; i = nxt[i])
    		if(ver[i] != fa)
    			g[++ k] = dp[ver[i]] + 1;
    	if(down[x]) g[++ k] = down[x];
    	sort(g + 1, g + 1 + k, cmp);
    	beh[0] = beh[k + 1] = pre[0] = pre[k + 1] = 0;
    	F(i, 1, k) beh[i] = max(beh[i - 1], g[i] + i);
    	for(int i = k; i >= 1; -- i) pre[i] = max(pre[i + 1], g[i] + i);
    	F(i, 1, k) g[i] = - g[i]; 
    	for(int i = head[x]; i; i = nxt[i])
    		if(ver[i] != fa)
    		{
    			int pos = lower_bound(g + 1, g + 1 + k, -dp[ver[i]] - 1) - g;
    			down[ver[i]] = max(pre[pos + 1] - 1, beh[pos - 1]);
    		}	
    	for(int i = head[x]; i; i = nxt[i])
    		if(ver[i] != fa)
    			change_root(ver[i], x);
    }
    int main()
    {
    	n = read();
    	F(i, 2, n) x = read(), add(i, x), add(x, i);
    	dfs(1, 0), change_root(1, 0);
    	F(i, 1, n) minn = min(minn, ans[i]);
    	printf("%d
    ", minn + 1);
    	F(i, 1, n) if(ans[i] == minn) printf("%d ", i);
    	return 0;
    }
    /*--------------- Bn_ff 2020.7.2 ybt1773 ---------------*/
    int read()
    {
    	int x = 0, f = 1;
    	char c = getchar();
    	while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return x * f;
    }
    
  • 相关阅读:
    Azure PowerShell (7) 使用CSV文件批量设置Virtual Machine Endpoint
    Windows Azure Cloud Service (39) 如何将现有Web应用迁移到Azure PaaS平台
    Azure China (7) 使用WebMetrix将Web Site发布至Azure China
    Microsoft Azure News(4) Azure新D系列虚拟机上线
    Windows Azure Cloud Service (38) 微软IaaS与PaaS比较
    Windows Azure Cloud Service (37) 浅谈Cloud Service
    Azure PowerShell (6) 设置单个Virtual Machine Endpoint
    Azure PowerShell (5) 使用Azure PowerShell创建简单的Azure虚拟机和Linux虚拟机
    功能代码(1)---通过Jquery来处理复选框
    案例1.用Ajax实现用户名的校验
  • 原文地址:https://www.cnblogs.com/Bn_ff/p/13210517.html
Copyright © 2011-2022 走看看