【BZOJ5333】荣誉称号(SDOI2018)-找规律+树形DP

测试地址:荣誉称号
做法:本题需要用到找规律+树形DP。
第一次想出Luogu黑题祭。
首先,考虑题目中条件的形式,如果我们令点i的父亲为点i2(实际上就是在二进制中右移一位),那么所有点可以拼成一棵二叉树,问题就可以表示成:要使树上所有长为k+1的祖孙链上的a值之和对m的余数为0,点ia值加1的花费是bi,求最小花费。
因为要使所有长为k+1的祖孙链上的a值之和对m的余数为0,那么每一个深度为x的点的两个儿子a值应该同余(因为从根到两个儿子的路径对m余数都为0)。进一步地向下推,我们可以得到一个结论:每个点的a值和它的k+1级父亲的a值同余。也就是说,在决定前k+1层的某一个点时,顺带着也决定了它所有x(k+1)级儿子的值,这时候我们就可以把k+1层以下的点的贡献直接缩到前k+1层的点上去。以下为了方便讨论,直接令a值为实际a值对m的余数。
cost(i,j)为点i(1x<2k+1)选择的a值为j时所连带产生的贡献。如果暴力算出这个数组,那么时间复杂度将是O(nm),无法承受。事实上,我们可以尝试先O(n)求出cost(i,0),然后进行递推。要从cost(i,j)cost(i,j+1),首先是所有点都要多增加费用,然后对于那些原本a值就是j+1的点,要减去mbv的贡献。于是我们只需要在O(n)cost(i,0)时,顺带求出totsum(i):点i连带的点的费用总和,sum(i,j):点i连带的原a值为j的点的费用总和,就可以对每个i进行O(m)递推了,那么总的时间复杂度就是O(n+2k+1m),可以承受。
那么接下来考虑树形DP。对于所有第k层的点,它的两个儿子要取的a值和它到根路径上a的和有关,于是我们这样定义状态:令f(i,j)为点i的子树以及所有连带的点中,在根到点i路径上a值之和的余数为j时,所能得到的最小花费。因为DP时的方便,我们在求f(i,j)时才枚举两个儿子要选什么,所以这个f(i,j)的贡献中不包含i及其连带点的贡献。那么对于所有第k层的点有:
f(i,j)=cost(lson,mj)+cost(rson,mj)
其中mj应该再对m取模,以下这个位置的式子都应该取模,为了方便就不写了。
而对于前k1层的点有:
f(i,j)=min{f(lson,j+x)+cost(lson,x)+f(rson,j+y)+cost(rson,y)}
显然x,y的选择互不影响,所以直接O(m)分别求出和x,y有关的部分式子的最小值再加起来即可。
而最后的答案为:
ans=min{f(1,x)+cost(1,x)}
就是加上了点1及其连带点的贡献。于是DP的复杂度为O(2km2),可以承受,我们就解决了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=20000010;
const ll K=2060;
const ll M=1010;
const ll inf=1000000000ll*1000000000ll;
int T,n,k;
ll m,a[N],b[N],pre[N];
ll totsum[K],sum[K][M],cost[K][M];
ll f[K][M];

unsigned int SA, SB, SC;
int p, A, B;

unsigned int rng61(){
    SA ^= SA << 16;
    SA ^= SA >> 5;
    SA ^= SA << 1;
    unsigned int t = SA;
    SA = SB;
    SB = SC;
    SC ^= t ^ SA;
    return SC;
}

void gen(){
    scanf("%d%d%lld%d%u%u%u%d%d", &n, &k, &m, &p, &SA, &SB, &SC, &A, &B);
    for(int i = 1; i <= p; i++)scanf("%d%d", &a[i], &b[i]);
    for(int i = p + 1; i <= n; i++){
        a[i] = rng61() % A + 1;
        b[i] = rng61() % B + 1;
    }
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));

        gen();

        memset(totsum,0,sizeof(totsum));
        memset(sum,0,sizeof(sum));
        memset(cost,0,sizeof(cost));

        int tot=1;
        while(tot<=n) tot<<=1;
        n=tot;
        for(int i=1;i<=n;i++)
        {
            pre[i]=(i>>(k+1))?pre[i>>(k+1)]:i;
            a[i]%=m;
            totsum[pre[i]]+=b[i];
            sum[pre[i]][a[i]]+=b[i];
            cost[pre[i]][0]+=(m-a[i])%m*b[i];
        }
        for(int i=1;i<(1<<(k+1));i++)
            for(int j=1;j<m;j++)
                cost[i][j]=cost[i][j-1]+totsum[i]-m*sum[i][j];

        for(int i=(1<<(k-1));i<(1<<k);i++)
            for(int j=0;j<m;j++)
                f[i][j]=cost[i<<1][(m-j)%m]+cost[i<<1|1][(m-j)%m];
        for(int i=(1<<(k-1))-1;i>=1;i--)
            for(int j=0;j<m;j++)
            {
                ll ans1=inf,ans2=inf;
                for(int x=0;x<m;x++)
                    ans1=min(ans1,f[i<<1][(j+x)%m]+cost[i<<1][x]);
                for(int x=0;x<m;x++)
                    ans2=min(ans2,f[i<<1|1][(j+x)%m]+cost[i<<1|1][x]);
                f[i][j]=ans1+ans2;
            }

        ll ans=inf;
        for(int i=0;i<m;i++)
            ans=min(ans,f[1][i]+cost[1][i]);
        printf("%lld\n",ans);
    }

    return 0;
}
posted @ 2018-08-23 20:19  Maxwei_wzj  阅读(147)  评论(0编辑  收藏  举报