[JSOI2008]魔兽地图DotR 做题心得
马上要 JSOI2021 了,来看看以往的题
对于树形dp题,就是要想一下:基本策略是什么?然后根据基本策略,想一想dp要维护什么,怎么转移。再加上一些套路之类的东西,维护一下。像本题用到的就是树上背包。
还有就是,像树上花费多少价格购买甚么东西使得获利最大,长成这样的题,一般都是树上背包搞一搞。
题解
一些记号
\(n,m\) 是物品数与总钱数
我们定义 \(pow(u)\) 表示 \(u\) 的力量值,\(pri(u)\) 表示 \(u\) 的花费,\(lim(u)\) 表示 \(u\) 最多能搞多少个。
对于非基础装备,\(pri,lim\) 并不是直接给,但是我们可以在树上算一下:
\(pri(u)\) 就是它所需要的每个物品的价格乘以合成需要多少个物品,即
\[\sum\limits_{v\in son(u)} need(v)*pri(v) \]\(lim(u)\) 就是每个需要的物品的 \(lim/need\) 的 \(min\),最后再和 \(m/pri(u)\) (\(m\) 是总钱数)取一下 \(min\)
然后还有一些 dp 数组,后面再说
基本策略与dp设状态
我们肯定是,先买一堆基础物品,然后不断合上去
对于中间的某些物品,我们用一部分增加力量值,并用剩下的一部分用来合成更高级的东西。注意我们并不一定全部用来合成,或者全部用来增加力量值。
然后贯穿全题的,还有一个重要的东西:钱。钱是一切的根源,但是我们的钱有限,最重点要规划的就是钱怎么用。
于是我们的dp就成型了:\(f(u,j,k)\) 表示 dp 到树上的点 \(u\),留 \(j\) 个传上去用来合成,现在花了 \(k\) 块钱,最大能获取的力量值。
dp转移
这个状态简单想一下大概是能转移的,考虑一下 \(f(u,j,k)\) 怎么转移
我们枚举一共准备造 \(J\) 个 (\(J\ge j\)),显然自己这里合成不需要花钱,所以钱(\(k\) 块钱)全部花给儿子,假设这部分能获得的最大的力量值是 \(P_{son}\)。然后贡献的力量值就是 \(P_{son}+(J-j)*pow(u)\)
(后面 \(J-j\) 是因为要留 \(j\) 个上传)
现在的问题是 \(P_{son}\) 怎么算。假设每个儿子的 \(f\) 我们都能算出来,其实这就是一个合并背包的问题了,另开一个 dp 数组 \(g\) 来搞定这个问题。
设 \(g[t][k]\) 表示,考虑前 \(t\) 个儿子,花了 \(k\) 块钱,最大力量值。假设第 \(t\) 个儿子是 \(v\),那么 \(v\) 的义务是:上传 \(j*need(v)\) 个来给 \(u\) ,用来合成 \(j\) 个 \(u\)。而它享有的权利是,获得 \(k\) 块钱中的一部分。
于是我们枚举一下 \(k\) 块钱中分多少给它,得转移:
然后假设 \(u\) 的儿子总数为 \(T\),显然 \(P_{son}\) 就是 \(g[T][k]\)
到最后求完 \(f\) 之后,你会发现其实原树不连通,对于每个根都要求一遍,然后还要合并一下。
但是其实这里的转移和 \(g\) 就几乎没区别了,除了没有责任义务的限制。抄着写,改改就行了。也是一个背包合并。
代码
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 102
#define M 2003
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
#define p_b push_back
#define sz(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define iter(a,p) (a.begin()+p)
#define PUT(a,n) F(i,1,n) printf("%d ",a[i]); puts("");
int I() {char c=getchar(); int x=0; int f=1; while(c<‘0‘ or c>‘9‘) f=(c==‘-‘)?-1:1,c=getchar(); while(c>=‘0‘ and c<=‘9‘) x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
template <typename T> void Rd(T& arg){arg=I();}
template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
class Graph
{
public:
struct edge{int v,w,nx;} e[N<<1];
int head[N]; int ecnt=-1;
void clear() {MEM(e,-1); MEM(head,-1); ecnt=-1;}
void add(int u,int v,int w)
{
e[++ecnt]=(edge){v,w,head[u]}; head[u]=ecnt;
}
void add2(int u,int v,int w)
{
add(u,v,w),add(v,u,w);
}
int& st(int u) {return head[u];}
int& to(int i) {return e[i].v;}
int& nx(int i) {return e[i].nx;}
int& need(int i) {return e[i].w;}
}G;
int n,m;
bool type[N]; // 低级:0; 高级:1
int fa[N];
int power[N],price[N],lim[N];
void Input()
{
Rd(n,m);
G.clear(); MEM(fa,-1);
F(i,1,n)
{
power[i]=I();
char o[3]; scanf("%s",o);
if (o[0]==‘A‘)
{
type[i]=1;
int tot=I();
F(j,1,tot)
{
int v,need; Rd(v,need);
G.add(i,v,need); fa[v]=i;
}
}
else
{
type[i]=0;
Rd(price[i],lim[i]);
}
}
}
int f[N][N][M],g[N][M];
void DFS(int u)
{
if (!type[u])
{
lim[u]=min(lim[u],m/price[u]);
F(J,0,lim[u]) F(j,0,J)
{
f[u][j][J*price[u]]=(J-j)*power[u];
}
return;
}
lim[u]=1145141919;
Tra(i,u)
{
DFS(v);
price[u]+=price[v]*G.need(i);
lim[u]=min(lim[u],lim[v]/G.need(i));
}
lim[u]=min(lim[u],m/price[u]);
MEM(g,0xcf); g[0][0]=0;
D(J,lim[u],0)
{
int t=0;
Tra(i,u)
{
++t;
F(k,0,m) F(kk,0,k)
{
g[t][k]=max(g[t][k],g[t-1][kk]+f[v][J*G.need(i)][k-kk]);
}
}
F(j,0,J) F(k,0,m)
{
f[u][j][k]=max(f[u][j][k],g[t][k]+(J-j)*power[u]);
}
}
}
int h[N][M];
int mx[M];
void Sakuya()
{
MEM(f,0xcf); MEM(g,0xcf); MEM(h,0xcf);
h[0][0]=0;
int t=0;
F(i,1,n) if (fa[i]==-1)
{
DFS(i); ++t;
F(k,0,m)
{
mx[k]=0;
F(j,0,lim[i]) mx[k]=max(mx[k],f[i][j][k]);
}
F(k,0,m) F(kk,0,k)
{
h[t][k]=max(h[t][k],h[t-1][kk]+mx[k-kk]);
}
}
int ans=0;
F(i,0,m) ans=max(ans,h[t][i]);
printf("%d\n",ans);
}
void IsMyWife()
{
Input();
Sakuya();
}
}
#undef int //long long
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();
return 0;
}
注:
"背包合并"是我瞎口胡的一个说法,说着玩的,不知道它是否是一个专业的说法。