2020.08.03日常总结

二分是个神奇的算法,它能让你难以维护的数据突然变得十分容易维护……

洛谷P4322   [JSOI2016]最佳团体 \color{green}{\texttt{洛谷P4322 [JSOI2016]最佳团体}} 洛谷P4322 [JSOI2016]最佳团体

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

  • 有一个有 N + 1 N+1 N+1 个点的树,每个节点有两个值: p i , s i ( 1 ≤ i ≤ N ) p_{i},s_{i}(1 \leq i \leq N) pi,si(1iN)

  • 你需要选 t + 1 t+1 t+1 个点,如果一个点选了,则其父亲必须要选。

  • 一个选择方案集合 T T T 的总价值为:

    ∑ i ∈ T p i ∑ i ∈ T s i \dfrac{\sum\limits_{i \in T} p_i}{\sum\limits_{i \in T} s_i} iTsiiTpi

  • 求总价值的最大值。 1 ≤ t ≤ N ≤ 2500 , 1 ≤ p i , s i ≤ 1 × 1 0 4 ( 1 ≤ i ≤ N ) , Father ( i ) < i 1 \leq t \leq N \leq 2500,1 \leq p_i,s_i \leq 1 \times 10^4(1 \leq i \leq N),\texttt{Father}(i)<i 1tN2500,1pi,si1×104(1iN),Father(i)<i。节点从 0 0 0 编号到 N N N

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

是不是感觉总价值的那个式子特别难维护?

我们可以记 mid \texttt{mid} mid 表示总价值大于等于 mid \texttt{mid} mid。我们发现 mid \texttt{mid} mid 越大,越难以满足,所以我们可以二分答案。

改写一个总价值那个式子:

∑ i ∈ T p i ∑ i ∈ T s i ≥ mid ∑ i ∈ T p i ≥ mid × ∑ i ∈ T s i ∑ i ∈ T p i − mid × ∑ i ∈ T s i ≥ 0 ∑ i ∈ T ( p i − mid × s i ) ≥ 0 \begin{aligned} \dfrac{\sum\limits_{i \in T} p_i}{\sum\limits_{i \in T}s_i} &\geq \texttt{mid}\\ \sum\limits_{i \in T} p_i &\geq \texttt{mid} \times \sum \limits_{i \in T}s_i\\ \sum\limits_{i \in T} p_i - \texttt{mid} \times \sum \limits_{i \in T} s_i & \geq 0\\ \sum\limits_{i \in T} \left (p_i - \texttt{mid} \times s_i \right ) & \geq 0 \end{aligned} iTsiiTpiiTpiiTpimid×iTsiiT(pimid×si)midmid×iTsi00

改每个点的价值为 p i − mid × s i p_i -\texttt{mid} \times s_i pimid×si,原题被转化为了一个树上 dp 的问题。只要最后的总价值 ≥ 0 \geq 0 0,代表 mid \texttt{mid} mid 是一个可行答案。

如何树上 dp?很简单,记 f i , j f_{i,j} fi,j 表示在 i i i 的子树中选 j j j 个点时的最大总价值,原题被转化为了一个树上背包问题,可以用类似 选课 一题的方法解决。

[code] \color{blue}{\texttt{[code]}} [code]

struct edge{//链式前向星 
	int next,to;//的模板 
}e[2510];int h[2510],tot;
inline void add(int a,int b){
	e[++tot]=(edge){h[a],b};h[a]=tot;
}
double p[2510],s[2510],w[2510];
double f[2510][2510];//dp用数组 
int n,t,sze[2510];double l,r,mid;
inline void dp(int u){
	f[u][1]=w[u];sze[u]=1;//init
	for(int i=h[u];i;i=e[i].next){
		register int to=e[i].to;
		dp(to);//先递归计算儿子值 
		sze[u]+=sze[to];//子树大小 
		for(int k=min(sze[u],t+1);k>=1;k--)
			for(int l=0;l<=min(sze[to],k-1);l++)
				f[u][k]=max(f[u][k],f[u][k-l]+f[to][l]);
//				该状态转移方程的含义是:在u当前加入的所有儿子中,to这个子树选l个点,其它儿子所对应的子树共选k-l个点时的最大总价值
//				注意就像01背包一样,k必须是倒序的
	}
}
const int inf=0x3f3f3f3f;
inline bool check(double mid){
	for(int i=1;i<=n;i++)
		w[i]=p[i]-mid*s[i];
	w[0]=0;//计算每个人贡献 
	for(int i=0;i<=n;i++)
		for(int j=1;j<=t+1;j++)
			f[i][j]=-inf;
	dp(0);//从根开始进行dp 
	return f[0][t+1]>=0;
}
const double eps=1e-4;
int main(){
	t=read();n=read();
	for(int i=1;i<=n;i++){
		s[i]=read();p[i]=read();
		add(read(),i);//建图 
	}
	l=0.0;r=10000.0;
	while (l+eps<r){
		mid=(l+r)/2.0;
		if (check(mid)) l=mid;
		else r=mid;
	}
	printf("%.3lf",l);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值