[SDOI2018]荣誉称号

题目

在去年R2众多毒瘤题中唯一显得清新的一道

看到下标反复\(/2\)的形式我们就可以想到完全二叉树,于是我们2就把题目转化成了

修改一些点的点权,使得一棵完全二叉树上所有长度为\(k+1\)的上行路径上点权和\(mod\ m=0\)

现在就需要发掘一下题目的性质,首先是这棵完全二叉树的前\(k+1\)层,这里好像没有什么特殊的性质,就是需要满足\(k+1\)层的每一个节点到根的点权和\(mod\ m=0\)

接下来就出现一些有趣的东西啦

对于第\(k+2\)层的节点,我们需要让第\(2\)层到这一层的路径的点权和\(mod\ m=0\),但是注意到\(1\)层到\(k+1\)层的路径和已经\(mod\ m=0\)

少了一个第\(1\)层的点,但是多了一个\(k+2\)层的点,点权和在模\(m\)意义下没有什么变化

这说明了什么,第\(1\)层和\(k+2\)层的点权在模\(m\)意义下是相等的

相应的,我们还会发现第\(2\)层和\(k+2\)层要相等,\(3\)层和\(k+4\)层要相等...

最后我们发现剩下的这些层竟然都被前\(k+1\)层的节点给控制了,我们确定了前\(k+1\)层的点权,剩下的点权就都确定了,大概就是牵一发而动全身

我们对于前\(k+1\)层的每一个节点预处理出\(f_{i,j}\)表示节点\(i\)控制的节点中点权在\(mod\ m\)意义下等于\(j\)的所有点点权之和是多少,这个我们一边\(O(n)\)递推就能完成

之后对于每一个点,我们以\(O(m^2)\)的时间预处理出这个点的点权修改成\(0\)\(m-1\)这些数的花费是多少

\(dp_{i,j}\)表示第\(i\)个点从这个点到\(k+1\)的所有路径都被处理成了在模\(m\)意义下为\(j\)的最小花费

对于每一个点来说,这个点到\(k+1\)层的所有路径长度都应该是相等的,于是我们自底向上来一遍树形\(dp\)就好了,具体的就是枚举两个儿子到\(k+1\)层的点权和是多少,枚举这个点点权修改成什么,这样就能转移啦

最后的答案就是\(dp_{1,0}\)

但是我们交上去就发现怎么过了后面两个\(subtask\)\(wa\)了第一个点

结果发现第一个点的二叉树甚至不够\(k+1\)层,于是我们直接补全成\(k+1\)层,多补出来的那些点点权修改成什么都为\(0\)就好啦

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long 
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=1e7+5;
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int n,k,m,h,a[maxn],b[maxn],T;
LL val[2096][205],co[2096][205],dp[2096][205];
int id[maxn];
unsigned int SA,SB,SC;int p,A,B;
inline 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 gen() {
    scanf("%d%d%d%d%u%u%u%d%d",&n,&k,&m,&p,&SA,&SB,&SC,&A,&B);
    for(re int i=1;i<=p;i++) a[i]=read(),b[i]=read();
    for(re int i=p+1;i<=n;i++) {
        a[i]=rng61()%A+1;
        b[i]=rng61()%B+1;
    }
    for(re int i=1;i<=n;i++) a[i]%=m;
}
int main() {
	T=read();
	while(T--) {
		memset(dp,20,sizeof(dp));
		memset(co,0,sizeof(co));
		memset(val,0,sizeof(val));
		gen();int g=(1<<(k+1));h=g-1;
		for(re int i=1;i<=n;i++) {
			if(i/g) id[i]=id[i/g];
				else id[i]=i;
			val[id[i]][a[i]]+=b[i];
		} 
		for(re int i=1;i<=n;i++) {
			if(id[i]!=i) {h=i-1;break;}
			for(re int j=0;j<m;j++) 
				for(re int k=0;k<m;k++) 
					co[i][j]+=((j-k+m)%m)*val[i][k];
		}
		for(re int i=h;i;--i) {
			if((i<<1)>h&&(i<<1|1)>h) {
				for(re int j=0;j<m;j++)
					dp[i][j]=co[i][j];
				continue;
			}
			int ls=i<<1,rs=i<<1|1;
			for(re int j=0;j<m;j++)
				for(re int k=0;k<m;k++)
					dp[i][(j+k)%m]=min(dp[i][(j+k)%m],co[i][j]+dp[ls][k]+dp[rs][k]);
		}
		printf("%lld\n",dp[1][0]);
	}
	return 0;
}
posted @ 2019-04-18 18:57  asuldb  阅读(190)  评论(0编辑  收藏  举报