题目: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; }