1. 程式人生 > 實用技巧 >題解 HDU6566 【The Hanged Man】

題解 HDU6566 【The Hanged Man】

題意為給定 \(n\) 個點的樹,每個節點為一個物品,有體積和價值,選物品必須滿足不相鄰,即選出一個獨立集,求對於 \(\forall i \in [1,m]\),容量為 \(i\) 時的揹包最大價值的方案數。

\(1 \leqslant n \leqslant 50,1 \leqslant m \leqslant 5000\)

暴力就是直接樹形揹包,\(f_{i,j,0/1}\) 為在 \(i\) 的子樹內,容量為 \(j\),是否選 \(i\) 的方案數,但複雜度為 \(O(nm^2)\),無法接受。

考慮用 \(dfs\) 序轉移來優化,但是發現從 \(dfs\) 序中 \(i\) 轉移到 \(i+1\)

時,若 \(i+1\) 對應的節點在 \(i\) 對應的節點的上方時,就可能不知道 \(i+1\) 的父親選擇的情況:

因此還需知道像圖中 \(i+1\) 的父親那樣的轉折點的選擇情況,直接狀壓一個點到根節點路徑上的所有轉折點不現實,會被鏈卡成狀態數為 \(O(2^n)\)

考慮優化狀態數,先進行重鏈剖分,剖分重鏈時優先遍歷輕兒子,因為最後才遍歷重兒子,所以一個點到根節點的所有轉折點都是重鏈的鏈底,那麼再進行狀壓,狀態數就為 \(O(2^{\log n})=O(n)\) 了。

\(f_{i,S,j}\) 為考慮到 \(dfs\) 序中第 \(i\) 個點,到根的轉折點的狀態為 \(S\)

,容量為 \(j\) 的方案數,轉移是 \(O(1)\) 的,複雜度為 \(O(n^2m)\)

另外一個做法是進行點分治,將每次的分治中心作為轉折點來狀壓,這樣狀態數也是 \(O(n)\) 的。

#include<bits/stdc++.h>
#define maxn 210
#define maxm 5010
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int T,n,m,cnt,now;
int w[maxn],v[maxn],id[maxn];
int siz[maxn],fa[maxn],son[maxn],top[maxn],rev[maxn];
vector<int> t[maxn];
struct edge
{
    int to,nxt;
}e[maxn];
int head[maxn],edge_cnt;
void add(int from,int to)
{
    e[++edge_cnt]={to,head[from]},head[from]=edge_cnt;
}
struct node
{
    int v;
    ll cnt;
}f[2][maxn][maxm],ans[maxm];
node operator +(const node &x,const int &val)
{
    return {x.v+val,x.cnt};
}
void mx(node &x,node y)
{
    if(x.v<y.v) x=y;
    else if(x.v==y.v) x.cnt+=y.cnt;
}
void dfs_son(int x,int fath)
{
    fa[x]=fath,siz[x]=1;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to;
        if(y==fath) continue;
        dfs_son(y,x),siz[x]+=siz[y];
        if(siz[y]>siz[son[x]]) son[x]=y;
    }
}
void dfs_chain(int x,int tp)
{
    top[x]=tp,rev[++cnt]=x;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to;
        if(y==fa[x]||y==son[x]) continue;
        dfs_chain(y,y);
    }
    if(son[x]) dfs_chain(son[x],tp);
}
void clear()
{
    edge_cnt=cnt=0,now=1;
    memset(f,0,sizeof(f));
    memset(ans,0,sizeof(ans));
    memset(son,0,sizeof(son));
    memset(head,0,sizeof(head));
    for(int i=1;i<=n;++i) t[i].clear();
}
void solve(int num)
{
    read(n),read(m),clear();
    for(int i=1;i<=n;++i) read(w[i]),read(v[i]);
    for(int i=1;i<n;++i)
    {
        int x,y;
        read(x),read(y);
        add(x,y),add(y,x);
    }
    dfs_son(1,0),dfs_chain(1,1);
    for(int i=1;i<=n;++i)
    {
        int p=i;
        t[i].push_back(i);
        while(fa[top[p]]) p=fa[top[p]],t[i].push_back(p);
    }
    f[0][0][0]={0,1};
    for(int i=1;i<=n;++i)
    {
        for(int s=0;s<(1<<t[rev[i]].size());++s)
            for(int j=0;j<=m;++j)
                f[now][s][j]={0,0};
        int fath=20;
        for(int j=0;j<t[rev[i-1]].size();++j)
        {
            int p=t[rev[i-1]][j];
            if(fa[rev[i]]==p) fath=j;
            id[j]=-1;
            for(int k=0;k<t[rev[i]].size();++k)
                if(p==t[rev[i]][k])
                    id[j]=k;
        }
        for(int s=0;s<(1<<t[rev[i-1]].size());++s)
        {
            int S=0;
            for(int j=0;j<t[rev[i-1]].size();++j)
                if((s&(1<<j))&&id[j]!=-1)
                    S|=1<<id[j];
            for(int j=0;j<=m;++j)
            {
                if(!f[now^1][s][j].cnt) continue;
                mx(f[now][S][j],f[now^1][s][j]);
                if((s&(1<<fath))||j+w[rev[i]]>m) continue;
                mx(f[now][S+1][j+w[rev[i]]],f[now^1][s][j]+v[rev[i]]);
            }
        }
        now^=1;
    }
    for(int s=0;s<(1<<t[rev[n]].size());++s)
        for(int i=1;i<=m;++i)
            mx(ans[i],f[now^1][s][i]);
    printf("Case %d:\n",num);
    for(int i=1;i<=m;++i) printf("%lld%c",ans[i].cnt," \n"[i==m]);
}
int main()
{
    read(T);
    for(int i=1;i<=T;++i) solve(i);
    return 0;
}