1. 程式人生 > >loj2509 hnoi2018排列

loj2509 hnoi2018排列

put || ons 情況 nlog AR 其他 過去 back

題意:對於a數組,求它的一個合法排列的最大權值。合法排列:對於任意j,k,如果a[p[j]]=p[k],那麽k<j。

權值:sigma(a[p[i]]*i)。n<=50W。

標程:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 ll read()
 5 {
 6    ll x=0,f=1;char ch=getchar();
 7    while (ch<0||ch>9) {if (ch==-) f=-1;ch=getchar();}
8 while (ch>=0&&ch<=9) x=(x<<1)+(x<<3)+ch-0,ch=getchar(); 9 return x*f; 10 } 11 const int N=500005; 12 vector<ll> vec[N]; 13 ll ans,sum[N],he[N],cnt,head[N],n,a[N],fa[N],w[N],sz[N],cn,vis[N],tail[N],f[N]; 14 int find(int x){return x==f[x]?x:f[x]=find(f[x]);} 15
struct _node{ll sum,he,sz,id;_node(ll A,ll B,ll C,ll D){sum=A;he=B;sz=C;id=D;}}; 16 struct cmp{ 17 bool operator () (const _node &A,const _node &B) 18 {return A.he*B.sz>B.he*A.sz||A.he*B.sz==B.he*A.sz&&A.sz<B.sz;} 19 }; 20 priority_queue<_node,vector<_node>,cmp> q;
21 int main() 22 { 23 n=read(); 24 for (int i=1;i<=n;i++) f[i]=i; 25 for (int i=1;i<=n;i++) 26 { 27 a[i]=read(); 28 if (0<a[i]&&a[i]<=n) vec[a[i]].push_back(i),fa[i]=a[i]; else fa[i]=-1; 29 } 30 for (int i=1;i<=n;i++) w[i]=read(),q.push(_node(sum[i]=w[i],he[i]=w[i],sz[i]=1,i)); 31 while (!q.empty()) 32 { 33 int x=q.top().id,fx=fa[find(x)];q.pop(); 34 if (vis[x]) continue; vis[x]=1; //dijkstra的思想,肯定先訪問最後一次合並過的點,其他過去版本直接continue,這樣就不用再記錄一個del的堆。 35 if (fx==-1) 36 { 37 ans+=sum[x]+cn*he[x]; 38 for (int i=0;i<vec[x].size();i++) fa[vec[x][i]]=-1; 39 cn+=sz[x]; 40 } 41 else { 42 if (vis[fx]) return puts("-1"),0; 43 for (int i=0;i<vec[x].size();i++) f[vec[x][i]]=find(x); 44 sum[fx]+=sum[x]+he[x]*sz[fx];he[fx]+=he[x];sz[fx]+=sz[x]; 45 q.push(_node(sum[fx],he[fx],sz[fx],fx)); 46 } 47 } 48 printf("%lld\n",ans); 49 return 0; 50 }

易錯點:1.居然碰到了yhx欽定的最難調錯誤沒有之一,記!

return A.he*B.sz>B.he*A.sz||A.he*B.sz==B.he*A.sz&&A.sz<B.sz;
如果不判定相等的情況就不一定取到最後一個。

2.判斷無解:vis表示已經被合並/刪除的節點,重新連爸爸後爸爸應該是沒有被刪除的,如果vis[fa]=1,那麽必然矛盾。

3.更改父親的操作如果用vector暴力加,時間復雜度會到O(n^2)。用並查集保存同父親的點是最快的做法。

題解:堆+並查集+建樹+貪心

做法好神。如果a[p[j]]=p[k],那麽k<j:也就是說比如a[1]=3,那麽在排列中3一定在1前面。對於1<=a[i]<=n的點,連邊a[i]->i,表示先取a[i],再取i。那麽就形成了一棵樹,如果有環必然無解。

這棵樹肯定是每次取一個沒有父親的點作為p[i]。基於貪心,i越小,選越小的w[i]更優。

因此我們每次用堆/set維護權值最小的點,如果它沒有父親肯定直接取走,反之和其父親合並,表示如果取走父親後接下來肯定就取它。

合並之後,該點的兒子都連邊向它父親,也就是說fa[son[x]]=fa[x],可以用並查集維護。這樣這個點的權值用sigma/size來代替。

可以證明:1.比較兩個點的sigma/size就相當於比較它們sigma(i*w[p[i]])的權值。

2.對於同一個點,sigma/size隨著合並不嚴格單調減。(設he1/sz1<he2/sz2,那麽1向2合並,必然有he2/sz2>(he1+he2)/(sz1+sz2)。化簡he2/sz2>(he1+he2)/(sz1+sz2),則he1*sz2<he2*sz1,同假設成立)

時間復雜度O(nlogn+na(n))。

loj2509 hnoi2018排列