[21ZR联赛集训d7]tournament(动态规划+树形结构)

\(\text{Zbox}\) 投资了一家电子竞技俱乐部, 现在这家俱乐部需要参加一个杯赛。这次的杯赛中总共有 \(2^n\) 只参赛队伍, 队伍之间的实力差距很大, 不妨认为一场比赛实力较强的队伍必然会获胜。不妨认为实力最弱的队伍编号为 \(1\), 次弱的编号为 \(2\), \(\cdots\), 最强的编号为 \(2^n\)\(1\le n\le 18\)

序列 [p1,p2,...,p2^n] 将在输入中给出, 每个位置上的数字表示在这个位置上的是编号为这个数字的队伍。由于 \(\text{Zbox}\) 非常有钱, 他可以花钱使得他投资的队伍和另一只队伍交换位置(也可以不交换)。现在 \(\text{Zbox}\) 希望他的队伍能够赢下尽可能多的比赛, 但他却忘了将他投资的队伍编号告诉你, 因此你需要输入对于每个编号的队伍, 进行一次交换位置操作后, 至多能在这场杯赛中赢下几轮.

本场签到题,然而没有签到。

image

考虑上图的比赛树,一支队伍能在一棵子树中杀穿(经过多轮比赛站到子树根上)的充要条件当然是他在这棵子树中实力最强。而如果一支队伍要换到某个子树中并想要杀穿这棵子树,他需要强于这棵子树的第二强,这样他就可以换掉这棵子树的第一强

杀穿大小相同的子树得到的最大胜场数是一样的,故我们只需对于每一种大小为 \(size\) 的子树求其中每一棵里次大值的最小值 \(\min_{size}\)。在此基础上,我们枚举队伍,对于每一支队伍找出极大的 \(size\) 使得该队实力大于 \(\min_{size}\) 即可。

怎样计算 \(\min_{size}\)?我们设 \(dp_1(i,j)\) 表示大小为 \(i\),根节点标号为 \(j\) 的子树里的最大值,设 \(dp_2(i,j)\) 为这一子树里的次大值。前者转移是将其下两个大小为 \(i-1\) 的子树最大值取 \(\max\),后者则是对那两个子树最大值取 \(\min\),对次大值取 \(\max\),再把两者答案取 \(\max\)。完成两个 \(dp\) 数组计算后,再用每个 \(dp_2(i,j)\) 更新 \(\min_i\)

计算 \(\min_{size}\) 和对每支队伍求答案的时间复杂度都是 \(O(2^n\times n)\),可以通过本题。值得注意的是,\(2^{18}=262144\),切不要开小数组导致 RE!

下面是 AC 代码片段:

const int N=3e5+10;
int a[N],dp1[N][20],dp2[N][20],minn[N];

int main()
{
	int n; scanf("%d",&n);
	for(int i=1;i<=(1<<n);++i)
	{
		scanf("%d",&a[i]);
		dp1[i][0]=a[i];
	}
	memset(minn,0x3f,sizeof minn);
	//枚举子树大小		//枚举该大小的子树
	for(int i=1;i<=n;++i) for(int j=1;j<=(1<<n);j+=(1<<i))
	{
		dp1[j][i]=max(dp1[j][i-1],dp1[j+(1<<i-1)][i-1]);//其下的两棵子树
		dp2[j][i]=max(min(dp1[j][i-1],dp1[j+(1<<i-1)][i-1]),max(dp2[j][i-1],dp2[j+(1<<i-1)][i-1]));
		minn[i]=min(minn[i],dp2[j][i]);
	}
	for(int i=1,ans;i<=(1<<n);++i)
	{
		ans=0;
		for(int j=1;j<=n;++j) if(minn[j]<i) ans=j;//如果队伍i的实力能征服大小为j的子树,则更新ans,从小往大扫,最后一次为最大值
		printf("%d ",ans);
	}
	putchar('\n');
	return 0;
}

THE END

posted @ 2021-10-21 15:54  q0000000  阅读(69)  评论(0编辑  收藏  举报