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