Newcoder 140 B.discount(树形DP)

本文探讨了在多种饮料选择中,利用直接折扣和免费赠品的优惠策略,通过构建数学模型和算法,寻找购买n种饮料所需的最少花费。文章详细介绍了如何通过树状结构和动态规划求解最优解,适用于大规模数据处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

nn种饮料,第i种饮料价格为p[i]p[i],当买第ii种饮料时有两种优惠:

1.直接优惠d[i]元,也就是说只需要花费p[i]d[i]p[i]−d[i]买第ii种饮料

2.免费得到第f[i]种饮料

问这nn种饮料每种至少有一个所需的最少花费

Input

第一行一整数n表示饮料种类,之后输入nn个整数p[1],...,p[n]表示每种饮料的价格,之后输入nn个整数d[1],...,d[n],最后输入nn个整数f[1],...,f[n]

(1n105,0d[i]p[i]109,1f[i]n)(1≤n≤105,0≤d[i]≤p[i]≤109,1≤f[i]≤n)

Output

输出最小花费

Sample Input

3
10 3 5
5 0 5
1 3 2

Sample Output

8

Solution

以赠送关系反向建图构成若干基环,首先考虑树上问题,则第二种优惠方式为购买某个儿子节点可以赠送该点,以g[u][0]g[u][0]表示以uu为根的子树中每点都至少买一次的最少代价,以g[u][1]表示以uu为根的子树中每点都至少买一次,且以第二种优惠方式购买u点的最少代价(即可以赠送uu的父亲节点),那么有转移

g[u][1]=p[u]+vson(u)g[v][0]

g[u][0]=min(p[u]d[u]+vson(u)g[v][0],minwson(u)g[w][1]+vson(u),vwg[v][0])g[u][0]=min(p[u]−d[u]+∑v∈son(u)g[v][0],minw∈son(u)g[w][1]+∑v∈son(u),v≠wg[v][0])

其中g[u][0]g[u][0]的转移即为要么用第一种优惠购买uu,要么u是通过购买uu的某个儿子w赠送的

sum[u]=vson(u)g[v][0]sum[u]=∑v∈son(u)g[v][0],则上述转移可以写成

g[u][1]=p[u]+sum[u]g[u][1]=p[u]+sum[u]

g[u][0]=min(p[u]d[u]+sum[u],minwson(u)(g[w][1]g[w][0])+sum[u])g[u][0]=min(p[u]−d[u]+sum[u],minw∈son(u)(g[w][1]−g[w][0])+sum[u])

下面考虑基环的情况,假设环上的点为v1,...,vmv1,...,vm,枚举v1v1的转移,即v1v1是否要通过购买vmvm赠送得到,以sta=0/1sta=0/1表示v1v1不用/用通过购买vmvm赠送得到,以dp[i][0]dp[i][0]表示购买v1,...,viv1,...,vi以及其外挂着的树上所有节点至少一次所需最少代价,dp[i][1]dp[i][1]表示购买v1,...,viv1,...,vi以及其外挂着的树上所有节点至少一次且以第二种优惠方式购买vivi的最少代价,那么

dp[1][1]=g[v1][1],dp[1][0]=sta?sum[v1]:dp[v1][0]dp[1][1]=g[v1][1],dp[1][0]=sta?sum[v1]:dp[v1][0],并有转移

dp[i][1]=dp[i1][0]+g[vi][1]dp[i][1]=dp[i−1][0]+g[vi][1]

dp[i][0]=min(dp[i1][1]+sum[vi],dp[i1][0]+g[vi][0])dp[i][0]=min(dp[i−1][1]+sum[vi],dp[i−1][0]+g[vi][0])

答案即为dp[m][sta]dp[m][sta],枚举stasta选取较优值即为答案

Code

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
#define maxn 100005
int n,p[maxn],d[maxn],f[maxn],vis[maxn],mark[maxn];
ll g[maxn][2],dp[maxn][2],sum[maxn];
vector<int>e[maxn];
void dfs(int u)
{
    vis[u]=1;
    ll mn=1e16;
    for(int i=0;i<e[u].size();i++)
    {
        int v=e[u][i];
        if(mark[v])continue;
        dfs(v);
        sum[u]+=g[v][0];
        mn=min(mn,g[v][1]-g[v][0]);
    }
    g[u][1]=p[u]+sum[u];
    g[u][0]=min(p[u]-d[u]+sum[u],mn+sum[u]);
}
ll Solve(int u)
{
    while(!vis[u])vis[u]=1,u=f[u];
    vector<int>v;
    v.clear();
    v.push_back(u);
    while(f[v.back()]!=u)v.push_back(f[v.back()]);
    for(int i=0;i<v.size();i++)mark[v[i]]=1;
    for(int i=0;i<v.size();i++)dfs(v[i]);
    ll ans=1e16;
    for(int sta=0;sta<=1;sta++)
    {
        dp[0][1]=g[v[0]][1];
        dp[0][0]=sta?sum[v[0]]:g[v[0]][0];
        for(int i=1;i<v.size();i++)
        {
            dp[i][1]=g[v[i]][1]+dp[i-1][0];
            dp[i][0]=min(dp[i-1][1]+sum[v[i]],dp[i-1][0]+g[v[i]][0]);
        }
        ans=min(ans,dp[v.size()-1][sta]);
    }
    return ans;
}
int main()
{   
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    for(int i=1;i<=n;i++)scanf("%d",&d[i]);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&f[i]);
        e[f[i]].push_back(i);
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
        if(!vis[i])ans+=Solve(i);
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值