[DP](计蒜之道2016程序设计大赛初赛第六场)微软的员工福利 题解

版权声明:本文为博主原创文章,欢迎转载。 https://blog.csdn.net/try__jhf/article/details/82832026

[DP] (计蒜之道2016初赛第六场) 微软的员工福利 题解

题目大意

给出一个 n n 个节点的有根树,每个点可以赋予给定的两个值 v [ i ] [ 0 / 1 ] v[i][0/1] 其中之一,这棵树的权值就是所有节点的值,但是对于每个非叶节点节点 i i 而言,如果在它和它所有儿子节点中最大值与最小值的差大小为 x x ,那么需要在树的权值中扣除 i 666 x 1000 i*666*\lceil\frac{x}{1000}\rceil ,求这棵树的最大权值。

n 1 0 5 , v [ i ] [ 0 / 1 ] 1 0 5 n\le10^5,v[i][0/1]\le10^5

解题分析

这道题非A即B,所以典型的2-SAT可以考虑套路1:先用其中一个,然后用差值调整,但是由于最大值与最小值的差要扣除,所以还需要套路2,由于 x 1000 \lceil\frac{x}{1000}\rceil 最多有101个,所以可以一开始用套路2枚举扣除值的大小,然后枚举在这个范围内所有的可包围点,用DP结合套路1来算出最优解。

什么是two pointers

示例代码

题目传送门

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=100005,maxm=200005;
int n,now,v[maxn][2],tot,son[maxm],nxt[maxm],lnk[maxn],ha[maxm];
LL f[maxn][2];
struct data{
	int x,y;
	data (int x=0,int y=0):x(x),y(y){}
	bool operator < (const data b)const{return x<b.x;}
}w[maxm];
inline void readi(int &x){
	x=0; char ch=getchar();
	while ('0'>ch||ch>'9') ch=getchar();
	while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void _add(int x,int y){son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;}
void _init(){
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	readi(n); tot=0;
	for (int i=1;i<=n;i++) {readi(v[i][0]); readi(v[i][1]); if(v[i][0]>v[i][1]) swap(v[i][0],v[i][1]);}
	for (int i=1,x,y;i<n;i++){
		readi(x); readi(y); _add(x,y); _add(y,x);
	}
}
int gei(int L,int R,int x){
	int A=(L<=v[x][0]&&v[x][0]<=R),B=(L<=v[x][1]&&v[x][1]<=R); return B*2+A;
}
void _dfs(int x,int fa){
	for (int j=lnk[x];j;j=nxt[j])
		if (son[j]!=fa) _dfs(son[j],x);
	now=0;
	for (int j=lnk[x];j;j=nxt[j])
		if (son[j]!=fa) {w[++now]=data(v[son[j]][0],son[j]); w[++now]=data(v[son[j]][1],son[j]);}
	if (!now) {f[x][0]=v[x][0]; f[x][1]=v[x][1]; return;}
	w[++now]=data(v[x][0],x); w[++now]=data(v[x][1],x);
	sort(w+1,w+now+1); LL mx[2]; mx[0]=mx[1]=((LL)1<<60)*(-1);
	for (int D=0;D<=w[now].x-w[1].x+1000;D+=1000){ //枚举极差
		int k=0; LL sum=-(LL)D/1000*666*x;
		for (int i=1;i<=now;i++) ha[w[i].y]=0;
		for (int i=1,j=1,t,L,R;i<=now;i=t){
			if (v[x][1]<w[i].x) break;
			L=w[i].x; R=L+D;
			for (;j<=now&&w[j].x<=R;ha[w[j++].y]++)
				if (ha[w[j].y]) sum+=max(f[w[j].y][0],f[w[j].y][1])-f[w[j].y][0];
            	//如果两个都可以选上,贪心选较大的
				else {sum+=f[w[j].y][gei(L,R,w[j].y)==2]; k++;}
            	//如果只有一个,优先考虑小的
			if (k==(now>>1)){
				int te=gei(L,R,x);
				if (te&1) mx[0]=max(mx[0],sum+v[x][0]);
				if (te>1) mx[1]=max(mx[1],sum+v[x][1]);
			} //更新答案
			for (t=i;w[i].x==w[t].x&&t<=now;ha[w[t++].y]--){
				if (ha[w[t].y]==1) {sum-=f[w[t].y][gei(L,R,w[t].y)==2]; k--;}
				else sum-=max(f[w[t].y][0],f[w[t].y][1])-f[w[t].y][1];
			}//区间右移,去除前面的
		}
	}
	f[x][0]=mx[0]; f[x][1]=mx[1];
}
void _solve(){
	_dfs(1,0); printf("%lld",max(f[1][0],f[1][1]));
}
int main()
{
	_init();
	_solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/try__jhf/article/details/82832026