[SDOI2018]bzoj 5333 荣誉称号 - dp

121 篇文章 0 订阅
40 篇文章 0 订阅

题解:首先考虑问题的弱化版本,即n=2^(k+1)-1,这时候一条链是从叶子到根的(并且是完全二叉树),若根到某节点x的权值之和是y,则其左右任何一个节点到其子树的任意一个点的权值之和都是(m-y)%m。因此可是预处理f[i][j]表示把i的权值改成j的代价,dp[i][j]表示从i出发走到叶子结点的权值是j的最小代价,那么对于叶子,dp[x][i]=f[x][i],否则dp[x][i]=min(dp[y1][j]+dp[y2][j]+f[x][(i-j+m)%m]),其中y1和y2是x的儿子节点,就是枚举当前这个节点的第一位权值。
考虑n比较大的情况,注意到,最终a[x]应当是和a[x/(2^(k+1))]相等的,因此只要确定了前2^(k+1)-1个点的权值,整棵树的权值就确定了。因此只维护那些 x2k+11 x ≤ 2 k + 1 − 1 的x的f[x][i]。考虑记fa[y]表示y不断往上跳k+1个点,不能跳的时候的位置,若x=fa[y],考虑y对f[x][i]的影响:
f[x][i]+=(iay)by,ayi f [ x ] [ i ] + = ( i − a y ) b y , a y ≤ i
f[x][i]+=(may+i)by,ay>i f [ x ] [ i ] + = ( m − a y + i ) b y , a y > i
而fa数组显然可以O(n)求出,f[x][i]按照上述过程直接计算是O(nm)的,过不了,但其实把f[x][i]改写成对y求和的形式,会发现:
f[x][i]=iybyyay×by+may>iby f [ x ] [ i ] = i ∑ y b y − ∑ y a y × b y + m ∑ a y > i b y
因此维护c[x][i]表示 ay>=i a y >= i by b y 的和,这个显然每次c[fa[y]][a[y]]+=b[y],然后最后求个后缀和即可;前两项显然随便维护,因此可以在 O(n+2k+1m) O ( n + 2 k + 1 m ) 的时间内求出f数组,因此可以在O(n+2^{k+1}m^2)时间内通过本题,最后一个细节是,如果树不是满的,可以加一些b=0的叶子结点扩充到满,可以省略一些细节。
代码十分容易实现:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#define gc getchar()
#define K 2100
#define M 210
#define N 20000010
#define lint long long
#define INF (LLONG_MAX/10)
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
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;
}
int a[N],b[N],fa[N];lint bas[K];
lint c[K][M],f[K][M],dp[K][M];
int main()
{
//  freopen("data.in","r",stdin);
    for(int T=inn();T;T--)
    {
        int n=inn(),k=inn(),m=inn(),p=inn(),L=0;while((1<<L)-1<n) L++;
        scanf("%u%u%u",&SA,&SB,&SC),A=inn(),B=inn();
        for(int i=1;i<=p;i++) a[i]=inn(),b[i]=inn();
        for(int i=p+1;i<=n;i++) a[i]=rng61()%A+1,b[i]=rng61()%B+1;
        for(int i=1;i<=n;i++) a[i]%=m;
        for(int i=n+1;i<=(1<<L)-1;i++) a[i]=b[i]=0;
        n=(1<<L)-1;int kv=1;
        for(int i=0;i<=k;i++) kv<<=1;
        for(int i=1;i<kv;i++)
            memset(c[i],0,sizeof(lint)*(m+1)),
            fa[i]=i,c[i][a[i]]=b[i],bas[i]=a[i]*b[i];
        for(int i=kv;i<=n;i++) c[fa[i]=fa[i/kv]][a[i]]+=b[i],bas[fa[i]]+=a[i]*b[i];
        for(int i=1;i<kv;i++)
            for(int j=m-2;j>=0;j--) c[i][j]+=c[i][j+1];
        for(int i=1;i<kv;i++)
            for(int j=0;j<m;j++)
                f[i][j]=(lint)j*c[i][0]-bas[i]+(lint)m*c[i][j+1];
        for(int i=kv/2;i<kv;i++)
            for(int j=0;j<m;j++) dp[i][j]=f[i][j];
        for(int i=kv/2-1;i;i--)
            for(int j=0;j<m;j++)
            {
                dp[i][j]=INF;
                for(int k=0;k<m;k++)
                    dp[i][j]=min(dp[i][j],dp[i<<1][k]+dp[i<<1|1][k]+f[i][(j-k+m)%m]);
            }
        printf("%lld\n",dp[1][0]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值