洛谷 P4437 [HNOI/AHOI2018]排列(贪心+堆,思维题)

题面传送门

开始 WA ycx 的遗产(bushi

首先可以将题目转化为图论模型:\(\forall i\) 连边 \(a_i\to i\),然后求图的一个拓扑序 \(b_1,b_2,\dots b_n\) 使得 \(\sum\limits_{i=1}^niw_{b_i}\) 最小。显然如果原图出现环就 \(-1\) 了。否则原图一定是一棵森林。

然后我就在那儿想各种乱搞,包括但不限于 DP、贪心之类,然后都假掉了

我们知道有个东西叫排序不等式,它是说 \(\forall a_1\leq a_2\leq a_3\leq\dots\leq a_n,b_1\leq b_2\leq b_3\leq\dots\leq b_n\) 都有 \(\sum\limits_{i=1}^na_ib_i\geq\sum\limits_{i=1}^na_ib_{p_i}\geq\sum\limits_{i=1}^na_ib_{n-i+1}\),其中 \(p\) 为 \(1\sim n\) 的任意一个排列。

将排序不等式应用于此题。如果设 \(a_i=i\),\(b\) 为 \(w\) 排好序的结果可得理论最大值为 \(\sum\limits_{i=1}^nib_i\)。换句话说,假设使 \(w_i\) 取到最小值的 \(i\) 为 \(x\),那么我们肯定贪心地想把 \(x\) 放在前面,越靠前越好。

不过问题是,\(a_x\) 不一定等于 \(0\),也就是说 \(x\) 不一定有能力成为拓扑序的第一个元素。这时候该怎么办呢?我们不考虑现在,我们考虑未来。我们定义当前状态下的“可选集合”为满足 \(a_x=0\) 或者 \(a_x\) 已经被加入拓扑序的 \(x\) 的集合,也就是说可选集合内的元素都可以成为拓扑序中的下一个元素。考虑什么时候 \(x\) 能够进入可选集合。显然要当 \(a_x\) 已经被加入拓扑序之后。而根据之前的推论我们肯定希望 \(x\) 越靠前越好,也就是说一旦 \(a_x\) 被加入拓扑序,我们就令 \(x\) 为拓扑序中下一个元素,相当于将 \(a_x\) 与 \(x\) 捆绑在了一起。

P.S:如果你对为什么 \(x\) 越靠前越好的证明感兴趣的话,可以从这里往下看:

假设 \(a_x\) 是第 \(s\) 个加入拓扑序的,\(x\) 是第 \(t(t>s+1)\) 个加入拓扑序的,第 \(i\) 个加入拓扑序的元素为 \(b_{i}\),那么我们显然有代价为 \(\sum\limits_{i=1}^siw_{b_i}+\sum\limits_{i=1}^{t-s-1}(s+i)w_{b_{i+s}}+tw_x+\sum\limits_{i=t+1}^niw_{b_i}\)

而倘若我们将第 \(x\) 个元素加入拓扑序的时间提前到 \(s+1\),其他元素加入拓扑序的时间保持不变,那么代价变为 \(\sum\limits_{i=1}^siw_{b_i}+(s+1)w_x+\sum\limits_{i=1}^{t-s-1}(s+i+1)w_{b_i+s}+\sum\limits_{i=t+1}^niw_{b_i}\)

二者做差可得 \(\Delta=\sum\limits_{i=1}^{t-s-1}w_{b_i+s}-(t-s-1)w_x\)

根据 \(w_x\) 为 \(\min\{w_i\}\) 可知 \(\dfrac{\sum\limits_{i=1}^{t-s-1}w_{b_i+s}}{t-s-1}\geq w_x\),换句话说,任意 \(t-s-1\) 个 \(w_i\) 的平均值 \(\geq w_x\)。

故 \(\Delta\geq 0\)。而显然将第 \(x\) 个元素加入拓扑序的时间提前到 \(s+1\) 依旧合法。故我们选择 \(x\) 越靠前越好。

还有一个问题,就是经过若干次合并后树上的每一个元素都是一个合并序列,也就是若干个形如 \(t_1,t_2,\dots t_m\),其中 \(t_i\) 拓扑序下一个必须是 \(t_{i+1}\) 的序列。此时我们就要探究合并序列的大小关系,考虑两个序列 \(x_1,x_2,\dots x_p\) 和 \(y_1,y_2,\dots,y_q\) 合并的大小关系。

  • 若 \(x\) 合并到 \(y\) 前面,\(W=\sum\limits_{i=1}^piw_{x_i}+\sum\limits_{i=1}^q(i+p)w_{y_i}\)
  • 若 \(y\) 合并到 \(x\) 前面,\(W=\sum\limits_{i=1}^qiw_{y_i}+\sum\limits_{i=1}^p(i+q)w_{x_i}\)

二者做差可得 \(\Delta=-q\times\sum\limits_{i=1}^pw_{x_i}+p\times\sum\limits_{i=1}^qw_{y_i}\)

我们不妨假设 \(\Delta\geq 0\),不等号两边同除 \(pq\) 可得 \(-\dfrac{\sum\limits_{i=1}^pw_{x_i}}{p}+\dfrac{\sum\limits_{i=1}^qw_{y_i}}{q}\geq 0\)。

即 \(\dfrac{\sum\limits_{i=1}^qw_{y_i}}{q}\geq \dfrac{\sum\limits_{i=1}^pw_{x_i}}{p}\)

也就是说我们选择将平均值大的序列合并到平均值小的序列的后面。

用堆维护即可。

复杂度线性对数。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
	#define FILE_SIZE 1<<23
	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
	inline void putc(char x){(*p3++=x);}
	template<typename T> void read(T &x){
		x=0;char c=getchar();T neg=0;
		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
		if(neg) x=(~x)+1;
	}
	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=5e5;
int n,a[MAXN+5],b[MAXN+5],vis[MAXN+5];
void dfs(int x){
	if(!(vis[x]^2)) puts("-1"),exit(0);
	if(!(vis[x]^1)) return;vis[x]=2;
	if(a[x]) dfs(a[x]);vis[x]=1;
}
int f[MAXN+5],siz[MAXN+5];ll sum[MAXN+5],val[MAXN+5];
int getf(int x){return (!~f[x])?x:f[x]=getf(f[x]);}
void merge(int x,int y){
	x=getf(x);y=getf(y);
	f[y]=x;sum[x]+=sum[y];
	val[x]+=val[y]+1ll*sum[y]*siz[x];
	siz[x]+=siz[y];
//	printf("merge %d %d %d %lld %lld\n",x,y,siz[x],sum[x],val[x]);
}
struct frac{
	ll x,y;
	frac(ll _x=0,ll _y=1):x(_x),y(_y){}
	bool operator <(const frac &rhs) const{
		return x*rhs.y<rhs.x*y;
	}
};
set<pair<frac,int> > st;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	for(int i=1;i<=n;i++) dfs(i);f[0]=-1;
	for(int i=1;i<=n;i++) f[i]=-1,siz[i]=1,sum[i]=val[i]=b[i];
	for(int i=1;i<=n;i++) st.insert(mp(frac(b[i],1),i));
	while(!st.empty()){
		pair<frac,int> pp=*st.begin();st.erase(st.begin());
		int id=pp.se,f1=getf(id),f2=getf(a[f1]);
		if(f2) st.erase(st.find(mp(frac(sum[f2],siz[f2]),f2)));
		merge(f2,f1);if(f2) st.insert(mp(frac(sum[f2],siz[f2]),f2));
	} printf("%lld\n",val[0]);
	return 0;
}
上一篇:js闭包


下一篇:ES6之解构赋值以及它的底层原理