树形DP——POJ3513

  • 题目链接: http://poj.org/problem?id=3513

  • 题意: 一家人去电影院,这一家人构成一个森林,每个森林都是一棵树。电影院有2种票:家庭票和个人票,买了家庭票,则自己的孩子可以不用买票(指下一代,不包括下下代..),买了个人票,则就是个人票。给出家人票和个人票的价格,求一种购票策略使得最终花费最小(花费相同,则求总票数最小)。最后输出个人票的数量,家庭票的数量和总花费。

  • 分析: 这题的读入有点麻烦,不过我们耐心搞定后其实就OK了,然后建好树后,我们发现这题就是一道树形DP的题,当前某棵子树的最优策略,其实就是这棵子树的根节点的花费+它的子树的最优策略。

  • 状态: 用数组 DP[i][0/1/2] 表示第 i 个节点的人不买票[0],买个人票[1],买家庭票[2]时的最少花费。

  • 转移方程:

DP[u][0] = MIN(DP[v][1], DP[v][2]); //儿子只能买个人票和家庭票
DP[u][1] = S + MIN(DP[v][1], DP[v][2]); //儿子只能买个人票和家庭票
DP[u][2] = F + MIN(DP[v][0], DP[v][2]);//儿子不买票或者买家庭票(买个人票毫无意义)
  • P.S. 之前我一直用的数组存储邻接链表建图,结果是一直T,换成用vector存储就过了,耗时还只有500ms,所以如果顶点数很多,是可以考虑用vector存图

  • AC代码:

/*************************************************************************
    > File Name: test.cpp
    > Author: Akira 
    > Mail: qaq.febr2.qaq@gmail.com 
 ************************************************************************/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <bitset>
#include <queue>
#include <stack>
#include <map>
#include <cmath>
#include <vector>
#include <set>
#include <list>
#include <ctime>
#include <climits>
typedef long long LL;
typedef unsigned long long ULL;
typedef long double LD;
#define MST(a,b) memset(a,b,sizeof(a))
#define CLR(a) MST(a,0)
#define Sqr(a) ((a)*(a))
using namespace std;

#define MaxN 100005
#define MaxM MaxN
#define INF 0x3f3f3f3f
#define bug cout<<88888888<<endl;
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)

template<typename _> inline void scan(_& t)
{
    int c;
    while((c = getchar()) < '0' || c > '9');
    t = c - '0';
    while((c = getchar()) >= '0' && c <= '9') t = t * 10 + c - '0';
}
template<typename _> inline void print(_ x)
{
    int len = 0, p[20];
    if(x < 0) putchar('-'), x = -x;
    while(x) p[++len] = x % 10, x /= 10;
    if(!len) p[++len] = 0;
    while(len) putchar(p[len--] + '0');
}
struct Node
{
    int s;
    int f;
    int cost;
    int not_root;
};
vector<int> V[MaxN];
int S,F;
int a, b;
int people;
int notRoot[MaxN];
char name[1005];
map<string, int> M;
map<string, int>::iterator it;
Node DP[MaxN][3];
void initNode(int &loc)
{
    DP[loc][0].s =0;
    DP[loc][0].f = 0;
    DP[loc][0].cost = 0;
    DP[loc][1].s =0;
    DP[loc][1].f = 0;
    DP[loc][1].cost = 0;
    DP[loc][2].s=0;
    DP[loc][2].f=0;
    DP[loc][2].cost=0;
    DP[loc][0].not_root = 0;
    V[loc].clear();
}
int isNumber (string s)
{
    int i, ans;
    ans = 0;
    for (i=0; i<s.length(); i++)
    {
        if ( s[i] >= '0' && s[i] <= '9' )
                ans = ans*10 + (s[i]-'0');
        else
                return -1;
    }
    return ans;
}
void Initial ()
{
    people = 0;
    M.clear();
}
bool Input ()
{
    //如果上个testcase已经将这次输入的第一个变量读入到a,就无需再读入
    if ( a == -1 )  scanf("%d", &a);
    scanf("%d", &b);
    S = a;
    F = b;
    a = b = -1;
    if ( S == 0 && F == 0 )
            return false;
    char ch;
    int pid, sid;
    string parent, son;
    Initial();
    while (1)
    {
        //bug;
        //先将名字读入字符串数组,在将其置于string, 这样做是为了节省时间
        scanf("%s", &name);          
        parent = "";
        parent.append(name);
        a = isNumber(parent);
        if ( a != -1 )  break;     //读入了下个testcase的数据

        it = M.find(parent);     //查找有无对应映射
        if ( it ==  M.end() )
        {
            M[parent] = people;
            initNode(people);
            people++;
        }
        pid = M[parent];
        //读取当前节点的孩子
        ch = getchar();
        while ( ch != '\n' )
        {
            scanf("%s", &name);
            son = "";
            son.append(name);       //把字符数组的内容传给string
            it = M.find(son);
            if ( it == M.end() )
            {
                M[son] = people;
                initNode(people);
                people++;
            }
            sid = M[son];
            V[pid].push_back(sid);
            DP[sid][0].not_root = 1;
            ch = getchar();
        }
    }
   return true;
}

void cal(int &ans, int &s, int &f, Node a, Node b) //取最小花费和最小票数
{
    if(a.cost > b.cost)
    {
        ans += b.cost;
        s += b.s;
        f += b.f;
    }
    else if( a.cost < b.cost)
    {
        ans += a.cost;
        s += a.s;
        f += a.f;
    }
    else
    {
        int sa = a.s + a.f;
        int sb = b.s + b.f;
        ans += a.cost;
        if(sa >= sb)
        {
            s+=b.s; f+=b.f;
        }
        else 
        {
            s+=a.s; f+=a.f;
        }
    }
}
/*
ans0
DP[u][0] = MIN(DP[v][1], DP[v][2]);
DP[u][1] = cost + MIN(DP[v][1], DP[v][2]);
ans1
DP[u][2] = cost + MIN(DP[v][0], DP[v][2]);
*/
void DFS(int u)
{
    int ans0 = 0, ans1 = 0;
    int ns0 = 0, ns1 = 0, nf0 = 0, nf1 = 0;
    for(int i=0;i<V[u].size();i++)
    {
        int v = V[u][i];
        DFS(v);
        cal(ans0,ns0,nf0,DP[v][1],DP[v][2]);
        cal(ans1,ns1,nf1,DP[v][0],DP[v][2]);
    }
    //不买票
    DP[u][0].cost = ans0;
    DP[u][0].s = ns0; DP[u][0].f = nf0;
    //买个人票
    DP[u][1].cost = S+ans0;
    DP[u][1].s = ns0+1; DP[u][1].f = nf0;
    //买家庭票
    DP[u][2].cost = F+ans1;
    DP[u][2].s = ns1; DP[u][2].f = (nf1+1);
}
void solve()
{
    int ans = 0, sn = 0, fn = 0;
    for(int i=0;i<people;i++)
    {
        if(DP[i][0].not_root!=1)
        {
            DFS(i);
            //cout << i <<"is not root" << endl;
            cal(ans, sn, fn, DP[i][1], DP[i][2]);
        }
    }
    printf("%d %d %d\n", sn, fn, ans);
}

int main()
{
    int cas = 1;
    a = b = -1;
    while(Input())
    {
        printf("%d. ", cas++);
        solve();
    }
   // system("pause");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值