2017 CDQZ 联训 Day9 T2 可怜与超市

今天状态好差啊~ 写一发题解压压惊,非常感谢左侧 ← HSZX TS_Hugh 大佬教我树形DP..

题面

题目描述

样例1

样例2

解题思路

题中的依赖关系显然是一种树的结构,因为n比较小而b巨大无比,DP时可以把n设为状态的一维。令g[x][i] 表示在以x为根的子树中,不使用优惠券,购买i个物品要花费的最小代价。类似地,令f[x][i] 表示在以x为根的子树中,至少使用一张优惠券,购买i个物品的最小代价。

递归处理每棵子树,然后依次把当前子树的信息与根节点当前记录的信息合并。令tmp[i]表示在根节点x及x的前k-1棵子树中购买i个物品的最小代价,g[u][i] 表示在以u为根的子树中购买i个物品的最小代价(u为x的第k棵子树),用这两个数组更新数组g[x],f[x]也是同理。

因为优惠券的使用有依赖关系,所以f[x]的合并稍微特殊一点,先不考虑根节点的贡献,在所有子树合并完成之后直接把根节点的那个物品加入到f[x]数组的所有选取方案中。这样就实现了,让f[x]中的所有方案都必须购买根节点x所表示的物品。

这个方法好像看起来是O(n3) 的,但实际上却是O(n2) 的。当且仅当处理lca(u,v)时,u和v节点的信息才被合并。也就是说,每个点对的信息只会被合并一次,树上有O(n2) 个点对,所以时间爱你复杂度为O(n2)

代码

此代码常数巨大无比,因为我最开始爆了int,发现要开long long,懒得改了,直接写了#define ing long long。。

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cctype>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=5000+10;
typedef long long LLint;
#define int long long
LLint f[maxn][maxn],g[maxn][maxn],n,b;
vector<int>son[maxn];int c[maxn],d[maxn],siz[maxn];
inline int geti(){
    int ans=0,flag=0;char c=getchar();
    while(!isdigit(c)){flag|=c=='-';c=getchar();}
    while( isdigit(c)){ans=ans*10+c-'0';c=getchar();}
    return flag?-ans:ans;
}
void inline puti(int x){
    if(x<0)x=-x,putchar('-');
    if(x>9)puti(x/10); putchar(x%10+'0');
}
LLint tmp[maxn];
void sol(int x){
    g[x][0]=0;g[x][1]=c[x];siz[x]=1;//not use
    f[x][0]=0;//use
    for(int i=0;i<son[x].size();i++){//考虑所有子树
        int u=son[x][i];sol(u);
        memcpy(tmp,g[x],(siz[x]+1)*sizeof(LLint));//复制数据,防止重复影响
        for(int v=0;v<=siz[x];v++)
            for(int j=0;j<=siz[u];j++)
                g[x][v+j]=min(g[x][v+j],tmp[v]+g[u][j]);
        memcpy(tmp,f[x],(siz[x]+1)*sizeof(LLint));//复制数据,同理
        for(int v=0;v<siz[x];v++)
            for(int j=0;j<=siz[u];j++)
                f[x][v+j]=min(f[x][v+j],
                tmp[v]+min(f[u][j],g[u][j]));
        siz[x]+=siz[u];//表示当前已经和根节点合并的连通块大小
    }
    for(int i=siz[x];i>=1;i--){//把x加到所有方案中
        f[x][i]=f[x][i-1]+c[x]-d[x];
    }
}
signed main(){
    freopen("supermarket.in","r",stdin);
    freopen("supermarket.out","w",stdout);
    memset(g,0x7f,sizeof(g));
    memset(f,0x7f,sizeof(f));
    n=geti();b=geti();
    c[1]=geti();d[1]=geti();
    for(int i=2;i<=n;i++){
        c[i]=geti();d[i]=geti();
        int x=geti();
        son[x].push_back(i);
    }
    sol(1);//dfs
    for(int i=n;i>=0;i--){//O(n)扫一遍即可
        int cost=min(f[1][i],g[1][i]);
        if(cost<=b){
            printf("%d\n",i);
            break;
        }
    }
    return 0;
}

[2017.12.28] 来自TS_Hugh 大佬的卡常神技

char xB[(1<<15)+10],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)

本机实测,读入107 个整数,比普通的getchar() 读入优化快了0.2s !!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章