Bzoj5289:[Hnoi2018]排列
[HNOI/AHOI2018]排列
题解
%20 搜索枚举全排列
%40 给好的搜索??随机化一下吧,人品可以的应该能拿到吧。
%60 距离正解差了一个log?发现是树然后暴力枚举???
%80 都有这个分数了,为什么不拿100???
正解:
很显然的是对于\(a[i]\),我们直接把\(i\)向\(a[i]\)连一条边
没有0时,无解,听说还要判环
n+1个点,n条边,连同==>树。
然后原题变成:
给出一棵以 0 为根的有根树,需要为非 0 顶点标号 1∼n ,并且满足父亲比自己先标号。每个节点有点权,树的价值为点权乘标号的和。求树最大的价值。
一个很显然的贪心就是如果当前节点权值很小,且它的父亲被选取了,那么优先给它标号。
考虑如果 \(u\) 有父亲,显然当他的父亲被选之后马上就会选 \(u\) ,也就是说父子间的编号一定是相邻的。我们可以将 \(u\) 的答案并在他的父亲中。
同样的,对于两个不同的“块”,也是如此。
考虑一个长度为 \(l1\) 的序列 \(A\) 和一个长度为 \(l_2\) 的序列 \(B\) ,
序列前面已经安排好了 \(loc\) 个。考虑 \(AB\) 和 \(BA\) 两种合并后的序列的答案:
\(W_{AB}=\sum_{i=1}^{l_1}(i+loc)w_{A_i}+\sum_{i=1}^{l_2}(i+loc+l_1)w_{B_i}\)
\(W_{BA}=\sum_{i=1}^{l_2}(i+loc)w_{B_i}+\sum_{i=1}^{l_1}(i+loc+l_2)w_{A_i}\)
如果 \(W_{AB}> W_{BA}\Rightarrow \frac{\sum_{i=1}^{l_1}w_{A_i}}{l_1}<\frac{\sum_{i=1}^{l_2}w_{B_i}}{l_2}\)
也就是平均权值小的放前面答案会更优。
Code
但是不吸氧要TLEqwq。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#define ll long long
using namespace std;
const ll N=5e5+5;
ll n,a[N],fa[N],sz[N];
ll w[N],flag=1;
struct node{
ll id,vi,sz;
node(ll _id=0,ll _vi=0,ll _sz=0){id=_id,vi=_vi,sz=_sz;}
bool operator < (const node &b) const {return vi*b.sz>b.vi*sz;}
};
priority_queue<node>q;
ll read(){
ll x=0,w=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*w;
}
ll find(ll x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main(){
n=read();
for(ll i=1;i<=n;i++){
a[i]=read();if(a[i]==0)flag=0;
}if(flag){cout<<"-1"<<endl;return 0;}
ll ans=0;ll len=0;
for(ll i=1;i<=n;i++){
w[i]=read();
q.push(node(i,w[i],1));
sz[i]=1;ans+=w[i];
}
for(ll i=0;i<=n;i++)fa[i]=i;
while(!q.empty()){
node t=q.top();q.pop();
if(sz[t.id]!=t.sz)continue;
if(find(a[t.id])==0){
ans+=len*w[t.id];fa[t.id]=0;len+=t.sz;
}
else {
ll tmp=find(a[t.id]);
ans+=w[t.id]*sz[tmp];fa[t.id]=tmp;
w[tmp]+=w[t.id];sz[tmp]+=sz[t.id];
q.push(node(tmp,w[tmp],sz[tmp]));
}
}cout<<ans<<endl;
return 0;
}