【GDKOI2006】新红黑树

7 篇文章 0 订阅
1 篇文章 0 订阅

题目大意

给出 n n n 条边的左右端点和边的颜色( 1 1 1 − 1 -1 1)和权值 w w w,边组成一棵树,根节点为 0 0 0,有 A , B A,B A,B 二人轮流删边, A A A 只能删掉颜色为 1 1 1 的边, B B B 只能删掉颜色为 − 1 -1 1 的边,若一条边与根节点失去联系则自动消失,假设 s u m A sumA sumA A A A 砍掉的边的权值和, s u m B sumB sumB B B B 砍掉的边的权值和, A A A 想让 s u m A − s u m B sumA-sumB sumAsumB 尽可能大, B B B 想让 s u m A − s u m B sumA-sumB sumAsumB 尽可能小,且 A , B A,B A,B 足够聪明,A先进行操作,求最终的 s u m A − s u m B sumA-sumB sumAsumB 的值。

数据范围

1 ≤ n ≤ 20 , 1 ≤ w ≤ 1000 1 \le n \le20,1 \le w \le 1000 1n20,1w1000

思路

大部分人的做法都是状压DP或记忆化搜索,讲讲我的暴力优化算法 (记搜,不打也罢!)
首先化线段为点,建虚点连向端点为 0 0 0 的线段,以虚点为根,那么删边问题就变成删点问题了。
我们假设 d f s dfs dfs 函数会返回进行操作后的值,那么 A A A 肯定希望这个值越大越好, B B B 则反之,所以,我们在 d f s dfs dfs 函数中加入一个变量表示当前操作的是 A A A 还是 B B B ,就可以对 d f s dfs dfs 时的最大(小)值求解啦。
而对于消失的点,我们发现当某节点被删时,它的子树也一并被删去了,所以我们可以先预处理出来每个点的 d f s dfs dfs 序和子树大小 s i z e size size ,再枚举子树节点打上标记即可。

int get_ans(int who,int ans)
{
//	printf("%d %d\n",who,ans);
	bool flag=1,pd=0;
	int mzh=0,an1=-21000000,an2=21000000;
	for(int i=1;i<=tot;i++)
	{
        if(bz[tree[i].dfn]==0)
        {
           flag=0;
           if(tree[i].col==who)
           {
              pd=1;
              for(int j=tree[i].dfn;j<=tree[i].dfn+tree[i].sz-1;j++)
              bz[j]++;
		      int k=get_ans(change(who),tree[i].val*who+ans);
		      an1=max(an1,k),an2=min(an2,k);		     
		      for(int j=tree[i].dfn;j<=tree[i].dfn+tree[i].sz-1;j++)
		      bz[j]--; 	           	
		   }

		}
	}
	if(flag)
	return ans;
	if(!pd)
	an1=get_ans(change(who),ans),an2=an1;
	if(who==1)
	return an1;
	else
	return an2;
}

以上为 d f s dfs dfs 实现代码。
但是,很遗憾,这种做法有一个点T掉了。。。
难道真的要打记搜吗?
本着能不打记搜就不打记搜的理念,回顾一下刚刚的算法有什么可以优化的地方。。。
我们发现更新标记的步骤整整用掉了 O ( n ) O(n) O(n) 的时间复杂度,这让本来就是 2 2 2 的幂次时间复杂度算法更是雪上加霜。
那我们怎么将这一段打上标记呢?
有一个很直观的想法就是打线段树,但是线段树很长,而且 O ( n ) O(n) O(n) 优化到 O ( l o g n ) O(log n) O(logn) 似乎并没有多大的优化QAQ。
但我们有差分啊!按照 d f s dfs dfs 序枚举,设变量 a a a 表示这时的节点的 a a a 个祖先被删掉了,那我们修改时只需要将 d f s i dfs_i{} dfsi d f s i + s i z e i − 1 dfs_{i}+ size_{i}-1 dfsi+sizei1 进行修改即可,时间复杂度是 O ( 1 ) O(1) O(1) 的,所以暴力的时间复杂度就降为 O ( 2 n n ) O(2^n n) O(2nn) 的啦。

AC代码

#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{
	int col,val,dfn,sz;
}tree[30];
struct zlz{
	int head,to,nxt,val,col;
}edge[66],edge2[66];
int n,cnt,a,b,c,d,tot,cnt2,pop=-21000000,num,to[2020];
int bz1[2020],bz2[2020];
inline int read()
{
	int num=0,f=1;
	char ch=getchar();
	while(!(ch>='0'&&ch<='9'))
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		num=num*10+ch-'0';
		ch=getchar();
	}
	return num*f;
}
void xx(int u,int v,int w,int x)
{
	edge[cnt].to=v;
	edge[cnt].col=w;
	edge[cnt].val=x;
	edge[cnt].nxt=edge[u].head;
	edge[u].head=cnt;
	cnt++;
}
void xx2(int u,int v)
{
	edge2[cnt2].to=v;
	edge2[cnt2].nxt=edge2[u].head;
	edge2[u].head=cnt2;
	cnt2++;
}
void dfs(int x,int father,int lst)
{
	for(int i=edge[x].head;i!=-1;i=edge[i].nxt)
	{
		int y=edge[i].to;
		if(y==father)
		continue;
		tot++;
		tree[tot].col=edge[i].col;
		tree[tot].val=edge[i].val;
		xx2(tot,lst),xx2(lst,tot);
		dfs(y,x,tot);
	}
}
void build(int x,int father)
{
	num++,tree[x].dfn=num,to[num]=x;
	tree[x].sz=1;
	for(int i=edge2[x].head;i!=-1;i=edge2[i].nxt)
	{
		int y=edge2[i].to;
		if(y==father)
		continue;
		build(y,x);
		tree[x].sz+=tree[y].sz;
	}
	return;
}
int get_ans(int who,int ans)
{
	bool flag=1,pd=0;
	int  an1=-21000000,an2=21000000,mzh=0;
	for(register int i=2;i<=tot+1;i++)
	{
		mzh-=bz2[i];
		mzh+=bz1[i];
		if(mzh==0)
		{
			flag=0;
			if(tree[to[i]].col==who)
			{
				pd=1;
				bz1[i]++;
				bz2[i+tree[to[i]].sz]++;
				int k=0;
				if(who==1)
				k=get_ans(-1,ans+who*tree[to[i]].val);
				else
				k=get_ans(1,ans+who*tree[to[i]].val);
				an1=max(an1,k),an2=min(an2,k);
				bz1[i]--;
				bz2[i+tree[to[i]].sz]--;
			}
		}
	}
	if(flag)
	return ans;
	if(!pd)
	{
		if(who==1)
		an1=get_ans(-1,ans);
		else
		an1=get_ans(1,ans);
		an2=an1;
	}
	if(who==1)
	return an1;	
	else
	return an2;
}
int main()
{
	memset(edge,-1,sizeof(edge));
	memset(edge2,-1,sizeof(edge2));
	n=read();
	for(register int i=1;i<=n;i++)
	{
		a=read(),b=read(),c=read(),d=read();
		xx(a,b,c,d),xx(b,a,c,d);
	}
	dfs(0,0,0);
	build(0,0);
	bool p=0;
	for(register int i=2;i<=tot+1;i++)
	if(tree[to[i]].col==1)
	{
		p=1;
		bz1[tree[to[i]].dfn]=1;
		bz2[tree[to[i]].dfn+tree[to[i]].sz]=1;
	    pop=max(pop,get_ans(-1,tree[to[i]].val));
	    bz1[tree[to[i]].dfn]=0;
	    bz2[tree[to[i]].dfn+tree[to[i]].sz]=0;
	}
	if(!p)
	{
		pop=0;
		for(int i=1;i<=tot;i++)
		pop-=tree[i].val;
	}
	printf("%d\n",pop);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值