Codeforces Round 896 (Div. 1) C. Travel Plan(树形dp+组合数学)

2023-09-15 10:57:16

题目

有一棵n(1<=n<=1e18)个点的树,

点i连着2*i和2*i+1两个点,构成一棵完全二叉树

对于每个点i,记其值为a[i],a[i]可以取[1,m](1<=m<=1e5)的整数

记i到j的简单路径上的最大值为s[i][j],

则一棵权值确定的树对答案的贡献是\sum_{i=1}^{n}\sum_{j=i+1}^{n}s[i][j]

现在求所有可能情况下的树的贡献之和,答案对998244353取模

实际t<=200组样例,但保证summ不超过1e5

思路来源

羊村群小羊

题解

大致的思路就是把每个长度的路径都统计算出来,然后再算贡献

而n个点的树总是可以拆成左子树和右子树继续递归下去的,有子结构的概念

所以可以按子树大小做记忆化,每棵子树暴力维护所有长度的路径进行合并

由于路径长度最长2*logn,这里固定开了128长度的vector,只对这些做合并

dp[i][2]表示当前节点u的子树长度为i的路径的条数

其中dp[i][0]表示两端都位于子树内部的路径,dp[i][1]表示有一端位于根节点的路径

求出路径方案数后求贡献,最大值为i的方案数,首先特判i=1,

然后稍作容斥,方案数等于m个值从[1,i]任取减去m个值从[1,i-1]任取

长为i的路径的方案数*剩下n-i个点任取的方案数*最大值为j的方案数*最大值j,

就是当路径长度为i,而最大值为j时,(i,j)对答案的贡献,统计所有贡献累加即可

心得

int k = std::__lg(n + 1);
ll ls=((1LL << (k - 1)) - 1) + std::min(1LL << (k - 1), n - (1LL << k) + 1);
ll rs=n-1-ls;

求左子树大小这里,抄了一下jiangly的代码,但后来想了想也很好理解

对于倒数第二层往上,是左右子树平分的

而对于最后一层,左子树能拿到的大小,为min(剩下的点数,最后一层的一半)

代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef array<int,2> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef unsigned ui;
//typedef __uint128_t L;
typedef unsigned long long L;
typedef unsigned long long ull;
const int N=1e5+10,M=128,mod=998244353;
int t,m,pw[N][M];
ll n;
map<ll,vector<P>>mp;//dp[i][2]表示是否开口的方案数
void add(int &x,int y){
	x=(x+y)%mod;
}
vector<P>dfs(ll n){
	if(n==0)return vector<P>(1,{0,0});
	if(n==1)return vector<P>(1,{0,1});
	if(mp.count(n))return mp[n];
    int k = std::__lg(n + 1);
	ll ls=((1LL << (k - 1)) - 1) + std::min(1LL << (k - 1), n - (1LL << k) + 1);
	ll rs=n-1-ls;
	vector<P>l=dfs(ls),r=dfs(rs);
	int sl=SZ(l),sr=SZ(r);
	//printf("n:%lld lsz:%d rsz:%d\n",n,sl,sr);
	vector<P>dp(128,{0,0});
	rep(i,0,sl-1){
		rep(j,0,sr-1){
			if(!l[i][1] || !r[j][1])continue;
			add(dp[i+j+2][0],1ll*l[i][1]*r[j][1]%mod);
		}
	}
	rep(i,0,sl-1){
		add(dp[i][0],l[i][0]);
		add(dp[i][0],l[i][1]);
		add(dp[i+1][1],l[i][1]);
	}
	rep(i,0,sr-1){
		add(dp[i][0],r[i][0]);
		add(dp[i][0],r[i][1]);
		add(dp[i+1][1],r[i][1]);
	}
	add(dp[0][1],1);
	return mp[n]=dp;
}
int modpow(int x,ll n,int mod){
	if(!n)return 1;
	int res=1;
	for(;n;n>>=1,x=1ll*x*x%mod){
		if(n&1)res=1ll*res*x%mod;
	}
	return res;
}
int cal(int sz,int v){
	if(v==1)return 1;
	return (pw[v][sz]-pw[v-1][sz]+mod)%mod;
}
int sol(){
	vector<P>dp=dfs(n);
	int sz=SZ(dp),res=0;
	rep(j,0,sz-1){
		int cnt=(dp[j][0]+dp[j][1])%mod,len=j+1;
		if(len>n)break;
		int oth=modpow(m,n-len,mod)%mod;
		rep(i,1,m){
			add(res,1ll*cnt*cal(len,i)%mod*i%mod*oth%mod);
		}
	}
	return res;
}
int main(){
	rep(i,1,N-1){
		pw[i][0]=1;
		rep(j,1,M-1){
			pw[i][j]=1ll*pw[i][j-1]*i%mod;
		}
	}
	sci(t);
	while(t--){
		scanf("%lld%d",&n,&m);
		printf("%d\n",sol());
	}
	return 0;
}

更多推荐

【新书推荐】大模型赛道如何实现华丽的弯道超车 —— 《分布式统一大数据虚拟文件系统 Alluxio原理、技术与实践》

文章目录大模型赛道如何实现华丽的弯道超车——AI/ML训练赋能解决方案01具备对海量小文件的频繁数据访问的I/O效率02提高GPU利用率,降低成本并提高投资回报率03支持各种存储系统的原生接口04支持单云、混合云和多云部署01通过数据抽象化统一数据孤岛02通过分布式缓存实现数据本地性03优化整个工作流的数据共享直播预告

贵阳RapidSSL的ssl证书适合个人网站吗

现在很多开发者不论是为了记录还是宣传,很多人都会创建一个属于自己的网站,而有了自己的网站,为了保护网站信息安全以及防止网站数据被篡改与劫持,就需要为网站安装SSL证书。那么RapidSSL的SSL证书个人开发者可以使用吗?今天就随SSL盾小编了解RapidSSL旗下的SSL证书。1.RapidSSL是SSL证书颁发机构

JavaSE、JavaEE与Spring的概念和异同点剖析以及规范13 个分析

JavaSE、JavaEE与Spring的概念和异同点剖析以及规范13个分析目录概述需求:设计思路实现思路分析1.什么是JavaSE2.是JavaEE3.什么是Spring参考资料和推荐阅读Survivebydayanddevelopbynight.talkforimportbiz,showyourperfectcod

想要精通算法和SQL的成长之路 - 最长回文子串

想要精通算法和SQL的成长之路-最长回文子串前言一.最长回文子串1.1中心扩散法的运用前言想要精通算法和SQL的成长之路-系列导航一.最长回文子串原题链接1.1中心扩散法的运用这类具有回文性质的题目,我们如果用常规的从左往右或者从右往左的遍历方式,在编码上往往比较麻烦。那不妨,我们以字符串中的每一个字符为起点,同时向左

typedef function<int (int,int)> func_t;

这段代码是C++中用于创建函数类型别名(functiontypealias)的语法。让我们来逐步解释它:typedef:typedef是C++中的关键字,用于创建类型别名。它允许你为一个已存在的类型创建一个新的、易于使用的名称。function:这部分指定了要创建的类型别名的名称。在这里,我们将创建一个名为fun_t的

电脑如何查看代理服务器IP?

许多人在使用互联网时可能会遇到需要使用代理服务器的情况。但是,你知道如何在电脑上查看代理服务器IP吗?本文将为您分享简单易懂的方法,帮助您轻松了解代理设置的秘密!代理服务器在网络世界中担任着重要的角色,它可以充当中间人,转发用户和目标服务器之间的请求和响应。使用代理服务器可以带来许多好处。那么,要如何查看代理服务器IP

flutter开发实战-自定义长按TextField输入框剪切、复制、选择全部菜单AdaptiveTextSelectionToolba样式UI效果

flutter开发实战-自定义长按TextField输入框剪切、复制、选择全部菜单样式UI效果在开发过程中,需要长按TextField输入框cut、copy设置为中文“复制、粘贴”,我首先查看了TextField中的源码,看到了ToolbarOptions、AdaptiveTextSelectionToolbar,这时

java面试题-并发编程相关面试题

java面试题-并发编程相关面试题1线程的基础知识面试官:聊一下并行和并发有什么区别?候选人:是这样的~~现在都是多核CPU,在多核CPU下并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程面试官:说一下线程和进程的区别?候选人:嗯,好~进程

<Altium Designer> 将.DSN文件导入并转换成SchDoc文件

目录01使用向导方式导入.DSN02消除UniqueIdentifiersErrors03文章总结大家好,这里是程序员杰克。一名平平无奇的嵌入式软件工程师。本文主要是总结和分享将OrCADCapture画的原理图文件(.DSN)导入到AltiumDesigner,转换成对应的原理图文件(SchDoc)的方法。本文所使用

MySQL正则表达式:模式匹配、中文匹配、替换、提取字符串

在MySQL中,使用REGEXP或RLIKE操作符进行正则表达式匹配,而使用NOTREGEXP或NOTRLIKE操作符进行不匹配。一些常用的MySQL正则表达式语法:匹配字符:.:匹配任意字符(除了换行符)。[]:匹配方括号中的任意字符。[^]:匹配不在方括号中的任意字符。匹配重复:*:匹配零个或多个前面的字符。+:匹

【C++从0到王者】第三十一站:map与set

文章目录一、关联式容器二、pair键值对三、set1.set的介绍2.set的部分接口以及应用3.count4.lower_bound和upper_bound5.equal_range6.multiset容器四、map1.map的介绍2.map的一些常见接口以及使用3.map的[]运算符重载4.使用map改进一些题5.

热文推荐