「解题报告」[luoguP6594]换寝室 (二分答案 树形DP)

Posted brucew

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「解题报告」[luoguP6594]换寝室 (二分答案 树形DP)相关的知识,希望对你有一定的参考价值。

「解题报告」[luoguP6594]换寝室 (二分答案 树形DP)

传送门

题面

题意

(n) 个寝室, 构成一个树结构.

每个寝室有一个点权 (h_i).

树上的边可以被禁用.

一个连通块的不满意值为这个连通块中寝室权值中最大值与最小值的差.

(m) 个老师, 每个老师有一条查寝路线. 一个老师的不满意值为他的查寝路线中被禁用的边的数量.

求出在满足所有老师的不满意值之和小于等于 (k) 的情况下, 所有连通块的不满意值 的最大值最小.

数据范围

(1 le n le 800, 1 le m le 10^5, 1 le h_i le 10^9, 0 le k le 8 imes 10^7.)


思路

一道简单题做了一个上午... 脑子可能是被门夹了...

最大值最小, 明显的二分答案.

老师的总不满意度要 (le k), 考虑把不满意度转化为边的权值. 若一条边被 (x) 条查寝路线经过, 则权值为 (x), 用树上差分弄一下就行了.

这样的话, 我们的任务就转化为 : 在树上删去若干条边, 满足每个联通块内点的权值之差最大值 (le lim), 并使删去边权之和最小.

(DP)

考虑树型 (DP) . 先对点的权值进行离散化. 设 (f[u][l][r]) 为 : 考虑点 (u) 的子树, (u) 所在的联通块的点权在 ([l,r]) 范围内时, 删去边权之和的最小值.

(minx[u]=min{ f[u][l][r] mid 1le l le r le max\_val}) ,

(v)(u) 的子节点, (wgt[u])(u) 的父边边权.

若 $val[u]<l $ 或 $ val[u]>r$, 则 (f[u][l][r]=inf),

否则 (f[u][l][r]=sum min(f[v][l][r],minx[v]+wgt[v])).

复杂度为 (O(n^3log val)), 考虑优化.

优化

发现当 (l) 确定时, (r) 的范围可以根据 (l+lim) 确定, 且当 (r < l+lim) 的时候, 答案一定没有 (r=l+lim) 的时候优秀, 所以可以直接钦定 (r=l+lim).

所以, 可以删去 (r) 这一维, 将状态改变为 (f[u][l]), 转移类似.

并且, 由于我们不需要比较点权大小, 所以实际上不需要离散化. 把 (f[u][i]) 的意义改为 (l=val[i]) 时删去边权之和的最小值就行了.


代码

#include<bits/stdc++.h>
#define pb push_back
#define sz(x) (int)(x).size()
using namespace std;
const int _=800+7;
const int L=20;

int n,m,K,dep[_],f[_][27],wgt[_],g[_][_],minx[_],val[_];
vector<int> to[_];

int _lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=L;i>=0;i--)
	if(dep[f[x][i]]>=dep[y])
	    x=f[x][i];
    if(x==y) return x;
    for(int i=L;i>=0;i--)
	if(f[x][i]!=f[y][i]){
	    x=f[x][i];
	    y=f[y][i];
	}
    return f[x][0];
}

void _pre1(int u,int fa){
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=L;i++)
	f[u][i]=f[f[u][i-1]][i-1];
    for(int i=0;i<sz(to[u]);i++){
		int v=to[u][i];
		if(v==fa) continue;
		_pre1(v,u);
    }
}

void _pre2(int u){
    for(int i=0;i<sz(to[u]);i++){
	int v=to[u][i];
	if(v==f[u][0]) continue;
		_pre2(v);
		wgt[u]+=wgt[v];
    }
}

void _init(){
    cin>>n>>m>>K;
    for(int i=1;i<=n;i++)
	scanf("%d",&val[i]);
    
    int x,y;
    for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		to[x].pb(y); to[y].pb(x);
    }
    
    _pre1(1,0);
    for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		wgt[x]++,wgt[y]++,wgt[_lca(x,y)]-=2;
    }
    _pre2(1);
}

void _dp(int u,int lim){
    for(int i=0;i<sz(to[u]);i++){
		int v=to[u][i];
		if(v==f[u][0]) continue;
		_dp(v,lim);
    }

    for(int j=1;j<=n;j++){
        if(val[j]+lim<val[u]||val[j]>val[u]) continue;
        g[u][j]=0;
        for(int i=0;i<sz(to[u]);i++){
            int v=to[u][i];
            if(v==f[u][0]) continue;
            g[u][j]+=min(g[v][j],minx[v]+wgt[v]);
        }
        minx[u]=min(minx[u],g[u][j]);
    }
}
	
bool _check(int lim){
    memset(g,0x3f,sizeof(g));
    memset(minx,0x3f,sizeof(minx));
    _dp(1,lim);
    return minx[1]<=K;
}

void _run(){
    int ans=0,l=0,r=1e9;
    while(l<=r){
        int mid=(l+r)>>1;
        if(_check(mid)){ r=mid-1; ans=mid; }
        else l=mid+1;
    }
    printf("%d
",ans);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("x.in","r",stdin);
#endif
    _init();
    _run();
    return 0;
}

以上是关于「解题报告」[luoguP6594]换寝室 (二分答案 树形DP)的主要内容,如果未能解决你的问题,请参考以下文章

「解题报告」[luoguP6592]幼儿园 (DP)

「解题报告」[luoguP6584]重拳出击 (贪心).md

题解报告:hdu 1421 搬寝室

[LuoguP1462]通往奥格瑞玛的道路

HDU 2199 (二分&三分 _A题)解题报告

HDU 2141(二分&三分 _B题)解题报告