bzoj4182: Shopping【點分治+單調隊列優化多重揹包】

題目大意:

給定一棵有 n 個點的樹,第 i 個點有 di 件商品,價格爲 ci,價值爲 wi。
你手頭有 m 塊錢,且你要保證你買過的點在樹上互相連通,問買到的物品的總價值最多是多少。
1 ≤ n ≤ 500, 1 ≤ m ≤ 4000, di ≤ 100。

解題思路:

如果直接樹形dp是O(nm2d) 的,顯然過不了

考慮如果強制要選一個點怎麼做。
相當於以該點爲根,那麼就是一個有依賴的多重揹包(就是選了子樹根才能選子樹中的點),直接在樹上做也是O(nm2) ,這裏有一個套路:先搞出dfs序,設f[i]表示考慮了dfs序後i個點,花了j的錢的答案,如果不選就跳過整棵子樹的區間,如果選就從i+1轉移過來即可。這樣就轉到了序列上,可以用單調隊列優化到O(nm) ,詳見這裏

如果我們用點分治,每次把重心設爲必選點,總複雜度就是 O(nmlogn) 的,而且可以保證正確性。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=505,M=4005;
int n,m,ans,w[N],c[N],d[N];
int root,totsize,idx,size[N],maxsub[N],id[N],vis[N],q[N],f[N][M];
int tot,first[N],nxt[N<<1],to[N<<1];
void add(int x,int y)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y;
}
void getroot(int u,int fa)
{
    size[u]=1,maxsub[u]=0;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(v==fa||vis[v])continue;
        getroot(v,u),size[u]+=size[v];
        if(size[v]>maxsub[u])maxsub[u]=size[v];
    }
    maxsub[u]=max(maxsub[u],totsize-size[u]);
    if(maxsub[u]<maxsub[root])root=u;
}
void dfs(int u,int fa)
{
    id[++idx]=u,size[u]=1;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(v==fa||vis[v])continue;
        dfs(v,u),size[u]+=size[v];
    }
}
void solve(int u)
{
    vis[u]=1,idx=0,dfs(u,0);
    memset(f[idx+1],0,sizeof(f[idx+1]));
    for(int i=idx;i;i--)
    {
        int u=id[i],p=c[u],h=w[u],cnt=d[u],head=1,tail=0;
        for(int j=0;j<=m;j++)f[i][j]=f[i+size[u]][j];
        for(int j=0;j<p;j++)
        {
            int head=1,tail=0;
            for(int k=0;j+k*p<=m;k++)
            {
                while(head<=tail&&q[head]<k-cnt)head++;
                if(head<=tail)f[i][j+k*p]=max(f[i][j+k*p],f[i+1][j+q[head]*p]+(k-q[head])*h);
                while(head<=tail&&f[i+1][j+k*p]-k*h>=f[i+1][j+q[tail]*p]-q[tail]*h)tail--;
                q[++tail]=k;
            }
        }
    }
    for(int i=1;i<=m;i++)ans=max(ans,f[1][i]);
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(vis[v])continue;
        maxsub[root=0]=totsize=size[v],getroot(v,u),solve(root);
    }
}
int main()
{
    freopen("lx.in","r",stdin);
    freopen("lx.out","w",stdout);
    for(int T=getint();T;T--)
    {
        ans=tot=0;
        memset(first,0,sizeof(first));
        memset(vis,0,sizeof(vis));
        n=getint(),m=getint();
        for(int i=1;i<=n;i++)w[i]=getint();
        for(int i=1;i<=n;i++)c[i]=getint();
        for(int i=1;i<=n;i++)d[i]=getint();
        for(int i=1;i<n;i++)
        {
            int x=getint(),y=getint();
            add(x,y),add(y,x);
        }
        totsize=maxsub[root=0]=n,getroot(1,0),solve(root);
        printf("%d\n",ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章