• [SDOI2018]荣誉称号



    题解

    感觉这种分析性质的题目我搞不动啊
    花了好久才弄得大致明白了
    首先可以看出这个限制使得(n)个点构成了一棵满二叉树
    并且树上每条长度为((k+1))的自上往下的链都是(m)的倍数
    所以我们可以想到每个点的(a)都对(m)取mod
    我们可以发现第(t)层的物品的(a)一定与第(t+k+1)层的物品同余
    所以可以仅计算出上面的(k+1)层每个节点的(a)值,那么下面的所有点的(a)值就都是唯一确定的了
    下面的节点用处不大,我们直接把这棵树弄成只有前(k+1)层节点的一棵树
    这样我们可以发现一个性质,就是对于每条从根到叶子的链,链上(a)的和一定是(m)的倍数
    这样节点数就最多只有(2^{k+1}=2048)个了
    那么(m)也不大,我们就可以设计一个状态了:(f[i][j])表示从节点(i)出发,一路上的(a)值之和(mod m = j)的最小花费
    答案显然就是(f[1][0])
    那么转移也应该是不难的
    我们就枚举(j)是多少,再枚举这个节点的(a)值是多少
    那么这个(dp)的转移就是(f[u][i] = min(f[u][j] , f[ls][(i-j+m)mod m] + f[rs][(i-j+m)mod m] + 这个节点的a值修改为j的代价))
    那么现在的问题就只有如何处理处这个节点的a值修改为j的代价这个东西了
    这玩意儿似乎不是很好求
    所以我们要把ta预处理出来
    (g[u][i])表示点u修改为(i)的代价
    假设我们预处理时到了节点(v)
    由于我们只(dp)了前((k+1))
    所以考虑前((k+1))层的哪些点修改会对这个点造成影响?
    因为第(t)层的物品的(a)一定与第(t+k+1)层的物品同余
    所以就让这个节点往上若干次((k+1))步,直到跳到深度小于等于((k+1))的节点(u)
    那么我们就考虑当点(u)修改为(i)时点(v)对于点(u)的贡献
    我们需要分情况讨论一下:
    (i >= a[v] : g[u][i] += (i - a[v]) * b[v])
    (i < a[v] : g[u][i] += (m + i - a[v]) * b[v])
    直接暴力更新的复杂度为(O(nm)),这样就可以获得(70)分了
    那么把这玩意儿化开
    就是(g[u][i] += i imes sum_{v in son[u] }b[v] - sum_{v in son[u]}{a[v] imes b[v]} + [i < a[v]]m imes sum_{v in son[u]}{b[v]})
    前面两项就对前((k+1))的点记录一下(sum_b , sum_{a imes b})就行了
    后面一项每次(O(1))打一个标记最后对于前((k+1))层的每个节点做一遍后缀和就好了
    这样我们就处理出来了(g)
    那么正经的(dp)式子就是(f[u][i] = min(f[u][j] , f[ls][(i-j+m)mod m] + f[rs][(i-j+m)mod m] + g[u][j]))

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    # define LL long long
    # define ls(now) (now << 1)
    # define rs(now) (now << 1 | 1)
    const int M = 10000005 ;
    const int N = 5050 ;
    const int E = 205 ;
    using namespace std ;
    
    int n , m , k ;
    int a[M] , b[M] , dep[M] ;
    LL f[N][E] , g[N][E] ;
    LL sum[N][E] , sumb[N] , sumab[N] ;
    unsigned int SA, SB, SC;
    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;
    }
    
    inline void Clear() {
    	memset(a , 0 , sizeof(a)) ;
    	memset(b , 0 , sizeof(b)) ;
    	memset(f , 31 , sizeof(f)) ;
    	memset(g , 31 , sizeof(g)) ;
    	memset(sum , 0 , sizeof(sum)) ;
    	memset(sumb , 0 , sizeof(sumb)) ;
    	memset(sumab , 0 , sizeof(sumab)) ;
    }
    void Read(){
    	int p, A, B;
        scanf("%d%d%d%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;
        }
    }
    
    inline int par(int u) {
    	while(dep[u] > k + 1)
    		u /= (1 << (k + 1)) ;
    	return u ;
    }
    int main() {
    	int Case ; scanf("%d",&Case) ;
    	while(Case --) {
    		Clear() ;
    		Read() ;
    		for(int i = 1 ; i <= n ; i ++) a[i] %= m ; 
    		for(int i = 1 ; i <= n ; i ++) {
    			dep[i] = dep[i >> 1] + 1 ;
    			if(a[i] > 0) 
    				sum[par(i)][a[i] - 1] += 1LL * m * b[i] ;
    			sumb[par(i)] += b[i] ;
    			sumab[par(i)] += 1LL * a[i] * b[i] ;
    		}
    		for(int i = 1 ; i < (1 << (k + 1)) ; i ++)
    			for(int j = m - 1 ; j >= 0 ; j --) {
    				sum[i][j] = sum[i][j + 1] + sum[i][j] ;
    				g[i][j] = j * sumb[i] - sumab[i] + sum[i][j] ;
    			}
    		for(int u = (1 << k) ; u < (1 << (k + 1)) ; u ++)
    			for(int i = 0 ; i < m ; i ++)
    				f[u][i] = g[u][i] ;
    		for(int u = (1 << k) - 1 ; u >= 1 ; u --)
    			for(int i = 0 ; i < m ; i ++)
    				for(int j = 0 ; j < m ; j ++)
    					f[u][i] = min( f[u][i] , f[ls(u)][(i - j + m) % m] + f[rs(u)][(i - j + m) % m] + g[u][j] ) ;
    		printf("%lld
    ",f[1][0]) ;
    	}
    	return 0 ;
    }
    
  • 相关阅读:
    Beyond Compare 30天评估期已过解决方案
    windows快捷键及命令
    表情包的转码解码
    html+css实现文本从右向左
    js控制页面滚回上一记录位置
    将base64转为二进制
    牛客挑战赛58
    字符串专题
    牛客挑战赛59
    组合数学专题
  • 原文地址:https://www.cnblogs.com/beretty/p/10680020.html
Copyright © 2020-2023  润新知