[COCI2016-2017#1] Mag 题解

题目TP门

题目描述

你将获得一棵由无向边连接的树。树上每个节点都有一个魔力值。

我们定义,一条路径的魔力值为路径上所有节点魔力值的乘积除以路径上的节点数。

例如,若一条路径包含两个魔力值分别为\(3,5\)的节点,则这条路径的魔力值为 \(3\times 5/2=7.5\)。

请你计算,这棵树上魔力值最小的路径的魔力值。

输入格式

第一行一个整数\(n\),表示树共有\(n\)个节点,编号为\(1\dots n\)。

接下来\(n-1\)行,每行两个整数\(a_i,b_i\),表示编号为\(a_i,b_i\)的两个节点由一条无向边连接。

接下来\(n\)行,每行一个整数\(x_i\),表示编号为\(i\)的节点的魔力值。

输出格式

一行,一个既约分数\(p/q\)。

输入输出样例

输入 #1

2
1 2
3
4

输出 #1

3/1

输入 #2

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

输出 #2

1/2

说明/提示

【样例解释】

样例 1 解释

注意,路径可以只包含一个节点。

这棵树上魔力值最小的路径的包含节点\(1\),其魔力值为\(3/1\)。

样例 2 解释

这棵树上魔力值最小的路径的包含节点\(2,4\),其魔力值为\(1\times 1/2=1/2\)。

数据规模与约定

对于\(100\%\)的数据,\(1\le n\le 10^6\),\(1\le a_i,b_i\le n\),\(1\le x_i\le 10^9\)。

数据保证,\(p,q\)不会超过\(10^{18}\)。

说明

题目译自 COCI2016-2017 CONTEST #1 T4 Mag。

思路

首先来证明一点。满足题意的最优解一定为由全部是1,或仅含有1个2的链来组成的(当且仅当2两边的)。

   a     b

——————2——————

1 1 1 1 1 1 1 2 1 1 1 1 1 1 1

证明:

  • 当\(a=b\)时,含有2的链的价值为:\(\frac{2}{a+b+1}=\frac{2}{2a +1}\),而不含有2的链价值为\(\frac{1}{a}\),很明显,含有二的链比含有1的链优。
  • 当\(a≠b\)时,不妨设\(a>b\),设\(a-b=k\),含有2的链价值为\(\frac{2}{a+b+1}=\frac{2}{2a-k +1}\),而含1的链为\(\frac{1}{a-k}=\frac{2}{2a-2k}\),明显含有1的链更优。在1链和1链中添加任意一个大于2的数,则都会上述情况一样,没有含1的链更优,可以以上述情况来推广。

有了上述的证明,就可以进行树形DP了。就称只含有1的链为1链,其中含了一的1链为2链。

设\(dp[i][0|1][0|1]\)的第一维表示是哪一个节点的状态。对应的价值最小。第二维表示该节点参与的链中是否含有2这个点,若含有2,则该维度对应值为0,否则为1。第三维表示分子分母,1为分子,0为分母。

\(sec[0|1][0|1]\)对应的是次小值。其中的第一维与\(dp[i]\)的第二维意义相同,第二维与其第三维相同。

\(id[0|1][0|1]\)的第一维表示该节点参与的链中是否含有2这个点,若含有2,则该维度对应值为0,否则为1。第二维表示最小一次对应次小与最小。

先来说说如何状态转移。

如下面的代码所示(主体部分)。

for(int i = 0; i < SIZ; i++) {
int next = v[now][i];
if(next == fa)
continue;
DP(next, now);
if(val[now] == 1) {
if(val[next] > 2)
continue;
double son1 = (dp[next][1][1] * 1.0) / ((dp[next][1][0] + 1) * 1.0);
double son2 = (dp[next][0][1] * 1.0) / ((dp[next][0][0] + 1) * 1.0);
double self1 = (dp[now][1][1] * 1.0) / (dp[now][1][0] * 1.0);
double self2 = (dp[now][0][1] * 1.0) / (dp[now][0][0] * 1.0);
double num1 = (sec[1][1] * 1.0) / (sec[1][0] * 1.0);
double num2 = (sec[0][1] * 1.0) / (sec[0][0] * 1.0);
if(son1 < self1) {
sec[1][1] = dp[now][1][1];
sec[1][0] = dp[now][1][0];
id[1][0] = id[1][1];
dp[now][1][1] = dp[next][1][1];
dp[now][1][0] = dp[next][1][0] + 1;
id[1][1] = next;
}
else if(son1 < num1) {
id[1][0] = next;
sec[1][1] = dp[next][1][1];
sec[1][0] = dp[next][1][0] + 1;
}
if(son2 < self2) {
sec[0][1] = dp[now][0][1];
sec[0][0] = dp[now][0][0];
id[0][0] = id[0][1];
dp[now][0][1] = dp[next][0][1];
dp[now][0][0] = dp[next][0][0] + 1;
id[0][1] = next;
}
else if(son2 < num2) {
sec[0][1] = dp[next][0][1];
sec[0][0] = dp[next][0][0] + 1;
id[0][0] = next;
}
}
else if(val[now] == 2) {
if(val[next] != 1)
continue;
double son = (dp[next][1][1] * 2.0) / ((dp[next][1][0] + 1) * 1.0);
double self = (dp[now][0][1] * 1.0) / (dp[now][0][0] * 1.0);
double num = (sec[0][1] * 1.0) / (sec[0][0] * 1.0);
if(son < self) {
sec[0][1] = dp[now][0][1];
sec[0][0] = dp[now][0][0];
id[0][0] = id[0][1];
dp[now][0][1] = dp[next][1][1] * 2;
dp[now][0][0] = dp[next][1][0] + 1;
id[0][1] = next;
}
else if(son < num) {
sec[0][1] = dp[next][1][1] * 2;
sec[0][0] = dp[next][1][0] + 1;
id[0][0] = next;
}
}
}

情况一

设当前正在遍历的节点为now,对于now的每一个子节点next,若有now的权值为1,则有:

  • son1:当前子节点带有的最小1链加上父节点的价值。
  • son2:当前子节点带有的最小2链加上父节点的价值。
  • self1:最小1链的价值。
  • self2:最小2链的价值。
  • num1:次小1链的价值。
  • num2:次小2链的价值。

若next的权值大于2,则没有资格更新自己的父节点。需要先判断是否有资格更新父节点的值。

if(val[next] > 2)
continue;

又有几种情况:

  • 当son1 < self1时,即是当前子节点的1链可以更新最小链的1链。先使用最小1链来更新次小1链的值。在使用子节点来更新最小1链的值。代码如下。
if(son1 < self1) {
sec[1][1] = dp[now][1][1];
sec[1][0] = dp[now][1][0];
id[1][0] = id[1][1];
dp[now][1][1] = dp[next][1][1];
dp[now][1][0] = dp[next][1][0] + 1;
id[1][1] = next;
}
  • 在不满足上述情况时,当前子节点仅仅只能更新次小链的值,那么就用次小链更新最小链的值。代码如下。
else if(son1 < num1) {
id[1][0] = next;
sec[1][1] = dp[next][1][1];
sec[1][0] = dp[next][1][0] + 1;
}
  • 更新最小二链与最次小二链,与更新1链的方法相似。更新最小2链的代码如下。
if(son2 < self2) {
sec[0][1] = dp[now][0][1];
sec[0][0] = dp[now][0][0];
id[0][0] = id[0][1];
dp[now][0][1] = dp[next][0][1];
dp[now][0][0] = dp[next][0][0] + 1;
id[0][1] = next;
}
  • 更新次小2链的价值
else if(son2 < num2) {
sec[0][1] = dp[next][0][1];
sec[0][0] = dp[next][0][0] + 1;
id[0][0] = next;
}

情况二

设当前正在遍历的节点为now,对于now的每一个子节点next,若有now的权值为2。因为这条链中必会有2,所以就不需要考虑更新1链的价值。则有:

  • son:当前子节点带有的最小1链加上父节点的价值。
  • self2:最小2链的价值。
  • num:次小2链的价值。

若当前子节点的子节点的权值不为1,则也没有资格更新父节点。

if(val[next] != 1)
continue;

更新方式与上述相同。

但最小2链只能由儿子的1链来更新,因为一条2链中只能含有1个2,而当前节点就是2。

  • 更新最小
if(son < self) {
sec[0][1] = dp[now][0][1];
sec[0][0] = dp[now][0][0];
id[0][0] = id[0][1];
dp[now][0][1] = dp[next][1][1] * 2;
dp[now][0][0] = dp[next][1][0] + 1;
id[0][1] = next;
}
  • 更新次小
else if(son < num) {
sec[0][1] = dp[next][1][1] * 2;
sec[0][0] = dp[next][1][0] + 1;
id[0][0] = next;
}

现在来更新答案

又分了几种情况

  • 只用最小2链。若当前父节点只含有1个子节点,则只有1条链来更新答案。
double Num = (dp[now][0][1] * 1.0) / (dp[now][0][0] * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][0][1];
ans2 = dp[now][0][0];
}
  • 只用最小1链,理由与2链相同。
Num = (dp[now][1][1] * 1.0) / (dp[now][1][0] * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][1][1];
ans2 = dp[now][1][0];
}
  • 最长1链加最长2链。当且仅当最小2链与最小1链来自于不同的子节点。
if(id[1][1] != id[0][1]) {
Num = ((dp[now][0][1] * dp[now][1][1]) * 1.0) / ((dp[now][0][0] + dp[now][1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][0][1] * dp[now][1][1];
ans2 = dp[now][0][0] + dp[now][1][0] - 1;
}
}
  • 若不满足最小2链与最小1链来自于不同的子节点,则使用次小1,2链与最小2,1链相结合来更新答案(注意顺序)。
else {
if(id[1][0] != id[0][1]) {
Num = ((dp[now][0][1] * sec[1][1]) * 1.0) / ((dp[now][0][0] + sec[1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][0][1] * sec[1][1];
ans2 = dp[now][0][0] + sec[1][0] - 1;
}
}
if(id[0][0] != id[1][1]) {
Num = ((sec[0][1] * dp[now][1][1]) * 1.0) / ((sec[0][0] + dp[now][1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = sec[0][1] * dp[now][1][1];
ans2 = sec[0][0] + dp[now][1][0] - 1;
}
}
}
  • 用最小1链与次小1链来更新答案。
Num = ((dp[now][1][1] * sec[1][1]) * 1.0) / ((dp[now][1][0] + sec[1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][1][1] * sec[1][1];
ans2 = dp[now][1][0] + sec[1][0] - 1;
}

输出

记得分子分母需要互质。

C++代码

#include <cstdio>
#include <vector>
using namespace std;
#define INF 1e8
#define Min(a, b) ((a) < (b) ? (a) : (b))
void Quick_Read(int &N) {
N = 0;
char c = getchar();
int op = 1;
while(c < '0' || c > '9') {
if(c == '-')
op = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
N = (N << 1) + (N << 3) + c - 48;
c = getchar();
}
N *= op;
}
const int MAXN = 1e6 + 5;
bool flag;
vector<int> v[MAXN];
int down[MAXN], dp[MAXN][2][2];
int val[MAXN];
int n, minn;
double ans;
int ans1, ans2;
int GCD(int a, int b) {
return b == 0 ? a : GCD(b, a % b);
}
void DP(int now, int fa) {
int sec[2][2];
int id[2][2];
for(int i = 0; i <= 1; i++)
for(int j = 0; j <= 1; j++)
sec[i][j] = id[i][j] = INF;
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i];
if(next == fa)
continue;
DP(next, now);
if(val[now] == 1) {
if(val[next] > 2)
continue;
double son1 = (dp[next][1][1] * 1.0) / ((dp[next][1][0] + 1) * 1.0);
double son2 = (dp[next][0][1] * 1.0) / ((dp[next][0][0] + 1) * 1.0);
double self1 = (dp[now][1][1] * 1.0) / (dp[now][1][0] * 1.0);
double self2 = (dp[now][0][1] * 1.0) / (dp[now][0][0] * 1.0);
double num1 = (sec[1][1] * 1.0) / (sec[1][0] * 1.0);
double num2 = (sec[0][1] * 1.0) / (sec[0][0] * 1.0);
if(son1 < self1) {
sec[1][1] = dp[now][1][1];
sec[1][0] = dp[now][1][0];
id[1][0] = id[1][1];
dp[now][1][1] = dp[next][1][1];
dp[now][1][0] = dp[next][1][0] + 1;
id[1][1] = next;
}
else if(son1 < num1) {
id[1][0] = next;
sec[1][1] = dp[next][1][1];
sec[1][0] = dp[next][1][0] + 1;
}
if(son2 < self2) {
sec[0][1] = dp[now][0][1];
sec[0][0] = dp[now][0][0];
id[0][0] = id[0][1];
dp[now][0][1] = dp[next][0][1];
dp[now][0][0] = dp[next][0][0] + 1;
id[0][1] = next;
}
else if(son2 < num2) {
sec[0][1] = dp[next][0][1];
sec[0][0] = dp[next][0][0] + 1;
id[0][0] = next;
}
}
else if(val[now] == 2) {
if(val[next] != 1)
continue;
double son = (dp[next][1][1] * 2.0) / ((dp[next][1][0] + 1) * 1.0);
double self = (dp[now][0][1] * 1.0) / (dp[now][0][0] * 1.0);
double num = (sec[0][1] * 1.0) / (sec[0][0] * 1.0);
if(son < self) {
sec[0][1] = dp[now][0][1];
sec[0][0] = dp[now][0][0];
id[0][0] = id[0][1];
dp[now][0][1] = dp[next][1][1] * 2;
dp[now][0][0] = dp[next][1][0] + 1;
id[0][1] = next;
}
else if(son < num) {
sec[0][1] = dp[next][1][1] * 2;
sec[0][0] = dp[next][1][0] + 1;
id[0][0] = next;
}
}
}
double Num = (dp[now][0][1] * 1.0) / (dp[now][0][0] * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][0][1];
ans2 = dp[now][0][0];
}
Num = (dp[now][1][1] * 1.0) / (dp[now][1][0] * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][1][1];
ans2 = dp[now][1][0];
}
if(id[1][1] != id[0][1]) {
Num = ((dp[now][0][1] * dp[now][1][1]) * 1.0) / ((dp[now][0][0] + dp[now][1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][0][1] * dp[now][1][1];
ans2 = dp[now][0][0] + dp[now][1][0] - 1;
}
}
else {
if(id[1][0] != id[0][1]) {
Num = ((dp[now][0][1] * sec[1][1]) * 1.0) / ((dp[now][0][0] + sec[1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][0][1] * sec[1][1];
ans2 = dp[now][0][0] + sec[1][0] - 1;
}
}
if(id[0][0] != id[1][1]) {
Num = ((sec[0][1] * dp[now][1][1]) * 1.0) / ((sec[0][0] + dp[now][1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = sec[0][1] * dp[now][1][1];
ans2 = sec[0][0] + dp[now][1][0] - 1;
}
}
}
Num = ((dp[now][1][1] * sec[1][1]) * 1.0) / ((dp[now][1][0] + sec[1][0] - 1) * 1.0);
if(Num < ans) {
ans = Num;
ans1 = dp[now][1][1] * sec[1][1];
ans2 = dp[now][1][0] + sec[1][0] - 1;
}
}
void Read() {
ans = INF;
minn = INF;
int A, B;
Quick_Read(n);
for(int i = 1; i < n; i++) {
Quick_Read(A);
Quick_Read(B);
v[A].push_back(B);
v[B].push_back(A);
}
for(int i = 1; i <= n; i++) {
Quick_Read(val[i]);
if(val[i] == 1) {
flag = true;
}
minn = Min(minn, val[i]);
}
for(int i = 1; i <= n; i++) {
dp[i][0][1] = dp[i][1][1] = INF;
if(val[i] == 1) {
dp[i][1][1] = 1;
dp[i][0][1] = 1;
}
else if(val[i] == 2) {
dp[i][1][1] = 2;
dp[i][0][1] = 2;
}
dp[i][0][0] = dp[i][1][0] = 1;
}
}
int main() {
Read();
if(!flag) {
printf("%d/1", minn);
return 0;
}
DP(1, -1);
int gcd = GCD(ans1, ans2);
ans1 /= gcd;
ans2 /= gcd;
printf("%d/%d", ans1, ans2);
return 0;
}

[COCI2016-2017#1] Mag的更多相关文章

  1. 「COCI2016/2017 Contest #2」Bruza

    「COCI2016/2017 Contest #2」Bruza 解题思路 : 首先对于任意时刻 \(i\) ,硬币一定移动到了深度为 \(i\) 的节点,所以第 \(i\) 时刻 Danel 一定染掉 ...

  2. bjwc Day1 暴力大战

    今天终于有题了... 题目是COCI2016/2017 Round #4 T1一看就是NP问题,k<=50,开始想暴力,想了个n^4的,大概能过,就没去管它 T2想得太naive,丢了100分给 ...

  3. CI Weekly #10 | 2017 DevOps 趋势预测

    2016 年的最后几个工作日,我们对 flow.ci Android & iOS 项目做了一些优化与修复: iOS 镜像 cocoapods 版本更新: fir iOS上传插件时间问题修复: ...

  4. 猖獗的假新闻:2017年1月1日起iOS的APP必须使用HTTPS

    一.假新闻如此猖獗 刚才一位老同事 打电话问:我们公司还是用的HTTP,马上就到2017年了,提交AppStore会被拒绝,怎么办? 公司里已经有很多人问过这个问题,回答一下: HTTP还是可以正常提 ...

  5. iOS的ATS配置 - 2017年前ATS规定的适配

    苹果规定 从2017年1月1日起,新提交的 app 不允许使用NSAllowsArbitraryLoads来绕过ATS(全称:App Transport Security)的限制. 以前为了能兼容ht ...

  6. 深入研究Visual studio 2017 RC新特性

    在[Xamarin+Prism开发详解三:Visual studio 2017 RC初体验]中分享了Visual studio 2017RC的大致情况,同时也发现大家对新的Visual Studio很 ...

  7. Xamarin+Prism开发详解三:Visual studio 2017 RC初体验

    Visual studio 2017 RC出来一段时间了,最近有时间就想安装试试,随带分享一下安装使用体验. 1,卸载visual studio 2015 虽然可以同时安装visual studio ...

  8. Microsoft Visual Studio 2017 for Mac Preview 下载+安装+案例Demo

    目录: 0. 前言 1. 在线安装器 2. 安装VS 3. HelloWorld 4. ASP.NET MVC 5. 软件下载 6. 结尾 0. 前言: 工作原因,上下班背着我的雷神,一个月瘦了10斤 ...

  9. Create an offline installation of Visual Studio 2017 RC

    Create an offline installation of Visual Studio 2017 RC ‎2016‎年‎12‎月‎7‎日                             ...

  10. .NET Core 2.0版本预计于2017年春季发布

    英文原文: NET Core 2.0 Planned for Spring 2017 微软项目经理 Immo Landwerth 公布了即将推出的 .NET Core 2.0 版本的细节,该版本预计于 ...

随机推荐

  1. 微信小程序分类的实现

    微信小程序的分类功能思路 实现思路 1.把屏幕当成一个固定的盒子,然后把盒子分成两边,并让盒子的每一边都能够滚动. 2.通过将左侧边栏元素的id和右边内容的categoryId进行匹配,渲染展示相同i ...

  2. 在Linux系统中安装Chrome浏览器

    前言:作为一个Web开发人员,经常与我们相伴的必然少不了浏览器,而Google旗下的chrome浏览器更是凭借着出色的性能.简洁的界面被广大开发者所喜爱,今天分享下如何在linux系统下安装chrom ...

  3. SpringBoot整合Logback日志框架配置全解析

    目录 本篇要点 一.Logback日志框架介绍 二.SpringBoot与Logback 1.默认日志格式 2.控制台输出 3.文件输出 4.日志级别 5.日志组 6.自定义log配置 三.logba ...

  4. 【转】not found while looking for property错误

    原址:http://blog.csdn.net/y3wegy/article/details/7840813 最近在研究Hibernate.过程当中碰到了很多问题啊!其中一个就是not found w ...

  5. Scipy 学习第3篇:数字向量的距离计算

    计算两个数字向量u和v之间的距离函数 1,欧氏距离(Euclidean distance) 在数学中,欧几里得距离或欧几里得度量是欧几里得空间中两点间"普通"(即直线)距离.使用这 ...

  6. 傲视Kubernetes(一):Kubernetes简介

    前言 从上个月,因工作需要外加兴趣所知,博主开始学习Kubernetes,时至今日可以说是刚刚入门.独自学不如一起学,后面博主会一边学着一边将学习内容以博文的形式呈现出来,希望能跟各位园友有问题一起讨 ...

  7. php ci下添加一个创建常用的模块和控制器方法

    我这么写是非常不好的 ,这些都可以写在lirbraries里面 (ci就是这么干的) 我这里是自己用 大概一个模型 没那么多讲究 现在core/CodeIgniter.php 文件 if($modle ...

  8. 基于YOLO-V2的行人检测(自训练)附pytorch安装方法

    声明:本文是别人发表在github上的项目,并非个人原创,因为那个项目直接下载后出现了一些版本不兼容的问题,故写此文帮助解决.(本人争取在今年有空的时间,自己实现基于YOLO-V4的行人检测) 项目链 ...

  9. leetcode104:permutations

    题目描述 给出一组数字,返回该组数字的所有排列 例如: [1,2,3]的所有排列如下 [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].  (以数字在数 ...

  10. hi-nginx-java并发性能一窥

    欲知hi-nginx-java的并发性能,用jmeter进行测试便知一二. 设定用户数为100000,循环次数为100,ramp-up perio为2: 请求地址为http://localhost/t ...