今天状态好差啊~ 写一发题解压压惊,非常感谢左侧 ← HSZX TS_Hugh 大佬教我树形DP..
题面
解题思路
题中的依赖关系显然是一种树的结构,因为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 !!!