CF516D. Drazil and Morning Exercise
比较简单的题,不过有很多有趣的东西。
题目大意是说,一棵树 \(n\) 个点,边有边权(\(1\le v \le 10^6\),为正整数),然后定义 \(f_i\) 表示 \(i\) 到所有叶子结点(定义为度数为 \(1\) 的点)的最大值。
然后要选出一个最大的联通快,使得 \(max f_i -min f_i \le l\),\(q\) 次询问 \(q\le50\)。
一个显然的,我们可以换根 dp 求出 \(f_I\),然后既然给了 \(l\),我们考虑枚举最小值,然后动态维护(具体的就是枚举 \(i\),然后看 \(i\le f_j \le i+l\) 的点构成的联通快最大为多少),然后套上 lct 应该就可以做到 \(nq\log\),感觉不太好过的样子。
这个 f 的计算方式其实明眼人看着就知道很蹊跷吧,考虑看一下 \(f_i\) 的取值,接下来也许会说一些无用的话,随便看看就行了,定义 \(dis_{i,j}\) 表示 \(i,j\) 的最短距离。
对于 \(f_i\) 来讲,若其父亲(假设有)选的点不是 \(i\) 子树类的,那么显然 \(i\) 也会取那个点(因为对于 \(fa_i\) 来讲,不取说明 \(dis_{i,fa_i}+dis_{i,son_i} \le dis_{fa_i,k}\),那么对于 \(i\) 来讲就是 \(dis_{i,son_i}\) 与 \(dis_{fa_i,k}+dis_{i,fa_i}\),显然后者更好)。
所以对于根节点跑出来最优点 \(k\) 之后,我们直接前往 \(root\) 到 \(k\) 这条链去看。
如果 \(i\) 也是到 \(k\) 最优,那么继续去看,其它子树都这么赋。
否则,\(i\) 一定是到一个子树外更优的,不然 \(k\) 就会变。那么 \(i\) 子树里面都是到那个点了那么结束。
综上,所有点的只有可能到两个点中的一个去(其实就是直径的两个端点)。
嗯,然后我们可以直接去求重心(即 \(f_i\) 最小的点),然后以重心为更节点,容易发现,要么所有点都会到一个叶子去,要么重心周围的点恰好有一个是与重心到的点是不一样的。
很有趣对吧,然后这么跑出来就显然会有 \(f_{fa_i} \le f_I\) 了(不然凭啥 \(i\) 不当重心,这不纯纯黑幕)。
所以是一个小根堆,我们从大到小枚举最大值,然后删最大值(记得删的时候优先删深度更大的),直接并查集维护即可,复杂度 \(nq*c\),\(c\) 是路径压缩那个数,不会打(
也许有点关联的题:
P3345 [ZJOI2015] 幻想乡战略游戏
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
template<typename T>
void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
template<typename T,typename... Args>
void read(T &_x,Args&...others){Read(_x);Read(others...);}
const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
#define pc(x) buf[plen++]=x
#define flush(); fwrite(buf,1,plen,stdout),plen=0;
template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 1e5+10;
int n,q,x,y,z,head[N],cnt,F[N],f[N],fa[N],g[N],siz[N],r,dep[N],o,ly,ans;
//f_i表示子树到i的最大值,g_i是全局
struct w
{
int to,nxt,z;
}b[N<<1];
struct w1
{
int z,x;
}a[N];
inline void add(int x,int y,int z)
{
b[++cnt].nxt = head[x];
b[cnt].to = y; b[cnt].z = z;
head[x] = cnt;
}
void dfs(int x,int y)
{
siz[x] = 1;
for(int i = head[x];i;i = b[i].nxt)
if(b[i].to != y)
dfs(b[i].to,x),siz[x] += siz[b[i].to],f[x] = max(f[x],f[b[i].to]+b[i].z);
}
void dfs1(int x,int y,int z)//求出树的重心
{
int mx = 0,cmx = 0; g[x] = max(f[x],z);
if(g[x] < ly) ly = g[x],o = x;
for(int i = head[x];i;i = b[i].nxt)
if(b[i].to != y)
{
if(f[b[i].to]+b[i].z > mx) cmx = mx,mx = f[b[i].to]+b[i].z;
else if(f[b[i].to]+b[i].z > cmx) cmx = f[b[i].to]+b[i].z;
}
for(int i = head[x];i;i = b[i].nxt)
if(b[i].to != y)
{
if(f[b[i].to]+b[i].z == mx) dfs1(b[i].to,x,max(z,cmx)+b[i].z);
else dfs1(b[i].to,x,max(z,mx)+b[i].z);
}
}
void dfs2(int x,int y)
{
dep[x] = y+1; fa[x] = y;
for(int i = head[x];i;i = b[i].nxt)
if(b[i].to != y)
dfs2(b[i].to,x);
}
int find(int x)
{
if(F[x] == x) return x;
return F[x] = find(F[x]);
}
inline bool cmp(w1 x,w1 y)
{
if(x.z == y.z) return dep[x.x] > dep[y.x];
return x.z > y.z;
}//值相同优先跑深度大的
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n); ly = 1e18;
for(int i = 1;i < n;i++) read(x),read(y),read(z),add(x,y,z),add(y,x,z);
dfs(1,0); dfs1(1,0,0); dfs2(o,0);
for(int i = 1;i <= n;i++) a[i].z = g[i],a[i].x = i;
sort(a+1,a+1+n,cmp); read(q);
for(int i = 1;i <= q;i++)
{
read(x); r = 1; ans = 0;
for(int j = 1;j <= n;j++) F[j] = j,siz[j] = 1;
for(int j = 1;j <= n;j++)
{
while(r <= n && a[j].z-x <= a[r].z)
{
for(int z = head[a[r].x];z;z = b[z].nxt)
if(b[z].to != fa[a[r].x])//从大到小,直接这么取即可
{
F[b[z].to] = a[r].x;
siz[a[r].x] += siz[b[z].to];
}
ans = max(ans,siz[a[r].x]);
r++;
}
siz[find(a[j].x)]--;
}
print(ans),pc('\n');
}
flush();
/*
(x-l,x)
我们从大到小枚举,这样删除都是删除深度最大值
即枚举最大值
*/
return 0;
}
/*
重心到外面是最小的,原因显然(不是看子树最小的重心,而是看最大值最小的重心)
通过换根dp很容易得到每个点走到叶子的最大花费(注意判断一下根节点也是叶子的情况)
问题变成求最大的联通快使得max-min<=l
枚举最小值,最大值也确定了
在这之间的都能选了
那么总数量是O(n)的,可以暴力加入删除
考虑每次删除f_i=x的,加入f_i=x+l的(l只有50种,每个暴力看)
然后可以套lct了,但我不会(
猜测f肯定是具有什么性质的(不然为啥不直接给你捏)
如果i到一个子树外的点最优,那么其子树一定都是到这里最优
我去好像有用的点最多两个
因为假设i要取子树外的,而i父亲不取
那么到那边去,如果那个点是到其子树内那么也取这个
否则,对于那个点肯定是到fa_i要到的地方去
那么最多两种点,其实就是直径
看了题解确实是,直接从重心开始,这样就是f_i>=f_{fa_i}
这个性质非常好,而且也十分显然,不然不满足重心的性质了
推荐题解:https://www.luogu.com.cn/article/on75r4g3
*/
浙公网安备 33010602011771号