BZOJ 5250 [九省联考2018]秘密袭击coat 树形DP

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/88856574

title

BZOJ 5250
LUOGU 4365
题目背景

We could have had it all. . . . . .
我们本该,拥有一切
Counting on a tree. . . . . .
何至于此,数数树上
Counting on a Tree(CoaT)即是本题的英文名称。

题目描述

Access Globe 最近正在玩一款战略游戏。在游戏中,他操控的角色是一名C 国士 兵。他的任务就是服从指挥官的指令参加战斗,并在战斗中取胜。
C 国即将向D 国发动一场秘密袭击。作战计划是这样的:选择D 国的s 个城市, 派出C 国战绩最高的s 个士兵分别秘密潜入这些城市。每个城市都有一个危险程度d_i,
C 国指挥官会派遣战绩最高的士兵潜入所选择的城市中危险程度最高的城市,派遣战绩第二高的士兵潜入所选择的城市中危险程度次高的城市,以此类推(即派遣战绩第i高的士兵潜入所选择城市中危险程度第i 高的城市)。D 国有n 个城市,n - 1 条双向道路连接着这些城市,使得这些城市两两之间都可以互相到达。为了任务执行顺利,C 国选出的s 个城市中,任意两个所选的城市,都可以不经过未被选择的城市互相到达。
Access Globe 操控的士兵的战绩是第k 高,他希望能估计出最终自己潜入的城市的 危险程度。Access Globe 假设C 国是以等概率选出任意满足条件的城市集合S ,他希望你帮他求出所有可能的城市集合中,Access Globe 操控的士兵潜入城市的危险程度之和。如果选择的城市不足k 个,那么Access Globe 不会被派出,这种情况下危险程度为0。
当然,你并不想帮他解决这个问题,你也不打算告诉他这个值除以998 244 353 的 余数,你只打算告诉他这个值除以64,123 的余数。

输入输出格式
输入格式:

从文件coat .in 中读入数据。
第1 行包含3 个整数n、k、W,表示D 国城市的个数、Access Globe 所操控士兵 潜入的城市战绩排名以及D 国的所有城市中最大的危险程度;
第2 行包含n 个1 到W 之间的整数d_1 ; d_2 ; … d_n,表示每个城市的危险程度;
第3 行到第n + 1 行,每行两个整数x_i; y_i,表示D 国存在一条连接城市x_i和城市y_i的双向道路。

输出格式:

输出到文件coat.out 中。 输出一个整数,表示所有可行的城市集合中,Access Globe 操控的士兵潜入城市的危险程度之和除以64,123 的余数。

输入输出样例
输入样例#1:

5 3 3
2 1 1 2 3
1 2
2 3
1 4
1 5

输出样例#1:

11

输入样例#2:

10 2 3
2 1 1 3 1 2 3 3 1 3
1 2
2 3
2 4
2 5
2 6
5 7
1 8
8 9
1 10

输出样例#2:

435

说明

D 国地图如下,其中危险程度为d 的城市的形状是(d + 3) 边形。
在这里插入图片描述
以下是所有符合条件且选择的城市不少于3 个的方案:
• 选择城市1、2、3,Access Globe 的士兵潜入的城市危险程度为1;
• 选择城市1、2、3、4,Access Globe 的士兵潜入的城市危险程度为1;
• 选择城市1、2、3、5,Access Globe 的士兵潜入的城市危险程度为1;
• 选择城市1、2、3、4、5,Access Globe 的士兵潜入的城市危险程度为2;
• 选择城市1、2、4,Access Globe 的士兵潜入的城市危险程度为1;
• 选择城市1、2、5,Access Globe 的士兵潜入的城市危险程度为1;
• 选择城市1、2、4、5,Access Globe 的士兵潜入的城市危险程度为2;
• 选择城市1、4、5,Access Globe 的士兵潜入的城市危险程度为2;而在选择的 城市少于3 时,Access Globe 的士兵潜入的城市危险程度均为0;
所以你应该输出(1 + 1 + 1 + 2 + 1 + 1 + 2 + 2) mod 64 123 = 11。
在这里插入图片描述

analysis

正解确实挺难的,据说是线段树+FFT,但是我真的不会FFT(快速傅里叶变换),所以就在洛谷上找到了一个所谓暴力碾标算的算法,不开O2的话,第14个点会T,但是开了O2后,跑的还挺快,真个下来才6s多,还好省选是开O2的。话说开了也没用,毕竟我是不可能在赛场上暴力A题的。

简单说下写法吧:
毕竟,刚看到这道题,大家都会想到树上算法,嗯,我们的暴力算法就是想办法把这道题转化成树形DP,我们可以设 f [ i ] [ k ] f[i][k] 表示以 i i 点为根的树中包含根的连通块,且其中有 k k 个点大于等于根的危险度的方案数。

那么,考虑每个点的贡献。把小于这个点的点当 1 1 ,大于这个点的点当 0 0 (双关键字)。然后问题转化成树上选恰好 k k 个1的连通块个数,然后就 O ( n 2 k ) O(n2k) ,考虑常数优化。对于 k k 比较小,转移的时候不要转移大于 k k 的部分。
对于 k k 比较大, D p Dp 之前先特判一下1的点够不够用。

未来有机会,会写写正解的。

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=2010;
const int mod=64123;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
inline void up(int &x,int y)
{
	x+=y;
	if (x>=mod) x-=mod;
}
int n,k,w,s,ans,f[maxn][maxn],d[maxn];//f[i][k]表示以i点为根的树中包含根的连通块,且其中有k个点大于等于根的危险度的方案数
inline void dfs(int x,int fa)
{
	if (d[s]<d[x] || (d[s]==d[x] && s<x))//这里“等于”有一定的问题,就是同一种连通块可能会被其他的根重复统计
		for (int i=1; i<=k; ++i)//所以要特判一下,d[s]==d[x] && s<x才认为其合法
			f[x][i]=f[fa][i-1];
	else
		for (int i=1; i<=k; ++i)
			f[x][i]=f[fa][i];
	for (int i=head[x]; i; i=Next[i])
		if (ver[i]!=fa)
			dfs(ver[i],x);
	for (int i=1; i<=k; ++i)
		up(f[fa][i],f[x][i]);
}
inline void findsum(int x)
{
	int tot=1;
	for (int i=1; i<=n; ++i)
		if (d[i]>d[x] || (d[i]==d[x] && i>x)) ++tot;
	if (tot<k) return ;
	memset(f[x],0,sizeof(f[x]));
	f[x][1]=1,s=x;
	for (int i=head[x]; i; i=Next[i])
		dfs(ver[i],x);
	ans=(ans+1ll*f[x][k]*d[x])%mod;
}
int main()
{
	read(n);read(k);read(w);
	for (int i=1; i<=n; ++i)
		read(d[i]);
	for (int i=1; i<n; ++i)
	{
		int x,y;
		read(x);read(y);
		add(x,y);add(y,x);
	}
	for (int i=1; i<=n; ++i)
		findsum(i);
	printf("%d\n",ans);
	return 0;
}//计数类树形依赖背包dp问题,可以先强制转移下来,这么一直转移到叶节点,最后回溯时加上儿子节点完善好的dp值

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/88856574