zoukankan      html  css  js  c++  java
  • BZOJ1017魔兽地图DotR 樹形DP

    @(BZOJ)[樹形DP, 三維DP]

    Description

    DotR (Defense of the Robots) Allstars是一个风靡全球的魔兽地图,他的规则简单与同样流行的地图DotA
    (Defense of the Ancients) Allstars。DotR里面的英雄只有一个属性——力量。他们需要购买装备来提升自己的
    力量值,每件装备都可以使佩戴它的英雄的力量值提高固定的点数,所以英雄的力量值等于它购买的所有装备的力
    量值之和。装备分为基本装备和高级装备两种。基本装备可以直接从商店里面用金币购买,而高级装备需要用基本
    装备或者较低级的高级装备来合成,合成不需要附加的金币。装备的合成路线可以用一棵树来表示。比如,Sange
    and Yasha的合成需要Sange,Yasha和Sange and Yasha Recipe Scroll三样物品。其中Sange又要用Ogre Axe, Belt
    of Giant Strength和 Sange Recipe Scroll合成。每件基本装备都有数量限制,这限制了你不能无限制地合成某
    些性价比很高的装备。现在,英雄Spectre有M个金币,他想用这些钱购买装备使自己的力量值尽量高。你能帮帮他
    吗?他会教你魔法Haunt(幽灵附体)作为回报的。

    Input

    第一行包含两个整数,N (1 <= n <= 51) 和 m (0 <= m <= 2,000)。分别表示装备的种类数和金币数。装备
    用1到N的整数编号。接下来的N行,按照装备1到装备n的顺序,每行描述一种装备。每一行的第一个非负整数表示这
    个装备贡献的力量值。接下来的非空字符表示这种装备是基本装备还是高级装备,A表示高级装备,B表示基本装备
    。如果是基本装备,紧接着的两个正整数分别表示它的单价(单位为金币)和数量限制(不超过100)。如果是高
    级装备,后面紧跟着一个正整数C,表示这个高级装备需要C种低级装备。后面的2C个数,依次描述某个低级装备的
    种类和需要的个数。

    Output

    第一行包含一个整数S,表示最多可以提升多少点力量值。

    Sample Input

    10 59
    5 A 3 6 1 9 2 10 1
    1 B 5 3
    1 B 4 3
    1 B 2 3
    8 A 3 2 1 3 1 7 1
    1 B 5 3
    5 B 3 3
    15 A 3 1 1 5 1 4 1
    1 B 3 5
    1 B 4 3
    

    Sample Output

    33
    

    Solution

    樹形DP.
    數組(f[i][j][k])用於記錄當前以(i)為根的子樹中, 留下(j)(i)裝備給父節點, 并在子樹中總共使用(k)個金幣, 所能得到的以(i)為根的這棵子樹中可以得到的最大能力值.
    嗯, 聽起來很晦澀, 實際上也很晦澀..
    然後這題有個莫名的坑點, 這棵裝備樹有可能只有葉子節點, 而沒有任何非葉子節點..所以要加一個if語句進行特判, 假如只有基本裝備的話, 則直接背包DP搞定它.

    #include<cstdio>
    #include<cctype>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    inline void read(int &x)
    {
    	x = 0;
    	int flag = 1;
    	char c;
    	while(! isdigit(c = getchar()))
    		if(c == '-')
    			flag *= - 1;
    	while(isdigit(c))
    		x = x * 10 + c - '0', c = getchar();
    	x *= flag;
    }
    void read(char &c)
    {
    	while(! isgraph(c = getchar()));
    }
    void println(int x)
    {
    	if(x < 0)
    		putchar('-'), x *= - 1;
    	if(x == 0)
    		putchar('0');
    	int ans[10 + (1 << 4)], top = 0;
    	while(x)
    		ans[top ++] = x % 10, x /= 10;
    	for(; top; top --)
    		putchar(ans[top - 1] + '0');
    	putchar('
    ');
    }
    const int N = 1 << 6;
    const int M = 1 << 11;
    const int LIM = 1 << 7;
    int n, m, ans;
    struct Equipment
    {
    	int val, cost, lim, pre, need;
    }a[N];
    struct Edge
    {
    	int v, next;
    }G[N];
    int top;
    int head[N];
    void add_edge(int u, int v)
    {
    	G[top].v = v, G[top].next = head[u];
    	head[u] = top ++;
    	a[v].pre = u;
    }
    int f[N][LIM][M], tmp[M];
    //f[i][j][k]記錄以i為根的子樹中使用k的金額並且留下j個i給父親節點時, 這棵子樹可以達到的最大能力值 
    void solve(int u)
    {
    	if(! (~ head[u]))
    	{
    		a[u].lim = min(a[u].lim, m / a[u].cost);
    		for(int i = 0; i <= a[u].lim; i ++)	//遍歷當前點留給父親節點的數量 
    			for(int j = i; j <= a[u].lim; j ++)	//遍歷當前點總共取的數量 
    				f[u][i][j * a[u].cost] = (j - i) * a[u].val;
    		return;
    	}
    	a[u].lim = M;
    	for(int i = head[u]; ~ i; i = G[i].next)
    		solve(G[i].v), a[u].lim = min(a[u].lim, a[G[i].v].lim / a[G[i].v].need);
    	for(int i = 0; i <= a[u].lim; i ++)
    		f[u][i][0] = 0;
    	for(int i = head[u]; ~ i; i = G[i].next)	//遍歷每一個子節點 
    	{
    		int v = G[i].v;
    		for(int j = 0; j <= a[u].lim; j ++)	//遍歷每一個當前點留給父親節墊的數量 
    		{
    			for(int k = 0; k <= m; k ++)
    				tmp[k] = f[u][j][k]; 
    			memset(f[u][j], - 1, sizeof(f[u][j]));
    			//複製信息以用於更新
    			for(int k = 0; k <= m; k ++)	//遍歷當前子樹中總共使用的價值 
     				for(int l = 0; l <= k; l ++)	//遍歷以子節點v為根的子樹中總共使用的價值 
    					if(~ tmp[k - l] && ~ f[v][j * a[v].need][l])
    						f[u][j][k] = max(f[u][j][k], f[v][j * a[v].need][l] + tmp[k - l]);
    		}
    	}
    	/*
    	到這裡貌似就已經寫完了
    	但實際上還忽略了一種情況
    	就是當前購買的u節點並不全部留給父節點, 而是有一些(或者全部)留著直接加入進能力值中
    	因此就會有下面的一段代碼
    	*/
    	for(int i = 0; i <= a[u].lim; i ++)
    		for(int j = i; j <= a[u].lim; j ++)
    			for(int k = 0; k <= m; k ++)
    				if(~ f[u][j][k])
    					f[u][i][k] = max(f[u][i][k], f[u][j][k] + (j - i) * a[u].val), ans = max(ans, f[u][i][k]); 
    }
    int main()
    {
    	#ifndef ONLINE_JUDGE
    	freopen("BZOJ1017.in", "r", stdin);
    	freopen("BZOJ1017.out", "w", stdout);
    	#endif 
    	read(n), read(m);
    	for(int i = 1; i <= n; i ++)
    		a[i].pre = head[i] = - 1;
    	top = 0;	//WOC就是這裡一開始寫成了- 1, 結果熬夜調試了一整個晚上QAQ 
    	for(int i = 1; i <= n; i ++)
    	{
    		read(a[i].val);
    		char opt;
    		read(opt);
    		if(opt == 'B')
    			read(a[i].cost), read(a[i].lim);
    		else
    		{
    			int j;
    			read(j);
    			while(j --)
    			{
    				int x;
    				read(x);
    				add_edge(i, x);
    				read(a[x].need);
    			}
    		}
    	}
    	int flag = 1;
    	for(int i = 1; i <= n; i ++)
    		if(~ a[i].pre)
    			flag = 0;
    	if(flag)
    	{
    		int f[N][M];
    		f[0][0] = 0;
    		for(int i = 1; i <= n; i ++)
    			for(int j = 0; j <= a[i].lim; j ++)
    				for(int k = m; k >= j * a[i].cost; k --)
    					f[i][k] = max(f[i][k], f[i - 1][k - j * a[i].cost] + j * a[i].val);
    		for(int i = 0; i <= m; i ++)
    			ans = max(ans, f[n][i]);
    		println(ans);
    		return 0;
    	}
    	ans = - 1;
    	memset(f, - 1, sizeof(f));
    	for(int i = 1; i <= n; i ++)
    		if(! (~ a[i].pre))
    			solve(i), flag = 1;
    	println(ans);
    }
    
    
  • 相关阅读:
    C基础之移位操作
    实现itoa()
    Python的time模块的clock方法在不同平台的效果不同
    __stdcall与__cdecl之区别浅析及相关知识
    Python 字典 dictionary changed size during iteration
    Windows下printf输出long long类型
    inotify也会爆棚
    一条对“失控的腾讯帝国:企鹅无法把控手机市场”的评论
    imfunny程序员的增量发展
    程序员第一定律:关于技能和收入
  • 原文地址:https://www.cnblogs.com/ZeonfaiHo/p/6483088.html
Copyright © 2011-2022 走看看