\(2019\)计蒜客信息学提高组赛前膜你赛 #5
不知道打这场比赛的时候我怎么崩成这个亚子……
另外,这场比赛的情景太棒了,我这就去追番(逃
如果觉得\(up\) \(and\) \(down\)讲的不太清楚的话这里还有另一种讲的思路
\(T2\) 桜の花が咲く顷に 樱花盛开之时
呐,\(Darling\),现在也很努力吗。
在吃饭之前,我们散个步好吗。
嗯?这种花朵,是叫樱花吗?
呐,将来会怎样绽放呢?
嗯!它会像我的头发一样,变的美丽吗。
诶嘿嘿,好期待呢。
呐,\(Darling\),到时候一起来看吧。 嗯,\(Darling\),我们约定好了呦。
——\(02\)
\(Sakura\) 盛开了。
有一棵有 \(n\) 个节点的樱花树,边有边权。
樱花树的每个节点上都有一朵 \(Sakura\),定义节点 \(i\) 上的 \(Sakura\) 的好看程度为:\(\sum\limits_{cnt(i,j)\le k}dis(i,j)\)
其中 \(k\) 是给定常数,\(cnt(i,j)\) 为点 \(i\) 到点 \(j\) 的路径经过的边数,\(dis(i,j)\) 为点 \(i\) 到点 \(j\) 的路径经过的边权之和。
请你求出 \(1\sim n\) 每个节点上的 \(Sakura\) 的好看程度。
输入格式
第一行,两个正整数,\(n\) 和 \(k\)。 接下来 \(n-1\) 行描述樱花树,每行三个正整数 \(u,v\) 和 \(w\),表示 \(u\) 和 \(v\) 之间连了一条权值为 \(w\) 的边。
输出格式
一行,\(n\) 个数 \(1 \sim n\) 每个节点的 \(Sakura\) 的好看程度,用空格隔开。
数据范围
对于 \(20\%\) 的数据,树是一条链,边权 \(\leq 1000000000\)
对于另外 30%30% 的数据,\(n \leq 5000,k \leq 10\), 边权 \(\leq 1000000000\)
对于另外 \(20\%\) 的数据,是一张菊花图
对于 \(100\%\) 的数据,\(n \leq 300000,k \leq 20\), 边权 \(\leq 1000000000\)
数据全部为随机生成
题解
我还专门抓了图片来嘛-w-
整理整理思路,这题求的是每条路径上的边的权值之和,套路性的转化一下思路,我们求每条边对于每个点的贡献,我太弱了说不清楚,还是画个图吧
这是原图,比如说我们要求以\(1\)为根的深度为\(3\)的子树的贡献,那怎么考虑呢
我们先看边\(edge(1 , 2)\),它的贡献是多少呢?
我们发现它的贡献就是它被走了多少次,而它被走的次数就是以\(2\)为根的深度为\(3 - 1\)的子树的大小,可以看到,这个以\(2\)为根的子树包括了点集\({2,8,9,10,11,12,13}\),这个集合里的每个点走到\(1\),都要经过边\(edge(1,2)\)
那么我们需要知道以每个点为根的子树大小,如果我们以\(1\)为根\(dfs\)一次,那么其他节点会有信息损失,比如\(2\),我们统计\(siz[2][k]\) ( \(k\)是树的深度 ) 会统计不到父亲上的信息,也就是\(siz[1][k - 1]\)会被遗漏。
但是如果我们以每个点为根\(dfs\),会\(T\)飞的。
那怎么办呢?
其实刚刚已经暗示了,既然我们以\(1\)为根\(dfs\)会遗漏信息,那我们把遗漏的信息加回来就好了。
具体处理方法
我们把信息有所遗漏的叫做“临时”,用\(siz\)数组保存,比如我们上面的\(siz[2][k]\)就是临时的
信息全面的叫做“最终”,用\(sizans\)数组保存
我们在第一次\(dfs\)的时候可以计算出\(siz\)数组,这个没有难度,如果真的不明白,宁可以康康代码,但是这也是一种警示,因为这个形式的\(dp\)实在是太基础了
for( rint i = 0; i <= k; i++ ) siz[now][i] = 1;
for( rint i = head[now]; i; i = a[i].nxt ){
int v = a[i].to, w = a[i].val;
if( v == fa ) continue ;
dfsdown( v, now );
for( rint j = 1; j <= k; j++ ){
siz[now][j] += siz[v][j - 1];
}
}
然后我们要求出\(sizans\)数组,就需要把遗漏的信息加回来, 也就是要把父亲产生的贡献加回来。
怎么加呢,我们可以发现,如果第一次我们以\(1\)为根\(dfs\)的话,那\(1\)的所有子孙都会被统计到,\(1\)并不会发生信息遗漏的情况,因为它没有祖先,那我们就考虑从信息被更新为最终信息的父节点向当前节点转移(也就是用\(sizans[fa][i - 1]\)来更新\(sizans[now][i]\))。
但是这么更新的话我们会发现一些问题,那就是有些东西被多加了一次,很容易发现是\(siz[now][i - 2]\)被多加了,那我们再减去它就是最终的\(sizans[now][i]\)
sizans[now][0] = 1; sizans[now][1] = siz[now][1] + sizans[fa][0];
for( rint i = 2; i <= k; i++ ){
sizans[now][i] = ( siz[now][i] + sizans[fa][i - 1] - siz[now][i - 2] );
}
现在,我们要求解了,但是此时有了一个问题,我们知道了每个点的子(父)节点的大小,可以算出每条与这个点相连的边的贡献,但是不和这个点相连,却在这棵子树里的其他边怎么办呢,没办法,只能从子节点转移
也就是说,在第一二次\(dfs\)的时候我们还需要再加两个数组,去维护另一组数据,而这组数据需要由\(siz\)和\(sizans\)求出
现在,我们来考虑这组数据该怎么求,把这组临时数据用\(dpans\)保存,最终数据用\(dpans2\)保存
我们发现每个点只能算出它周边和它相连的边的贡献,如果直接用\(sizans\)来求\(dpans2\)的话,那么会有极其多的边被重复计算,造成不必要的麻烦,所以我们选择在第一次\(dfs\)的时候求出临时的\(dpans\),它没有统计父亲的信息,所以我们再把父亲的信息加回来
第一次\(dfs\)完整代码
inline void dfsdown( int now, int fa ){
for( rint i = 0; i <= k; i++ ) siz[now][i] = 1;
for( rint i = head[now]; i; i = a[i].nxt ){
int v = a[i].to, w = a[i].val;
if( v == fa ) continue ;
dfsdown( v, now );
for( rint j = 1; j <= k; j++ ){
siz[now][j] += siz[v][j - 1];
dpans[now][j] += ( w * siz[v][j - 1] + dpans[v][j - 1] );
}
}
}
可是似乎再加父亲的信息时我们遇到一些问题,父亲\(sizans[fa][i - 1]\)连着的其他边似乎都是正确的,唯独多加了\(dpans[now][i - 2]\)的信息和加了错误的边\(edge(now,fa)\),\(dpans[now][i - 2]\)减去即可,可是那条边呢?
遇到问题,我们可以思考这个问题到底哪里有难度:
想减去\(edge(now,fa)\),我们需要知道\(siz[now][i - 2]\)和这条边的边权
我们发现,这条边难以处理是因为我们不知道它的边权
那么只要在第二次\(dfs\)递归的时候,我们把边权也传下来即可……
inline void dfsup( int now, int fa, int length ){
sizans[now][0] = 1; sizans[now][1] = siz[now][1] + sizans[fa][0];
dpans2[now][0] = 0; dpans2[now][1] = length + dpans[now][1];
for( rint i = 2; i <= k; i++ ){
sizans[now][i] = ( siz[now][i] + sizans[fa][i - 1] - siz[now][i - 2] );
dpans2[now][i] = dpans[now][i] + ( dpans2[fa][i - 1] - dpans[now][i - 2] - length * siz[now][i - 2] + length * ( sizans[fa][i - 1] - siz[now][i - 2] ) );
}
for( rint i = head[now]; i; i = a[i].nxt ){
int v = a[i].to, w = a[i].val;
if( v == fa ) continue ;
dfsup( v, now, w );
}
}
相对上一道题而言简单许多,我一遍过了\(-w-\)
完整代码
#include
using namespace std;
#define rint register int
#define ll long long
ll n, k, cnt;
ll head[300010], sizans[300010][21], dpans[300010][21], siz[300010][21], dpans2[300010][21];
struct edge{
int nxt, to, val;
}a[600010];
inline int read( void ){
int re = 0, f = 1; char ch = getchar();
while( ch > '9' || ch < '0' ){
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' ){
re = re * 10 + ch - '0';
ch = getchar();
}
return re * f;
}
inline void addedge( int x, int y, int z ){
a[++cnt].nxt = head[x];
a[cnt].to = y;
a[cnt].val = z;
head[x] = cnt;
}
inline void dfsdown( int now, int fa ){
for( rint i = 0; i <= k; i++ ) siz[now][i] = 1;
for( rint i = head[now]; i; i = a[i].nxt ){
int v = a[i].to, w = a[i].val;
if( v == fa ) continue ;
dfsdown( v, now );
for( rint j = 1; j <= k; j++ ){
siz[now][j] += siz[v][j - 1];
dpans[now][j] += ( w * siz[v][j - 1] + dpans[v][j - 1] );
}
}
}
inline void dfsup( int now, int fa, int length ){
sizans[now][0] = 1; sizans[now][1] = siz[now][1] + sizans[fa][0];
dpans2[now][0] = 0; dpans2[now][1] = length + dpans[now][1];
for( rint i = 2; i <= k; i++ ){
sizans[now][i] = ( siz[now][i] + sizans[fa][i - 1] - siz[now][i - 2] );
dpans2[now][i] = dpans[now][i] + ( dpans2[fa][i - 1] - dpans[now][i - 2] - length * siz[now][i - 2] + length * ( sizans[fa][i - 1] - siz[now][i - 2] ) );
}
for( rint i = head[now]; i; i = a[i].nxt ){
int v = a[i].to, w = a[i].val;
if( v == fa ) continue ;
dfsup( v, now, w );
}
}
int main( void ){
n = read(); k = read();
for( rint i = 1; i <= n - 1; i++ ){
int u, v, w; u = read(); v = read(); w = read();
addedge( u, v, w ); addedge( v, u, w );
}
dfsdown( 1, 1 );
for( rint i = 0; i <= k; i++ ){
sizans[1][i] = siz[1][i];
dpans2[1][i] = dpans[1][i];
}
for( rint i = head[1]; i; i = a[i].nxt ) dfsup( a[i].to, 1, a[i].val );
for( rint i = 1; i <= n; i++ ){
for( rint j = 0; j <= k; j++ ) cout << dpans2[i][j] << ' ';
cout << endl;
}
return 0;
}