2019 ACM ICPC Xi'an University of Posts & Telecommunications School Contest(i,j题题解)

博客给出ACM ICPC题目I题和J题的题解。I题是树形dp问题,通过考虑每条边的贡献度,利用dp数组计算不同颜色点间距离之和;J题是典型的01背包问题,有三个状态转移,通过状态滚动算出答案。

I 题题解
题意 给你一颗树 树上每个点涂了颜色 总共只有两种颜色 问你不同颜色点直接的距离之和为多少

思路 :树形dp问题 考虑每条边的贡献度 这条边的左边的结点和右边节点染色不同的点数目相乘在乘以权值就是条边的贡献度 这样说比较抽象 看下面dp数组含义比较好理解

设num[0] 为这颗树的染色为0的节点总数 num[1] 为这颗树染色为1的节点的总数

dp[i][0] 表示当前的节点子和其子节点染色为0的个数 dp[i][1] 表示当前节点和其子节点染色为1的个数 那么连接着这个节点的这条边的另一端染色为1的节点数目就为 num[1] - dp[i][1] 染色为0的节点个数就为 num[0 ]- dp[i][0] 设这条边的权值为w 那么这条边的贡献就为

w*( num[0] - dp[i][0]) * dp[i][1] + w* (num[1] - dp[i][1]) * dp[i][0]
dfs算一下每条边的贡献度就好了

#include<bits/stdc++.h>// 考虑计算每个边的贡献度 我这个边能过被计算几次 那么就等于我左边的1的个数乘以右边0的个数

using namespace std;

const int maxn = 1e5+10;
const int mod = 998244353;
typedef pair<int,int> p;
typedef long long ll;
ll dp[maxn][2],a[maxn],ans = 0,num[2];

vector<p> g[maxn];

void dfs(int son,int fa)
{
    for(int i = 0; i < g[son].size(); i++)
    {
        int w = g[son][i].second,u = g[son][i].first;
        if(u==fa) continue;
        dfs(u,son);// 从最底层开始回溯
        ans += w*((num[0] - dp[u][0])*dp[u][1])%mod + w*((num[1] - dp[u][1])*dp[u][0])%mod;// 计算贡献度
        ans %= mod;
        dp[son][0] += dp[u][0];// 更新节点信息
        dp[son][1] += dp[u][1];
    }
    dp[son][0] += a[son]==0;//我为自己代言!! 要算上自己这个点的贡献度
    dp[son][1] += a[son]==1;
}

int main()
{
    int n,u,v,w;// 
    cin>>n;
    for(int i = 1; i <= n; i++)
    {
        cin>>a[i];
        num[a[i]]++;
    }
    for(int i = 0; i < n-1; i++)
    {
        cin>>u>>v>>w;
        g[u].push_back(p(v,w));
        g[v].push_back(p(u,w));
    }
    dfs(1,-1);
    cout<<(ans*2)%mod<<endl;
}

j题题意 自己应该懂吧 这里就不多说了
思路: 典型的01背包问题 就是有几个状态的转移过来 使用碎片的话那么就是消耗1体积
也就是 p - 1
设dp[i][j][k] 表示钱为i 积分为j 碎片为 k 是的最大值 那么这个就有三个转移状态
对于每个 物品 有
if(j>= w[i]) dp[i][j[k] = max(dp[i][j][k],dp[i][j - w[i] ][k] + val[i]);// 能转移 从钱转移过来
if(i >= v[i]) dp[i[j][k] = max(dp[i][j][k],dp[i - v[i]][j][k] + val[i]);// 能转移 从积分转移过来
if(k > 0) dp[i][j][k] = max(dp[i][j[k],dp[i][j][k-1] + val[i]);// 能转移 从碎片转移过来

类似的使用01背包 三个状态每次从最大值开始滚动一下 防止多算了一次 就可以算出答案了
代码如下

#include<bits/stdc++.h>

using namespace std;

const int maxn = 110;

int v[maxn],w[maxn],val[maxn];
int dp[maxn][maxn][6];
int main()
{
    int n,m,w1,k,ans = 0;
    while(cin>>n>>m>>w1>>k)
    {
        ans = 0;
        memset(dp,0,sizeof(dp));
        memset(v,0,sizeof(v));
        memset(w,0,sizeof(w));
        memset(val,0,sizeof(val));
        for(int i = 0; i < n; i++)
        {
            cin>>v[i]>>w[i]>>val[i];
        }
        for(int i = 0; i < n; i++)
        {
            for(int j = m; j >= 0; j--)
            {
                for(int l = w1; l >= 0; l--)
                {
                    for(int p = k; p >= 0; p--)能过转移 然后就可以转移的
                    {
                        if(l >= w[i]) dp[j][l][p] = max(dp[j][l][p],dp[j][l - w[i] ][p] + val[i]);
                        if(j >= v[i]) dp[j][l][p] = max(dp[j][l][p],dp[j - v[i]][l][p] + val[i]);
                        if(p > 0) dp[j][l][p] = max(dp[j][l][p],dp[j][l][p-1] + val[i]);
                    }
                }
            }
        }
        cout<<dp[m][w1][k]<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值