P3237 [HNOI2014]米特运输
第104道题让我洛谷红名。
(^ w ^)
题目描述
米特是D星球上一种非常神秘的物质,蕴含着巨大的能量。在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题。
D星上有N个城市,我们将其顺序编号为1到N,1号城市为首都。这N个城市由N-1条单向高速通道连接起来,构成一棵以1号城市(首部)为根的树,高速通道的方向由树中的儿子指向父亲。树按深度分层:根结点深度为0,属于第1层;根结点的子节点深度为1,属于第2层;依此类推,深度为i的结点属于第i+l层。
建好高速通道之后,D星人开始考虑如何具体地储存和传输米特资源。由于发展程度不同,每个城市储存米特的能力不尽相同,其中第i个城市建有一个容量为A[i]的米特储存器。这个米特储存器除了具有储存的功能,还具有自动收集米特的能力。
如果到了晚上六点,有某个储存器处于未满的状态,它就会自动收集大气中蕴含的米特能源,在早上六点之前就能收集满;但是,只有在储存器完全空的状态下启动自动收集程序才是安全的,未满而又非空时启动可能有安全隐患。
早上六点到七点间,根节点城市(1号城市)会将其储存器里的米特消耗殆尽。根节点不会自动搜集米特,它只接受子节点传输来的米特。
早上七点,城市之间启动米特传输过程,传输过程逐层递进:先是第2层节点城市向第1层(根节点城市,即1号城市)传输,直到第1层的储存器满或第2层的储存器全为空;然后是第3层向第2层传输,直到对于第2层的每个节点,其储存器满或其予节点(位于第3层)的储存器全为空;依此类推,直到最后一层传输完成。传输过程一定会在晚上六点前完成。
由于技术原因,运输方案需要满足以下条件:
-
不能让某个储存器到了晚上六点传输结束时还处于非空但又未满的状态,这个时候储存器仍然会启动自动收集米特的程序,而给已经储存有米特的储存器启动收集程序可能导致危险,也就是说要让储存器到了晚上六点时要么空要么满;
-
关于首都——即1号城市的特殊情况, 每天早上六点到七点间1号城市中的米特储存器里的米特会自动被消耗殆尽,即运输方案不需要考虑首都的米特怎么运走;
-
除了1号城市,每个节点必须在其子节点城市向它运输米特之前将这座城市的米特储存器中原本存有的米特全部运出去给父节点,不允许储存器中残存的米特与外来的米特发生混合;
-
运向某一个城市的若干个来源的米特数量必须完全相同,不然,这些来源不同的米特按不同比例混合之后可能发生危险。
现在D星人已经建立好高速通道,每个城市也有了一定储存容量的米特储存器。为了满足上面的限制条件,可能需要重建一些城市中的米特储存器。你可以,也只能,将某一座城市(包括首都)中原来存在的米特储存器摧毁,再新建一座任意容量的新的米特储存器,其容量可以是小数(在输入数据中,储存器原始容量是正整数,但重建后可以是小数),不能是负数或零,使得需要被重建的米特储存器的数目尽量少。
输入输出格式
输入格式:
第一行是一个正整数N,表示城市的数目。
接下来N行,每行一个正整数,其中的第i行表示第i个城市原来存在的米特储存器的容量。
再接下来是N-I行,每行两个正整数a,b表示城市b到城市a有一条高速通道(a≠b)。
输出格式:
输出文件仅包含一行,一个整数,表示最少的被重建(即修改储存器容量)的米特储存器的数目。
输入输出样例
输入样例#1:
5
5
4
3
2
1
1 2
1 3
2 4
2 5
输出样例#1:
3
题解
感觉是道难题,我想了好久。
diss这种卡题面的出题人
50分算法
这应该是唯一的暴力吧
先设一下变量名:
名称 | 意义 |
---|---|
f a i \ fa_{i} fai | i \ i i的父节点 |
v i \ v_{i} vi | i \ i i的容量 |
f i \ f_{i} fi | i \ i i节点不变情况下的改变节点数量的最小值 |
S i \ S_{i} Si | i \ i i节点子节点的集合 |
首先我们理解题意:
-
一棵树,满足以下条件
- v i = ∑ x ∈ S i v x \ v_{i}=\sum_{x\in S_{i}}v_{x} vi=∑x∈Sivx
- v x = v i ∣ S i ∣          , x ∈ S i \ v_{x}=\frac{v_{i}}{|S_{i}|}\,\,\,\,\,\,\,\,,x\in S_{i} vx=∣Si∣vi,x∈Si
求将一棵树修改节点使之满足以上性质的最小修改数。
我们稍加思索就能得出当一个节点的权值确定的时候,整棵树就是完全确定的。那么就有一个暴力的想法。
枚举每一个节点不变的情况然后找最小值。这是容易操作的。代码是容易实现的。
AC算法
现在再定义几个变量。
名称 | 意义 |
---|---|
g i \ g_{i} gi | 再 v i \ v_{i} vi不变的情况下,根节点的权值 |
l i \ l_{i} li | g i \ g_{i} gi是 v i \ v_{i} vi的倍数 |
C i \ C_{i} Ci | i \ i i节点的父亲节点与根节点路径上的点的集合 |
由于根节点权值确定的话整棵树都确定,所以这样就把确认一个节点值的情况转化为确认根节点的值的情况。
我们也可以得到以下结论,
设
T
i
\ T_{i}
Ti表示
i
\ i
i点不变的情况下树的点集与边集
可有以下结论
g i = g j ⇔ T i = T j g_{i}=g_{j} \Leftrightarrow T_{i}=T_{j} gi=gj⇔Ti=Tj
那么我们可以求出所有的
g
i
\ g_{i}
gi,然后比较
g
i
\ g_{i}
gi的值,若相等,则为同一种情况。
而且有
g i = l i ⋅ v i g_{i}=l_{i} \cdot v_{i} gi=li⋅vi
l i = Π x ∈ C i ∣ S i ∣ l_{i}=\Pi_{x \in C_{i}}|S_{i}| li=Πx∈Ci∣Si∣
由此可见,
l
i
\ l_{i}
li可以在递归中求出。
随后统计
g
i
\ g_{i}
gi所有情况的个数。答案为相同情况的个数的最大值与
n
\ n
n的差。
你以为你AC了吗?
还没完!!!
由于
l
i
\ l_{i}
li可能爆long long,可能连50分都不到。
那么我们需要一个强大的优化。
l
i
,
g
i
\ l_{i},g_{i}
li,gi都是乘积,所以可以用对数优化一下。
g i = l i + l o g   v i g_{i}=l_{i}+log\,v_{i} gi=li+logvi
l i = ∑ x ∈ C i l o g   ∣ S i ∣ l_{i}=\sum_{x \in C_{i}}log\,|S_{i}| li=x∈Ci∑log∣Si∣
到这里就可以AC了。
AC代码
#include<bits/stdc++.h>
using namespace std;
double v[500100],g[500100];
vector<int> ee[500100];
long long n,cnt=1,ans=1;
void dfs(int x,double lo)
{
g[x]=lo+log(v[x]);
int i=0,siz=ee[x].size();
while(i<siz)
{
int y=ee[x][i];
dfs(y,lo+log((double)siz));
++i;
}
}
int main()
{
scanf("%lld",&n);
int i=1;
while(i<=n)
{
int aa;
scanf("%d",&aa);
v[i]=aa;
++i;
}
i=1;
while(i<n)
{
int x,y;
scanf("%d%d",&x,&y);
ee[x].push_back(y);
++i;
}
dfs(1,log(1.0));
sort(g+1,g+n+1);
i=1;
while(i<=n)
{
if(g[i]-g[i-1]<=1e-8)
{
++cnt;
ans=max(ans,cnt);
}
else cnt=1;
++i;
}
printf("%lld\n",n-ans);
return 0;
}
/*
1996年:东方灵异传(TOH1)
1997年:东方封魔录(TOH2)
1997年:东方梦时空(TOH3)
1998年:东方幻想乡(TOH4)
1998年:东方怪绮谈(TOH5)
2002年:东方红魔乡(TOH6)
2003年:东方妖妖梦(TOH7)
2004年:东方萃梦想(TOH7.5)
2004年:东方永夜抄(TOH8)
2005年:东方花映冢(TOH9)
2005年:东方文花帖(TOH9.5)
2007年:东方风神录(TOH10)
2008年:东方绯想天(TOH10.5)
2008年:东方地灵殿(TOH11)
2009年:东方星莲船(TOH12)
2009年:东方非想天则(TOH12.3)
2010年:东方文花帖DS(TOH12.5)
2010年:东方三月精(TOH12.8)
2011年:东方神灵庙(TOH13)
2013年:东方心绮楼(TOH13.5)
2013年:东方辉针城(TOH14)
2014年:弹幕天邪鬼(TOH14.3)
2014年:东方深秘录(TOH14.5)
2015年:东方绀珠传(TOH15)
2017年:东方凭依华(TOH15.5)
2017年:东方天空璋(TOH16)
*/
//写这些东西++RP
总结
题面可以卡人半小时。。。
其实50分代码是很好像的,但作对不是很容易。当然也不是很难。运气好就秒A,像我这样不好的话就。。。