码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2020-06-16 23:43:32      阅读:59      评论:0      收藏:0      [点我收藏+]

标签:int   情况下   联通   strong   状态   const   span   这一   continue   

「解题报告」[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 \times 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 1\le 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\n",ans);
}

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

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

标签:int   情况下   联通   strong   状态   const   span   这一   continue   

原文地址:https://www.cnblogs.com/BruceW/p/13149541.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!