[CEOI2017]Chase 题解

时间:2021-08-21
本文章向大家介绍 [CEOI2017]Chase 题解,主要包括 [CEOI2017]Chase 题解使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

perface

先扯一点没用的话,可以跳过。

考试的时候老师没有给来源,下意识以为是出的原题???

然后发的 solution 因为在机房里打打闹闹,电脑重启,极域被关掉了,没收到就以为没有???

“幸好”之前存的 std 没有被还原掉,于是就直接盯着 std 硬看,猜思路,猜状态,疯狂画图

最后实在没办法,请教机房的大佬,然后大佬直接对着 luogu 上的题面给我讲???

woc ,这个不是原题???(白盯了这么久)

于是决定写篇题解纪念一下。

题解的大致逻辑是考场思路(当然我考场并没有做出来)

Statement

Chase

Solve

考试的时候想到了的性质 :

  1. 起点一定会用一个,用完肯定走

显然,如果起点不用,那么不如直接在使用第一个磁铁的地方开始

因为在另外一个地方后使用磁铁贡献是 \(st^{\prime}\) 周围 \(f\) 的和减去红点的 \(f\)

而直接在 \(st^{\prime}\) 使用磁铁则不需要减去红色部分的值

显然,磁铁用完后,贡献不会增加。

这个性质可以引导我们枚举起点终点 就这 ,考试的时候也并不是很会用

  1. 假如一个点 \(u\) 被放置了磁铁,那么它的贡献可以表示为 \(\sum_{(u\to v)} f[v] \ \ \ \ -f[pre]\)

    其中 \(pre\) 是点 \(u\) 的上一个点。

能不能再挖掘出什么有用的性质呢?布吉岛,先打暴力再说

20pts

暴力枚举嘛,终点起点,放或不放,\(O(n^22^n)\)

40pts

考虑树上 \(dp\)

\(dp[i][j][0/1]\) 表示以 \(rt\) 为根的树,点 \(i\) 到子树内某一点,用了 \(j\)​ 个磁铁,点 \(i\) 用不用磁铁的最大贡献,那么

\[dp[u][j][0]=\max\{dp[v][j][0],dp[v][j][1]\}\\ dp[u][j][1]=\max\{\max(dp[v][j-1][0],dp[v][j-1][1])+sum[u]\} \]

其中 \(sum[u]\) 表示的是以 \(rt\) 为根的树中,\(\sum f[son_u]\)

第一个没啥好说的

第二个主要是看 \(sum[u]\) 什么意思,因为是从 \(u\) 到子树内,所以选 \(u\) 的时候,\(f[fath]+\sum f[son]\) 都会被吸引,但是因为我们是从 \(fath\) 走过来的,所以 \(f[fath]\) 没有贡献,其他都有贡献,刚刚好就是 \(sum[u]\)

这样一遍是 \(O(nv)\)​ 的,发现 \(rt\) 的意义和起点相同,所以还要枚举起点 \(rt\) ,总共就是 \(O(n^2v)\)

思考发现,好像最后半维 \([0/1]\) 是没必要的,上面两个式子可以合并成:

\[dp[u][j]=\max\{dp[v][j],dp[v][j-1]+sum[u]\} \]

这可以小小优化常数,还可以加入优化:磁铁为 \(0\) 则离开

70pts

即固定 \(rt=1\) (但是怎么判断呢)

100pts

考虑对 \(dp\) 进行优化

发现整个过程最愚蠢的地方就是枚举 \(rt\) ,所以考虑换根???

@#¥%……&* 乱推勾 \(8\)​ 了一下式子,发现这个 \(\max\)​ 不会处理 \(50pts\)​ 走人。

好的,现在赛后来看,大体有两种思路:

Way1

参考:CEOI2017 Chase - starsing

延续换根 \(dp\) 的思想,考虑如何强行处理 \(\max\)

假设我们已经求到了 \(rt=fath\) 意义下的 \(dp[i][j]\)

考虑最后得到的 \(rt=u\) 意义下的路径会是怎么样:

会是从 \(u\to v_1\) 或者 \(u\to v_2\)

这引导我们把 \(fath\) 的所有子树拍进一个 \(vector\) 里面

然后维护 \(dp[v][j](v\in son_{fath})\) 的前缀 \(pre\),后缀 \(suf\) 最大值

那么图中红线贡献 \(=pre+(sum[fath]+f[father]-f[u])+(sum[u]+f[fath])\)

那么图中蓝线贡献 \(=suf+(sum[fath]+f[father]-f[u])+(sum[u]+f[fath])\)

上式第一组括号内是 \(fath\) 的贡献,第二组是 \(u\to fath\) 的贡献

当然,上面的式子是在 \(fath\)\(u\) 都放磁铁的情况。

接下来,我觉得就可以直接通过代码理解了。

Code1

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5+5;
const int M = 1e2+5;

struct Edge{
	int nex,to;
}edge[N<<1];
int head[N],dp[N][M],sum[N],f[N];
int n,m,elen,ans;

void addedge(int u,int v){
	edge[++elen]={head[u],v},head[u]=elen;
	edge[++elen]={head[v],u},head[v]=elen;
}
void dfs(int u,int fath){
	for(int e=head[u],v;e;e=edge[e].nex)
		if((v=edge[e].to)!=fath)
			sum[u]+=f[v],dfs(v,u);
	for(int e=head[u],v;e;e=edge[e].nex)
		if((v=edge[e].to)!=fath)
			for(int i=1;i<=m;++i)
				dp[u][i]=max(dp[u][i],max(dp[v][i],dp[v][i-1]+sum[u]));//在 rt=1 意义下的 dp 求解
}

void dfs2(int u,int fath){
	vector<int>son,pre[M];int suf[M];
	memset(suf,0,sizeof(suf));
    //son 记录所有与 u 相连的点(包括 fath)
    //pre[i] 记录使用 i 个磁铁的前缀最大值
    //suf[i] 记录使用 i 个磁铁的后缀最大值
	for(int e=head[u],v;e;e=edge[e].nex){
		son.push_back(v=edge[e].to);
		for(int i=1;i<=m;++i)
			ans=max(ans,dp[u][i]=max(dp[u][i],max(dp[v][i],dp[v][i-1]+sum[u]+f[fath]))),//在 rt=u 的意义下(起点为u)的 dp 数组,注意在起点时,周围所有点都可以贡献
			dp[u][i]=0;//清零,为儿子的求解做准备
	}
	for(int i=0,tmp=0;i<=m;++i,tmp=0)
		for(auto v:son)
			tmp=max(tmp,dp[v][i]),
			pre[i].push_back(tmp);//pre[i][j] 表示前 j 个儿子,使用 i 个磁铁最大值
	for(int i=(int)son.size()-1;i;--i){
		int v=son[i];
		for(int j=m;j;--j)//倒序枚举,方便得到 suf
			dp[u][j]=max(dp[u][j],max(max(suf[j],suf[j-1]+sum[u]+f[fath]-f[v]),
								      max(pre[j][i-1],pre[j-1][i-1]+sum[u]+f[fath]-f[v]))),//放/不放磁铁
			suf[j]=max(suf[j],dp[v][j]);
        //此时, dp[u][j] 的意义变成了从 u 走到子树内 v^{\prime} (v^{\prime}\neq v) 的最大值
		if(v!=fath)dfs2(v,u);//////////////////
		for(int j=1;j<=m;++j)dp[u][j]=0;
	}
	if(son.size()){//第一个儿子,没有办法用 pre 更新
		int v=son[0];
		for(int j=1;j<=m;++j)
			dp[u][j]=max(dp[u][j],max(suf[j],suf[j-1]+sum[u]+f[fath]-f[v]));
		if(v!=fath)dfs2(v,u);///////////////
	}
}

signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;++i)scanf("%lld",&f[i]);
	for(int i=1,u,v;i<n;++i)
		scanf("%lld%lld",&u,&v),addedge(u,v);
	dfs(1,0);
	dfs2(1,0);
	printf("%lld\n",ans);
	return 0;
}

Way2

参考:Chase - zero4338 的博客

我们可以转化问题:一棵树,点有点权,求在一个计算法则下的直径

(所谓计算法则就是磁铁的使用)

考虑常规树的直径是怎么做的:设 \(d1[u],d2[u]\) 分别表示从 \(u\) 走到 \(u\) 子树内的最长路和次长路

那么,\(ans=\max\{d1[u]+d2[u]\}\)

尝试套到本题,特殊的是,从 上往下走 和 从下往上 走同一条路径的贡献并不相同

那么,我们设 \(up[i][j][0/1]\) 表示从 \(i\) 的子树内走到 \(i\) ,使用 \(j\) 块磁铁, \(i\) 放不放磁铁的最大贡献

同理,设 \(down[i][j][0/1]\)

容易写出状态转移方程:

\[\begin{align} up[u][i][0]& = \max(up[u][i][0], \max(up[v][i][0], up[v][i][1]))\\ up[u][i][1] &= \max(up[u][i][1], \max(up[v][i - 1][0], up[v][i - 1][1]) + sum[u] - f[v] + f[fath])\\ down[u][i][0]& = \max(down[u][i][0], \max(down[v][i][0], down[v][i][1]))\\ down[u][i][1]& = \max(down[u][i][1], \max(down[v][i - 1][0], down[v][i - 1][1]) + sum[u]) \end{align} \]

初态:

up[u][0][1] = down[u][0][1] = -inf;
up[u][1][1] = max(up[u][1][1], sum[u] + f[fath]);

因为显然。

答案可以通过前缀最大值更新

Code2

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
const int inf = 1e18;

struct Edge {
    int nex, to;
} edge[N << 1];
int head[N], f[N], sum[N];
int up[N][105][2], down[N][105][2];
int n, V, elen, ans;

void addedge(int u, int v)
{
    edge[++elen] = { head[u], v }, head[u] = elen;
    edge[++elen] = { head[v], u }, head[v] = elen;
}
void DP(int u, int fath)
{
    for (int e = head[u], v; e; e = edge[e].nex) {
        if ((v = edge[e].to) == fath)
            continue;
        DP(v, u);
        sum[u] += f[v];
    }
    up[u][0][1] = down[u][0][1] = -inf;
    up[u][1][1] = max(up[u][1][1], sum[u] + f[fath]);
    for (int e = head[u], v; e; e = edge[e].nex) {
        if ((v = edge[e].to) == fath)
            continue;
        int mx1 = 0, mx2 = 0;
        for (int i = V; ~i; --i) {
            mx1 = max(mx1, max(up[u][V - i][0], up[u][V - i][1]));
            mx2 = max(mx2, max(down[u][V - i][0], down[u][V - i][1] + f[fath] - f[v]));
            ans = max(ans, max(down[v][i][0], down[v][i][1]) + mx1);
            ans = max(ans, max(up[v][i][0], up[v][i][1]) + mx2);
        }
        for (int i = 1; i <= V; ++i)
            up[u][i][0] = max(up[u][i][0], max(up[v][i][0], up[v][i][1])),
            up[u][i][1] = max(up[u][i][1], max(up[v][i - 1][0], up[v][i - 1][1]) + sum[u] - f[v] + f[fath]),
            down[u][i][0] = max(down[u][i][0], max(down[v][i][0], down[v][i][1])),
            down[u][i][1] = max(down[u][i][1], max(down[v][i - 1][0], down[v][i - 1][1]) + sum[u]);
    }
}

signed main()
{
    scanf("%lld%lld", &n, &V);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &f[i]);
    for (int i = 1, u, v; i < n; ++i)
        scanf("%lld%lld", &u, &v), addedge(u, v);
    DP(1, 0);
    printf("%lld\n", ans);
    return 0;
}

原文地址:https://www.cnblogs.com/wyb-sen/p/15169226.html