百果之王(Kinggo.cpp)
【要求】
时间<1s
内存<256MB
【题目描述】
在一棵银杏树上有许多摇摇欲坠的 n n n 颗白果王,每个白果的初始质量都为 w w w,而且很巧的是这棵银杏树正好是一棵树。白果们日以继夜地生长,并会在质量到达一定限度 m m m ( w > m w > m w>m)时从树上落下。每个果子 i i i 所发之臭都会使部分白果加速生长。请你求出在从 1 1 1~ n n n颗白果的香味都发生作用后有多少白果从树上落下。
白果的香味作用有两种:
o p t = 1 opt = 1 opt=1:使 ( u , v ) (u,v) (u,v)的简单路径上的白果(包括 u , v u,v u,v)质量增加 x x x。
o p t = 2 opt = 2 opt=2:使 ( u , v ) (u,v) (u,v)的简单路径上的白果(包括 u u u, v v v)质量变为 x x x倍。
【输入格式】
从文件: k i n g g o . i n kinggo.in kinggo.in读入数据
第一行两个整数 n , w , m n,w,m n,w,m表示树的白果的数量以及所有白果的初始质量和质量上限。
第 2 2 2行到第 n n n行,每行输入两个 u , v u,v u,v,表示两个节点相连(整个图是连通图)
第 n + 1 n + 1 n+1行到第 2 n 2n 2n行 第 n + i n + i n+i行输入四个数 o p t , u , v , x opt , u ,v, x opt,u,v,x分别表示第 i i i个白果会进行的操作类型,操作的路径的起点和终点,以及要加或乘上的数。
这 n n n个操作依次进行(从 1 → 1\rightarrow 1→n)。
【输出格式】
输出数据到文件: k i n g g o . o u t kinggo.out kinggo.out
一行 n n n个数,每个数之间有空格.
如果第 i i i个白果掉下来,那么输出的第 i i i个数为 1 1 1;否则输出第 i i i个白果的最终质量.
【数据范围】
对于“100%”的数据都满足:
1 ≤ n ≤ 1 × 1 0 5 . 1 ≤ u , v ≤ n . o p t ∈ 1\leq n \leq1 \times 10^5.\;1 \leq u,v\leq n.\;opt\in \; 1≤n≤1×105.1≤u,v≤n.opt∈{ 1 , 2 1,2 1,2}且** o p t = 2 opt=2 opt=2的情况不超过 10 10 10次** . 1 ≤ x ≤ 10. 1 ≤ w ≤ m ≤ 2 × 1 0 5 \\1 \leq x \leq 10.\; 1\leq w \leq m\leq 2\times 10^5 1≤x≤10.1≤w≤m≤2×105
你说得对,但是:
有20%满足 1 ≤ n ≤ 5 × 1 0 3 1\leq n \leq 5\times 10^3 1≤n≤5×103
另外30% 满足所有白果的 o p t = 1 opt = 1 opt=1
以及剩下的50%满足另外50%满足的条件。
保证:
保证白果的香味不会引发池沼爆炸,也不会使任意一个白果的质量超过 2 × 1 0 9 2\times 10^9 2×109
【样例1输入】
3 3 7
1 2
2 3
1 2 3 2
2 1 1 2
1 1 3 2
【样例1输出】
1 7 7
【样例1解释】
所有果子初始质量为3,会在质量大于7时掉落。
1号果子会使2号果子到3号果子上所有点的质量增加2,2号质量变为5,3号质量变为5
2号使1号节点的质量乘2变为6
3号节点使1,2,3号节点质量都加2,最后:
w 1 = 8 , w 2 = 7 , w 3 = 7 w_1 = 8,w_2 = 7,w_3 = 7 w1=8,w2=7,w3=7
1号果子掉落输出1.而2号3号果子剩下,质量为7.
K i n g g o Kinggo Kinggo题解
本题前言:
本题可抽象为:对树上带点群的简单修改问题。
即对任意两个点形成的简单路径上的点进行加和乘的修改。由于我们只要求得出最后的结果,所以本题可以算是离线操作。
考虑 n n n的范围 5 × 1 0 5 5\times 10^5 5×105,容易想到 O ( n l o g n ) O(nlogn) O(nlogn)做法,但是这道题要用此种做法的话需要用到树链剖分,感兴趣的同学可以自己去了解一下,这里讲的是适用于本题的比较简单的做法——树上差分。
基础知识:差分
常见差分:1 2 3 4 5,对[1,3]进行+2处理,可在端点1处使差分值+2,右端点3的后一点4处使差分值-2
然后得到差分数组:2 0 0 -2 0,从左到右求前缀和,你会发现最后得到的数组就是所有数的变化值
即 Δ \Delta Δ = 2 2 2 0 0。多次操作也是同理。
↓ \downarrow ↓
树上差分:我们使两个端点差分值 + v a l +val +val, 使他们的 最近公共祖先 l c a lca lca及其父节点 f a [ l c a ] [ 0 ] fa[lca][0] fa[lca][0]的差分值 − v a l -val −val, 然后从叶子节点遍历上去求子树权值和即可得到整棵树的变化值。
差分的好处在于一次处理的复杂度= O ( 1 ) O(1) O(1)。可以多次操作后(可以发现结果是不变的),在最后的时候再更新整棵树——只需要一次 O ( n ) O(n) O(n)处理即可。而一般的处理就是 O ( n 2 ) O(n^2) O(n2),复杂度很劣。
思路点拨:
首先考虑比较简单的加法,而之所以说其简单是因为:
我们如果站在加法的意义上考虑变化值的话,会发现加法可以忽视具体的值而对一个区间进行等同效果(在加法意义上的变化)的处理。而在加法的基础上纳入乘法则需要考虑到点本身的值,如节点权值为3,乘5后的变化值(
Δ
\Delta
Δ=15-3=12)与3本身有关,不好进行区间处理,因为区间内的数权值可能不同。
回到加法本身上来,由于其变化值在加法意义上不与权值本身相关,我们便能想到用比较常用的差分知识,进行懒标记处理。
接下来考虑乘法,我们发现乘法的话用差分难以实现,只好从头到尾遍历一次再暴力更新乘法区间。之所以考虑暴力是因为观察到乘法的次数很少。
时间复杂度 T = O ( 10 × n ) T = O(10 \times n) T=O(10×n)(10其实可以忽略不计)
一点拓展:
如果乘法和加法的次数没有限制的话,本题便基本上只能用一开始提到的树链剖分完成
同理,我们如果站在乘法的意义上考虑变化值,此时变化值就是倍数差异:
那么乘法就可以进行差分操作:我们使两个端点差分值 ∗ v a l *val ∗val,使他们的最近公共祖先 l c a lca lca及其父节点 f a [ l c a ] [ 0 ] fa[lca][0] fa[lca][0]的差分值 ÷ v a l \div val ÷val,接下来就和加法差分很类似了,但还是考虑到本题乘法操作偏少,且如果用乘法意义的话,加法会使倍数变为分数: 3 + 2 = 3 × 5 3 3 + 2 = 3 \times \frac{5}{3} 3+2=3×35,所以我们本题还是要运用加法意义上的差分。
【代码细节】
其他细节见参考代码 k i n g g o . c p p kinggo.cpp kinggo.cpp的注释
先预处理出每个节点的任意级祖先。
然后求出节点的 l c a —— f f lca——ff lca——ff
接着分读入类型进行两种操作:
o p t = 1 opt = 1 opt=1,进行差分数组的处理
del[x] += val,del[y] += val;//在x,y节点上加上权值,在其lca和lca的父节点上减去相同权值
del[ff] -= val,del[fa[ff][0]] -= val;//lca及其父节点的差分值-val
o p t = 2 opt =2 opt=2,将差分数组转移到原树的数据上进行修改,然后再暴力修改乘法部分
void dfs2(int u)//往下遍历
{
for(int e = fs[u];e;e = nex[e])
{
int v = to[e];if(v == fa[u][0]) continue;
dfs2(v);
del[u] += del[v];//从子节点传上来差分值
del[v] = 0;
}
w[u] += del[u];
}
void dfs3(int u)//暴力更新乘法
{
if(dep[u] <= dep[ff]) return;//到达lca后返回
w[u] *= val;
if(fa[u][0]) dfs3(fa[u][0]);
}
........
dfs2(1),dfs3(x),dfs3(y),w[ff] *= val;
最后记得再遍历一次将差分数组转移到原树上修改即可。
另外本题实际数据其实较小,如果考虑到可能超
i
n
t
int
int,要开
l
o
n
g
l
o
n
g
longlong
longlong。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n,fs[N],nex[N<<1],to[N<<1],cnt,w[N],m,del[N],mul[N];//del[]记录差分的值
int x,y,dep[N],fa[N][24],son[N],opt,val,ff;//son[]用来找叶子结点
void add(int x,int y){nex[++cnt]=fs[x];fs[x]=cnt;to[cnt]=y;}
void dfs(int u)//求每个点的深度和父节点
{
for(int e = fs[u];e;e = nex[e])
{
int v = to[e];if(v == fa[u][0]) continue;
dep[v] = dep[u] + 1,fa[v][0] = u,son[u] = v;
dfs(v);
}
}
int lca(int u,int v) //找lCA
{
if(u == v) return u;
if(dep[u] < dep[v]) swap(u,v); //保证u深度更大
for(int i = 22;i >= 0;i--) //使它们俩跳至深度相同
{
if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if(u == v) return u; //u、v在一条链上,v恰是u和v的最近公共祖先
}
for(int i=22;i>=0;i--) //在u和v深度相同的情况下
{
if(fa[u][i] != fa[v][i]) //目标位置不相等,u和v就往上跳
{
u = fa[u][i]; //u往上跳
v = fa[v][i]; //v往上跳
}
}
return fa[u][0]; //最后肯定一起跳到了lca的下面一个
}
void dfs2(int u)//往下遍历
{
for(int e = fs[u];e;e = nex[e])
{
int v = to[e];if(v == fa[u][0]) continue;
dfs2(v);
del[u] += del[v];//从子节点传上来差分值
del[v] = 0;
}
w[u] += del[u];
}
void dfs3(int u)//暴力更新乘法
{
if(dep[u] <= dep[ff]) return;//到达lca后返回
w[u] *= val;
if(fa[u][0]) dfs3(fa[u][0]);
}
int main()
{
freopen("kinggo.in","r",stdin);
freopen("kinggo.out","w",stdout);
scanf("%d%d%d",&n,&x,&m);
for(int i = 1;i <= n; i++) w[i] += x;
for(int i = 1;i < n; i++)
{
scanf("%d%d",&x,&y);add(x,y),add(y,x);//加边
}
dfs(1);
for(int j = 1;j <= 22; j++)
for(int i = 1;i <= n; i++)
fa[i][j] = fa[fa[i][j-1]][j-1];//倍增找祖先
dep[0] = -1;
for(int i = 1;i <= n; i++)
{
scanf("%d%d%d%d",&opt,&x,&y,&val);ff = lca(x,y);
if(opt == 1)
{
del[x] += val,del[y] += val;//在x,y节点上加上权值,在其lca和lca的父节点上减去相同权值
del[ff] -= val,del[fa[ff][0]] -= val;//lca及其父节点的差分值-val
}
else
{
dfs2(1),dfs3(x),dfs3(y),w[ff] *= val;//为乘法,只能遍历整个树进行更新后再暴力做乘法运算
}
}
x = y = 0;
dfs2(1);//最后一次更新差分值
for(int i = 1;i <= n; i++)
{
if(w[i] > m) cout << 1 << ' ';
else cout << w[i]<< ' ';
}
return 0;
}
洛谷P2680 运输计划
参考代码:
//16.22-16.35
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
int fs[N],cnt,nex[N<<1],to[N<<1],w[N<<1],top[N],fa[N],son[N],sz[N],dep[N],del[N],dis[N];
void add(int x,int y,int z){nex[++cnt]=fs[x];fs[x]=cnt;to[cnt]=y;w[cnt]=z;}
struct line{
int x,y,dis;
}l[N];
int n,m,mm,ml;
int tot,num = 0;
void dfs1(int u)
{
sz[u] = 1;
for(int e = fs[u];e;e = nex[e])
{
int v = to[e];if(v == fa[u]) continue;
dep[v] = dep[u] + 1,fa[v]=u,dis[v] = dis[u] + w[e],dfs1(v);
sz[u] += sz[v];
if(sz[son[u]] <sz[v]) son[u] = v;
}
}
void dfs2(int u)
{
top[u] = son[fa[u]] == u? top[fa[u]] : u;
for(int e = fs[u];e;e = nex[e])
{
int v = to[e];if(v == fa[u]) continue;
dfs2(v);
}
}
void dfs3(int u)
{
for(int e = fs[u];e;e = nex[e])
{
int v = to[e];if(v == fa[u]) continue;
dfs3(v),del[u] += del[v];
}
}
int lca(int x,int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) y = fa[top[y]];
else x = fa[top[x]];//用if else 更好
}
return dep[x] < dep[y] ? x : y;
}
int time(int x,int y)
{
return dis[x] + dis[y] - (dis[lca(x,y)] << 1);
}
bool check(int x)
{
num = 0;int hh = 0;
for(int i = 1;i <= n; i++) del[i] = 0;
for(int i = 1;i <= m; i++)
{
if(l[i].dis > x)
{
num++,del[l[i].x] ++,del[l[i].y] ++ ,del[lca(l[i].x,l[i].y)]-=2;
}
hh = max(hh,l[i].dis - x);
}
if(!num) return 1;
dfs3(1);
for(int i = 2;i <= n; i++)
{
if(del[i] == num and dis[i] - dis[fa[i]] >= hh) return 1;//注意后一个条件
}
return 0;
}
void bisect()
{
int l = mm - ml,r = mm,mid;//左边界为最大距离减最大边,右边界为最大距离
while(l < r)
{
mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l;
}
int main()
{
scanf("%d%d",&n,&m);
int x,y,z;
for(int i = 1;i < n; i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
ml = max(ml,z);
}
dfs1(1),dfs2(1);
for(int i = 1;i <= m; i++)
{
scanf("%d%d",&x,&y);
int d = time(x,y);
mm = max(d,mm);
l[++tot] = line{x,y,d};
}
bisect();
}