[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 \times \sum_{v \in son[u] }b[v] - \sum_{v \in son[u]}{a[v] \times b[v]} + [i < a[v]]m \times \sum_{v \in son[u]}{b[v]}\)
前面两项就对前\((k+1)\)的点记录一下\(sum_b , sum_{a\times 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\n",f[1][0]) ;
}
return 0 ;
}