洛谷 P4437 - 排列

题解咕到了现在。。

洛谷题目页面传送门

\(n\) 个数 \(b_i\),你需要重新排列 \(b\) 得到 \(b'\) 使得 \(b_{a_i}\) 必须排在 \(b_i\) 前面。最大化 \(\sum\limits_{i=1}^nib'_i\) 或报告无解。

\(n\in\left[1,5\times10^5\right]\)

首先建图。如果有环的话肯定就无解,否则不难发现一定是一个森林。于是就转化成了 POJ 2054(只不过 POJ 的这题平方可过)。

首先想到的是贪心,每次选当前能选的节点集合中最小的,而这肯定是不行的,反例随便举。

考虑当前可选的节点集合中任意两个 \(x,y\),来比较当前选它们两个哪个更好。一个比较自然的想法是尝试找到一种方法,能将所有选 \(x\) 的方案交换几次变成选 \(y\) 并且更优。那我们就来看,我们假设当前选的是 \(x\),然后 \(y\) 在位置 \(p\)。考虑将 \(y\) 移动到 \(x\) 当前位置,然后将 \(x\) 换过去?可能会不行,因为 \([1,p]\) 内可能会有依赖于 \(x\) 的。

想到一种方法,即将 \(y\) 移到最前面,然后将 \([1,p]\) 内依赖于或等于 \(x\) 的子序列整体顺移。但是这样又不太好比较是否更优了,因为涉及到了 \(x\) 的无穷匮子孙。那我们只好退一万步,假设 \(y\) 是全树权值最小的节点,那么不难证明换过去一定更优(证明就是你会发现 \(x\) 的子孙们移动的距离之和等于 \(p-1\),恰好每格与 \(y\) 对应,那么每次 \(y\) 肯定能干得过其他节点,因为他是最强的嘛)。

你现在虽然能保证一定更优了,但是你毕竟退步了,是有条件的,即必须是全树最小点。也就意味着不是任何时候我们都能应用这个理论来比较优劣。那怎么办呢?我们不考虑当前,我们规划未来,即一旦全树最小点进入了候选集合,那么最先选的一定是它。那么什么时候它进入候选集合呢?那显然是当且仅当它爸爸被选了的时候,也就转化成了最小点爸爸被选之后一定选它。那么不难想到将它和它爸爸合并,这样总结点数 \(-1\),似乎可以归纳了。

然后你又会发现问题,因为想要归纳的话,你在任意时刻面临的节点不再是单一的节点,而可能是若干个曾经的节点合并起来的合体。那么我们需要将先前那条理论上升一下。直觉告诉我们应该是内部包含数们的平均数最小的合体。推理起来也不难,我们设 \([1,p]\) 内对应位置上的合体大小为 \(sz_i\),和为 \(sum_i\),平均数为 \(avg_i\),移动前后位置差为 \(d_i\),那么显然有 \(\sum\limits_{i=1}^p sz_id_i=0\)。而每个合体对答案变化量的贡献是 \(sum_id_i\),也即 \(avg_isz_id_i\),又因为 \(sz_id_i\) 是守恒的,所以选 \(avg\) 最小的一定没问题。

实现就比较简单了,用 set 随便维护一下即可。

根据考察能力守恒定律,思维题代码一定很好写(

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define Y second
const int N=500000;
void read(int &x){
	x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
int n;
int a[N+1],b[N+1];
int vis[N+1];
void dfs(int x){
	if(vis[x]==2)exit((puts("-1"),0));
	if(vis[x]==1)return;
	vis[x]=2;
	if(a[x])dfs(a[x]);
	vis[x]=1;
}
struct ufset{
	int ycx[N+2],sz[N+2];
	void init(){for(int i=1;i<=n+1;i++)ycx[i]=0,sz[i]=i>1;}
	int root(int x){return ycx[x]?ycx[x]=root(ycx[x]):x;}
	void mrg(int x,int y){x=root(x),y=root(y),sz[y]+=sz[x],ycx[x]=y;}
}ufs;
int ans[N+1],sum[N+1];
struct frac{
	int a,b;
	frac(int x,int y){a=x,b=y;}
	friend bool operator<(frac x,frac y){
		return x.a*y.b<y.a*x.b;
	}
};
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)read(b[i]);
	for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
	set<pair<frac,int> > st;
	for(int i=1;i<=n;i++)st.insert(mp(frac(b[i],1),i)),ans[i]=sum[i]=b[i];
	ufs.init();
	while(st.size()){
		int x=st.begin()->Y;
		int hsc=ufs.root(x+1)-1,ycx=ufs.root(a[hsc]+1)-1;
//		cout<<hsc<<" "<<ycx<<" "<<sum[hsc]<<" "<<sum[ycx]<<"!\n";
		st.erase(st.begin());
		if(ycx)st.erase(st.find(mp(frac(sum[ycx],ufs.sz[ycx+1]),ycx)));
		ans[ycx]+=ans[hsc]+sum[hsc]*ufs.sz[ycx+1];
		sum[ycx]+=sum[hsc];
		ufs.mrg(hsc+1,ycx+1);//确定关系 
		if(ycx)st.insert(mp(frac(sum[ycx],ufs.sz[ycx+1]),ycx));
	}
	cout<<ans[0];
	return 0;
}

顺便插一嘴,某 libra 姓神仙似乎提出了一个对平方 DP 的平衡树 + 启发式合并的平方二次对数的优化,不过似乎是假的,欢迎去 D 她(

posted @ 2020-10-01 17:30  ycx060617  阅读(458)  评论(3编辑  收藏  举报