[SDOI2018]荣誉称号

Posted by Dispwnl on May 3, 2019

题目

题解

可以发现题目中给定的是个完全二叉树的形式,然后题目给定的限制可以转换成树上每一条长度为$k+1$的链$a$值和$\%m=0$

发现深度为$x(x> k+1)$的点的值$a_1$与它深度为$x-k-1$祖先的值$a_2$满足$a_1\equiv a_2(\bmod m)$,这样只用确定前$k+1$层的值即可,把下面的信息都压到前 $k+1$层即可

处理出点$t$的$a$值为$x$时的最小花费(花费包括深度$>k+1$的点)$w_{t,x}$,设$f_{i,j}$表示点$i$的点值为$j(\bmod m)$时它的子树的最小花费

对于$x\in k+1层$,$f_{x,j}$肯定就是$w_{k+1,j}$,对于$x\in k层$,$f_{x,j}=w_{2x,m-j}+w_{2x+1,m-j}$,上面层的就可以分别按左右子树的代价最小值取值了

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<algorithm>
# define tl (i<<1)
# define tr (i<<1|1)
# define LL long long
using namespace std;
const int MAX=1e7+5,N=2e3+100;
int p,A,B,n,k,m,T;
unsigned int SA,SB,SC;
LL ans;
int a[MAX],b[MAX],fa[MAX];
LL sum[N];
LL w[N][N],Aw[N][N],f[N][N];
unsigned int rng61()
{
	SA^=SA<<16,SA^=SA>>5,SA^=SA<<1;
	unsigned int t=SA;
	return SA=SB,SB=SC,SC^=t^SA,SC;
}
void gen()
{
	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;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		gen();
		memset(Aw,0,sizeof(Aw));
		memset(w,0,sizeof(w));
		memset(sum,0,sizeof(sum));
		for(int i=1;i<=n;++i)
		  a[i]%=m;
		for(int i=1;i<=n;++i)
		  fa[i]=(i>>k+1)?fa[i>>k+1]:i,sum[fa[i]]+=b[i],Aw[fa[i]][a[i]]+=b[i],w[fa[i]][0]+=a[i]?1ll*(m-a[i])*b[i]:0;
		for(int i=1;i<(1<<k+1);++i)
		  for(int j=1;j<m;++j)
		    w[i][j]=w[i][j-1]+sum[i]-m*Aw[i][j];
		for(int i=(1<<k-1);i<(1<<k);++i)
		  {
		  	f[i][0]=w[tl][0]+w[tr][0];
		  	for(int j=1;j<m;++j)
		  	  f[i][j]=w[tl][m-j]+w[tr][m-j];
		  }
		for(int i=(1<<k-1)-1;i>=1;--i)
		  for(int j=0;j<=m;++j)
		    {
		    	LL ans1=1e18,ans2=1e18;
		    	for(int l=0;l<m;++l)
		    	  ans1=min(ans1,f[tl][(j+l)%m]+w[tl][l]),ans2=min(ans2,f[tr][(j+l)%m]+w[tr][l]);
		    	f[i][j]=ans1+ans2;
			}
		ans=1e18;
		for(int i=0;i<m;++i)
		  ans=min(ans,f[1][i]+w[1][i]);
		printf("%lld\n",ans);
	}
	return 0;
}