洛谷 P4437

题解咕到了现在。。

洛谷题目页面传送门

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

(ninleft[1,5 imes10^5 ight])

首先建图。如果有环的话肯定就无解,否则不难发现一定是一个森林。于是就转化成了 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),那么显然有 (sumlimits_{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]<<"!
";
		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 她(

原文地址:https://www.cnblogs.com/ycx-akioi/p/Luogu-P4437.html