1. 程式人生 > >洛谷 5290 [十二省聯考2019]春節十二響——堆

洛谷 5290 [十二省聯考2019]春節十二響——堆

turn dfs 啟發式 const namespace .org problem spa 題目

題目:https://www.luogu.org/problemnew/show/P5290

考場上想到了一個子樹裏如果有多個 “段” 準備和其他位置的 “段” 拼在一起,那麽這個子樹裏的這些 “段” 一定兩兩間互相有父子關系。

準備設計一個 DP ,但覺得很難弄。比如很難存下狀態,因為還要存 “有幾個待合並的“段”” 、“那些“段”的最大值是什麽” 之類的。所以就只寫了 60 分。

技術分享圖片
#include<cstdio>
#include
<cstring> #include<algorithm> #define ll long long using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>9||ch<0){if(ch==-)fx=0;ch=getchar();} while(ch>=0&&ch<=9)ret=ret*10+ch-0,ch=getchar(); return fx?ret:-ret; } ll Mx(ll a,ll b){
return a>b?a:b;} ll Mn(ll a,ll b){return a<b?a:b;} const int N=2e5+5; int n,hd[N],xnt,to[N],nxt[N],cd[N],c[N]; void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;cd[x]++;} namespace S1{ const int K=20,M=(1<<16)+5; const ll INF=2e10; int tim,dfn[K],ot[K],bin[K],t[M],tp[K];ll dp[M];
void ini_dfs(int cr) { dfn[cr]=++tim; for(int i=hd[cr];i;i=nxt[i]) ini_dfs(to[i]); ot[cr]=tim; } bool chk(int x,int y) { return (dfn[x]>=dfn[y]&&dfn[x]<=ot[y])||(dfn[y]>=dfn[x]&&dfn[y]<=ot[x]);} void solve() { ini_dfs(1); bin[0]=1; for(int i=1;i<=n;i++)bin[i]=bin[i-1]<<1; for(int s=0;s<bin[n];s++) { int tot=0,mx=0; bool fg=0; for(int i=1;i<=n;i++) if(s&bin[i-1]) { for(int j=1;j<=tot;j++) if(chk(i,tp[j])){fg=1;break;} if(fg)break; tp[++tot]=i; mx=Mx(mx,c[i]); } if(!fg)t[s]=mx; } for(int s=1;s<bin[n];s++) { dp[s]=(t[s]?t[s]:INF); for(int d=(s-1)&s;d;d=(d-1)&s) dp[s]=Mn(dp[s],dp[d]+dp[s^d]); //if(t[d]) dp[s]=Mn(dp[s],t[d]+dp[s^d]); } printf("%lld\n",dp[bin[n]-1]); } } namespace S2{ int a[N],b[N],ta,tb;ll ans; bool cmp(int u,int v){return u>v;} void dfsa(int cr,int fa) { a[++ta]=c[cr]; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=fa) dfsa(v,cr); } void dfsb(int cr,int fa) { b[++tb]=c[cr]; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=fa) dfsb(v,cr); } void solve() { if(cd[1]==1) { for(int i=1;i<=n;i++)ans+=c[i]; printf("%lld\n",ans); return; } dfsa(to[hd[1]],1); dfsb(to[nxt[hd[1]]],1); sort(a+1,a+ta+1,cmp); sort(b+1,b+tb+1,cmp); for(int i=1,lm=Mn(ta,tb);i<=lm;i++) ans+=Mx(a[i],b[i]); if(ta<tb){for(int i=ta+1;i<=tb;i++)ans+=b[i];} else {for(int i=tb+1;i<=ta;i++)ans+=a[i];} printf("%lld\n",ans+c[1]); } } int main() { freopen("spring.in","r",stdin); freopen("spring.out","w",stdout); n=rdn(); for(int i=1;i<=n;i++)c[i]=rdn(); for(int i=2,d;i<=n;i++) d=rdn(), add(d,i); if(n<=16){S1::solve();return 0;} bool fg=0; for(int i=2;i<=n;i++) if(cd[i]>1){fg=1;break;} if(!fg&&cd[1]<=2){S2::solve();return 0;} return 0; }

其實從 “鏈” 的部分受到啟發,如果兩個部分互相沒有父子關系,就是把最大值和最大值放在一段,次大值和次大值放在一段這樣貪心。

那麽在樹上也可以貪心(而不是 DP ),不用管 “有幾個待合並的段” ,直接把子樹裏的所有已有的 “段” 都視作待合並的,那麽就用大根堆維護子樹裏的 “段”。在 dfs 樹的時候,合並兩個子樹就用堆的啟發式合並即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>9||ch<0){if(ch==-)fx=0;ch=getchar();}
  while(ch>=0&&ch<=9)ret=ret*10+ch-0,ch=getchar();
  return fx?ret:-ret;
}
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int N=2e5+5;
int n,c[N],hd[N],xnt,to[N],nxt[N],rt[N],tot,tp[N];
priority_queue<int> q[N];
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void dfs(int cr)
{
  for(int i=hd[cr],v;i;i=nxt[i])
    {
      dfs(v=to[i]);
      if(q[rt[cr]].size()<q[rt[v]].size())
    swap(rt[cr],rt[v]);
      int r0=rt[cr], r1=rt[v], top=0;
      while(q[r1].size())
    {
      int c0=q[r0].top(), c1=q[r1].top();
      q[r0].pop(); q[r1].pop();
      tp[++top]=Mx(c0,c1);
    }
      for(int j=1;j<=top;j++)q[r0].push(tp[j]);
    }
  if(!rt[cr])rt[cr]=++tot;
  q[rt[cr]].push(c[cr]);
}
int main()
{
  n=rdn();
  for(int i=1;i<=n;i++)c[i]=rdn();
  for(int i=2,d;i<=n;i++)
    d=rdn(), add(d,i);
  dfs(1); ll ans=0; int cr=rt[1];
  while(q[cr].size())ans+=q[cr].top(), q[cr].pop();
  printf("%lld\n",ans);
  return 0;
}

洛谷 5290 [十二省聯考2019]春節十二響——堆