2019.03.01【HNOI2018/AHOI2018】【BZOJ5289】【洛谷P4437】排列(贪心)

BZOJ传送门

洛谷传送门


解析:

写这个题面的真的厉害。。。

简单叙述一下题意:

给出一个序列 a a a,一个下标的排列 p p p是合法的,当且仅当对于每个 j j j,当前数为 a p [ j ] a_{p[j]} ap[j],且 a p [ j ] a_{p[j]} ap[j]的值没有在下标数组 p p p的第 j j j个位置及以后出现过。

可以这么理解, a a a是原数组, p p p下标数组。

换句话说, a j a_j aj下标数组中要比 j j j先出现。

其他题解这里说的很模糊,出现?什么出现?到底是出现在原数组还是下标数组?

然后给出权值数组 w w w,定义一个合法下标数组有贡献,且贡献为 ∑ i = 1 n i w p [ i ] \sum\limits_{i=1}^niw_{p[i]} i=1niwp[i],求最大贡献

换句话说,原数组和权值数组的一个值是捆绑在了一起的。

现在考虑判断合法性。

刚才转化题意得到 a j a_j aj下标数组中要比 j j j先出现。

则我们连边 a j → j a_j\rightarrow j ajj来表示这个限制。

显然最后建出来的图如果有环就会产生矛盾。

发现这是一个有 n + 1 n+1 n+1个点 [ 0 , n ] [0,n] [0,n],和 n n n条边(显然不可能有重边)的图,所以只需要一次 d f s 0 dfs0 dfs0就能够判断是否有环了。

不然的话,图就是一棵树。

现在考虑怎么求最大的贡献

考虑从前面的,也就是系数小的开始贪心。

考虑当前权值最小点 i i i

如果 i i i没有父亲我们就肯定直接选择 i i i了。

如果 i i i有父亲,那么选择了父亲之后肯定是立即选择它,所以我们可以将它和父亲合并,记录 s i z siz siz和权值。

最后显然就是在一堆序列里面贪心做拼接。

考虑长度为 m 1 m_1 m1的序列 1 1 1和长度为 m 2 m_2 m2的序列 b b b,我们尝试在 a b ab ab b a ba ba两种拼接中找出最大值。

W a b = ∑ i = 1 m 1 i w a i + ∑ i = 1 m 2 ( i + m 1 ) w b i W b a = ∑ i = 1 m 2 i w b i + ∑ i = 1 m 1 ( i + m 2 ) w a i \begin{aligned} W_{ab}&=&&\sum_{i=1}^{m_1}iw_{a_i}+\sum_{i=1}^{m_2}(i+m_1)w_{b_i}\\ W_{ba}&=&&\sum_{i=1}^{m_2}iw_{b_i}+\sum_{i=1}^{m_1}(i+m_2)w_{a_i}\\ \end{aligned} WabWba==i=1m1iwai+i=1m2(i+m1)wbii=1m2iwbi+i=1m1(i+m2)wai

做差得到:

W a b − W b a = m 1 W b − m 2 W a W a b > W b a    ⟺    W b m 2 > W a m 1 W_{ab}-W_{ba}=m_1W_b-m_2W_a\\W_{ab} > W_{ba}\iff \frac{W_b}{m_2} > \frac{W_a}{m_1} WabWba=m1Wbm2WaWab>Wbam2Wb>m1Wa

换句话说,我们在前面要尽可能选择平均值较小的序列。

堆或者平衡树维护一下所有序列中最小平均值就行了。

比较还是化除法为乘法,避免精度误差。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

struct data{
	int u,siz;
	ll val;
	friend bool operator<(cs data &a,cs data &b){return a.val*b.siz>b.val*a.siz;}
};
priority_queue<data> s;

cs int N=5e5+5;

int n,cnt;
int fa[N];
vector<int> son[N];
int siz[N],bel[N];
ll w[N];

bool vis[N];
void dfs(int u){
	vis[u]=true;
	++cnt;
	for(int re e=0;e<son[u].size();++e)
	if(vis[son[u][e]])puts("-1"),exit(0);
	else dfs(son[u][e]);
}

inline int getfa(int u){
	while(u^bel[u])u=bel[u]=bel[bel[u]];
	return u;
}

ll ans=0;
signed main(){
	n=getint();
	for(int re i=1;i<=n;++i)son[fa[i]=getint()].push_back(i);
	dfs(0);
	if(cnt<=n)return puts("-1"),0;
	siz[0]=1;
	for(int re i=1;i<=n;++i){
		w[i]=getint();
		siz[i]=1;
		bel[i]=i;
		s.push((data){i,1,w[i]});
	}
	while(!s.empty()){
		data tmp=s.top();s.pop();
		if(tmp.siz^siz[getfa(tmp.u)])continue;
		int u=getfa(tmp.u);
		int p=bel[u]=getfa(fa[u]);
		ans+=siz[p]*w[u];siz[p]+=siz[u],w[p]+=w[u];
		if(p)s.push((data){p,siz[p],w[p]});
	}
	cout<<ans<<"\n";
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值