• 【BZOJ1495】网络收费(NOI2006)-树形DP+状压DP


    测试地址:网络收费
    做法:本题需要用到树形DP+状压DP。
    因为成对的贡献比较难做,我们尝试把贡献算到每一个叶子节点上。我们发现按照题目中的收费方式,它等价于对于每棵子树,A和B哪个更少,就统计这样的贡献:对于每个这种用户i,如果i,j的LCA是当前子树的根,则累计F(i,j)。为什么等价呢?因为观察计费形式,假设A更少,那么对所有满足LCA为当前根的点对(i,j),如果两个同为A,则累计两次F(i,j),等价于累计F(i,j)F(j,i)各一次,如果其中有一个为A,那么要么累计F(i,j),要么累计F(j,i),因此两种计算方式是等价的。注意到满足条件的j一定是一个连续区间,因此我们可以预处理出前缀和,加快询问的速度。
    接下来就要考虑状态转移了。我们发现在每个点上实际上是在做这样的决策:要使A更多还是使B更多。而我们发现,一个点的贡献受且仅受它的祖先决策的影响。注意到深度只有n,所以我们可以在状态中开一维表示该点祖先的决策状态,最多有2n种。那么我们可以得到一个状态定义,如下:
    f(i,j,k)为以点i为根的子树中,点i的祖先的决策状态为j,子树中有k个A时,能得出的最小花费。
    当点i为叶子节点时,我们可以借助前缀和O(n)算出这个状态的花费,而其他点的状态就类似背包一样转移即可,可以证明时间复杂度为O(22n)
    还有一点要注意,直接开f(i,j,k)的话,空间复杂度为O(23n),无法接受,注意到在深度为dep时,j最多有2dep种决策,而k最大为2ndep,那么如果我们把这两维合并成一维,那么这一维从始至终最多有2n种组合,那么我们就把空间复杂度也优化到了O(22n),可以通过此题。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll inf=1000000000ll*1000000000ll;
    int n,p;
    ll c[1050],sum[1050][1050],f[2050][2050];
    bool type[1050];
    
    ll calc(int v,int i,bool type)
    {
        int x=1,l=1,r=(1<<n),dep=n-1;
        ll ans=0;
        while(l!=r)
        {
            int mid=(l+r)>>1;
            if (v<=mid)
            {
                if (!type^(i&(1<<dep)?1:0)) ans+=sum[v][r]-sum[v][mid];
                r=mid;
            }
            else
            {
                if (!type^(i&(1<<dep)?1:0)) ans+=sum[v][mid]-sum[v][l-1];
                l=mid+1;
            }
            dep--;
        }
        return ans;
    }
    
    void dp(int v,int dep)
    {
        if (!dep)
        {
            for(int i=0;i<(1<<n);i++)
            {
                f[v][i*(1<<(dep+1))+0]=calc(v-(1<<n)+1,i,1);
                f[v][i*(1<<(dep+1))+1]=calc(v-(1<<n)+1,i,0);
                f[v][i*(1<<(dep+1))+type[v-(1<<n)+1]]+=c[v-(1<<n)+1];
            }
            return;
        }
        dp(v<<1,dep-1);
        dp(v<<1|1,dep-1);
        for(int i=0;i<(1<<(n-dep));i++)
            for(int j=0;j<=(1<<dep);j++)
            {
                int nxt=i*(1<<(dep+1))+j;
                f[v][nxt]=inf;
                bool flag=(j>=(1<<dep)-j);
                int nxtp=i*(1<<(dep+1))+flag*(1<<dep);
                for(int k=0;k<=j;k++)
                    if (k<=(1<<(dep-1))&&j-k<=(1<<(dep-1)))
                        f[v][nxt]=min(f[v][nxt],f[v<<1][nxtp+k]+f[v<<1|1][nxtp+(j-k)]);
            }
    }
    
    void init()
    {
        scanf("%d",&n);
        int p=(1<<(n+1))-1;
        for(int i=1;i<=(1<<n);i++)
            scanf("%d",&type[i]);
        for(int i=1;i<=(1<<n);i++)
            scanf("%lld",&c[i]);
        for(int i=1;i<=(1<<n);i++)
        {
            sum[i][i]=0;
            for(int j=i+1;j<=(1<<n);j++)
            {
                scanf("%lld",&sum[i][j]);
                sum[j][i]=sum[i][j];
            }
        }
        for(int i=1;i<=(1<<n);i++)
        {
            sum[i][0]=0;
            for(int j=1;j<=(1<<n);j++)
                sum[i][j]=sum[i][j-1]+sum[i][j];
        }
    }
    
    int main()
    {
        init();
        dp(1,n);
        ll ans=inf;
        for(int i=0;i<=(1<<n);i++)
            ans=min(ans,f[1][i]);
        printf("%lld",ans);
    
        return 0;
    }
  • 相关阅读:
    c博客作业05--指针
    C博客作业04--数组
    C博客作业03--函数
    C博客作业02--循环结构
    C博客作业01--分支、顺序结构
    我的第一篇博客
    DS博客作业05--查找
    DS博客作业04--图
    DS博客作业03--树
    DS博客作业02--栈和队列
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793393.html
Copyright © 2020-2023  润新知