为了更好的阅读体验,请点击这里

题目链接:Travel Plan

题目大意:\(n\) 个点的完全二叉树,每个点可以分配 \(1 \sim m\) 的点权,定义路径价值为路径中最大的点权,求所有路径的价值和。

对于任意长度(这里主要指包括几个节点)的路径 \(t\),最大点权不超过 \(k\) 的方案数有 \(k^t\) 个, 因此最大点权恰好为 \(k\) 的方案数有 \(k^t - (k-1)^t\)。所以,对于任意一条长度为 \(t\) 的路径,不考虑不在路径上其他点的影响时,其对于答案的贡献为:

\[\begin{aligned}
\text{path contribution}_t &= \sum_{k=1}^m (k^t - (k-1)^t) \cdot k \\
&= \sum_{k=1}^m \left( k^{t+1} - (k-1)^{t+1} - (k-1)^t \right) \\
&= m^{t+1} - \sum_{k=1}^{m-1} k^t
\end{aligned}
\]

由于路径长度不会超过 \(2 \log n\),因此求出全部长度路径分别对于答案的贡献时间复杂度为 \(O(m \log \log n)\)。

事实上,对于上面式子的第二项,可以用 Lagrange 插值、伯努利数、多项式等方法可以优化到 \(O(\log^2 n)\)。

下一步,问题转化为求出路径长度为 \(t\) 的个数分别是多少,然后乘一下即可。

第一种方法是点分治,显然复杂度是不够的,因为有 \(O(n \log n)\)。

第二种方法是题解做法。

首先,在这个完全二叉树中,不同形状的子二叉树共有 \(O(\log n)\) 个,设叶子个数为 \(leaf_i\),那么其中包括两种类型:

  1. \(leaf_i = 2^{p-1}\) 时(\(p\) 是这个子二叉树的最大深度),那么以 \(i\) 为根的子树是一个完全二叉树,显然有 \(O(\log n)\) 个。
  2. \(leaf_i \not = 2^{p-1}\) 时,节点 \(i\) 的左右儿子必有一个满足其为 \(2\) 的幂次,而另一个不满足,以这样的点为根的子树中的根可以脑补为一条链的形状,因此也有 \(O(\log n)\) 个。

不妨设 \(dp_{i,j}\) 表示以 \(i\) 为根的子树中长度为 \(j\) 的路径个数,\(f_{i,j}\) 表示以 \(i\) 为根的子树中,以 \(i\) 为结束端点长度为 \(j\) 的路径个数。满二叉树时,转移方程应该为:

\[\begin{aligned}
f_{i,1} &= 1 \\
f_{i,j} &= f_{lson(i), j-1} + f_{rson(i), j-1} (j \geq 2) \\
dp_{i,1} &= size_i \\
dp_{i,j} &= dp_{lson(i),j} + dp_{rson(i), j} + \sum_{k=0}^{j-1} f_{lson(i), k} \times f_{rson(i), j - 1 - k} (j \geq 2) \\
\end{aligned}
\]

具体实现的时候,事实上一共 \(O(\log n)\) 个点,因此第二部分的算法复杂度为 \(O(\log^3 n)\),这里也可以用 FFT 优化这个式子做到 \(O(\log^2 n)\)。

不过官方题解说可以做到。

最后一步,由于第一步中没有考虑不在路径上的其他点的方案影响,因此需要乘上去。

\[ans = \sum_{t=1}^{\text{max path length}} dp_{1, t} \times \text{pathcon}_t \times m^{n-t}
\]

这个题,本质上还是很妙的。我们很容易思考到这是一个转化成各个部分对于总的答案的贡献这一思路,然而这个题目中固定路径长度 \(t\),然后计算分成不同长度路径对于答案贡献这一方式还是相当难想到的。

upd1:这个题数据造得不严,之前写的 fulltree() 函数这个题过了,但是在 ABC 321 E 上炸掉了。代码已修改。

#include<bits/stdc++.h>
using namespace std; typedef long long ll;
typedef double db;
typedef long double ld; #define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl template<typename Tp> IL void read(Tp &x) {
x=0; int f=1; char ch=getchar();
while(!isdigit(ch)) {if(ch == '-') f=-1; ch=getchar();}
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();}
x *= f;
}
int buf[42];
template<typename Tp> IL void write(Tp x) {
int p = 0;
if(x < 0) { putchar('-'); x=-x;}
if(x == 0) { putchar('0'); return;}
while(x) {
buf[++p] = x % 10;
x /= 10;
}
for(int i=p;i;i--) putchar('0' + buf[i]);
} const int LOGN = 65;
const int LOGNN = 150;
const int mod = 998244353; ll n;
int m, dpid_cnt = 0; int pathcon[LOGNN];
int f[LOGNN][LOGNN], dp[LOGNN][LOGNN]; ll ksm(ll a, ll b) {
ll ret = 1;
while (b) {
if (b & 1ll) ret = ret * a % mod;
a = a * a % mod;
b >>= 1ll;
}
return ret;
} pair<int, ll> depl(ll u) {
if ((u << 1ll) > n) {
return mk(1, u);
}
auto p = depl(u << 1ll);
return mk(p.fi + 1, p.se);
} int depr(ll u) {
if ((u << 1ll | 1ll) > n) {
return 1;
}
return depr(u << 1ll | 1ll) + 1;
} bool fulltree(ll u) {
if ((u << 1ll) > n && (u << 1ll | 1ll) > n) return true;
if ((u << 1ll) <= n && (u << 1ll | 1ll) > n) return false;
return (depl(u << 1ll).fi == depr(u << 1ll | 1ll));
} ll getsz(ll u) {
if ((u << 1ll) > n) return 1;
if ((u << 1ll | 1ll) > n) return 2;
auto p = depl(u);
int dr = depr(u);
// dbg1(u); dbg1(p.fi); dbg1(p.se); dbg1(dr); dbg1((1ll << (1ll * dr)) - 1); dbg2((1ll << (1ll * dr)) - 1 + (n - p.se + 1));
if (p.fi == dr) return (1ll << (1ll * dr)) - 1;
else {
return (1ll << (1ll * dr)) - 1 + (n - p.se + 1);
}
} unordered_map<ll, int> dpid, szcnt; void dfs(ll u) {
int uid;
ll szu = getsz(u);
if (dpid.count(szu) == 0) dpid[szu] = uid = ++dpid_cnt;
else return; f[uid][0] = f[uid][1] = 1; dp[uid][0] = 1;
dp[uid][1] = szu % mod;
if (!fulltree(u)) szcnt[u] = 1; if ((u << 1ll) > n) return;
else if((u << 1ll | 1ll) > n) {
dfs(u << 1ll);
f[uid][2] = dp[uid][2] = 1;
return;
} dfs(u << 1ll); dfs(u << 1ll | 1ll); int lid = dpid[getsz(u << 1ll)], rid = dpid[getsz(u << 1ll | 1ll)];
for (int j = 2; j <= 2 * LOGN; j++) {
f[uid][j] = (f[lid][j-1] + f[rid][j-1]) % mod;
dp[uid][j] = (dp[lid][j] + dp[rid][j]) % mod;
for (int k = 0; k < j; k++) {
dp[uid][j] = (dp[uid][j] + 1ll * f[lid][k] * f[rid][j - 1 - k]) % mod;
}
}
} void solve() {
dpid_cnt = 0; dpid.clear(); szcnt.clear();
memset(pathcon, 0, sizeof(pathcon));
memset(f, 0, sizeof(f));
memset(dp, 0, sizeof(dp));
read(n); read(m);
for (int t = 0; t <= (LOGN << 1); t++) {
pathcon[t] = ksm(m, t + 1);
for (int k = 1; k < m; k++) {
pathcon[t] = (1ll * pathcon[t] - ksm(k, t) + mod) % mod;
}
} dfs(1); int ans = 0;
for (int t = 1; t <= min(n, 2ll * LOGN); t++) {
if (dp[1][t] == 0) break;
ans = (ans + 1ll * dp[1][t] * pathcon[t] % mod * ksm(m, n - t)) % mod;
}
write(ans); putchar(10);
} int main() {
#ifdef LOCAL
freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
#endif
int T = 1;
read(T);
while(T--) solve();
return 0;
}

Codeforces 1868C/1869E Travel Plan 题解 | 巧妙思路与 dp的更多相关文章

  1. Codeforces Round #388 (Div. 2) 749E(巧妙的概率dp思想)

    题目大意 给定一个1到n的排列,然后随机选取一个区间,让这个区间内的数随机改变顺序,问这样的一次操作后,该排列的逆序数的期望是多少 首先,一个随机的长度为len的排列的逆序数是(len)*(len-1 ...

  2. PAT1030 Travel Plan (30)---DFS

    (一)题意 题目链接:https://www.patest.cn/contests/pat-a-practise/1030 1030. Travel Plan (30) A traveler's ma ...

  3. Codeforces Round #546 (Div. 2) 题解

    Codeforces Round #546 (Div. 2) 题目链接:https://codeforces.com/contest/1136 A. Nastya Is Reading a Book ...

  4. PAT 甲级 1030 Travel Plan (30 分)(dijstra,较简单,但要注意是从0到n-1)

    1030 Travel Plan (30 分)   A traveler's map gives the distances between cities along the highways, to ...

  5. Codeforces Round #466 (Div. 2) 题解940A 940B 940C 940D 940E 940F

    Codeforces Round #466 (Div. 2) 题解 A.Points on the line 题目大意: 给你一个数列,定义数列的权值为最大值减去最小值,问最少删除几个数,使得数列的权 ...

  6. Codeforces Round #677 (Div. 3) 题解

    Codeforces Round #677 (Div. 3) 题解 A. Boring Apartments 题目 题解 简单签到题,直接数,小于这个数的\(+10\). 代码 #include &l ...

  7. Codeforces Round #182 (Div. 1)题解【ABCD】

    Codeforces Round #182 (Div. 1)题解 A题:Yaroslav and Sequence1 题意: 给你\(2*n+1\)个元素,你每次可以进行无数种操作,每次操作必须选择其 ...

  8. PAT 1030 Travel Plan[图论][难]

    1030 Travel Plan (30)(30 分) A traveler's map gives the distances between cities along the highways, ...

  9. 1030 Travel Plan (30 分)

    1030 Travel Plan (30 分) A traveler's map gives the distances between cities along the highways, toge ...

  10. [图算法] 1030. Travel Plan (30)

    1030. Travel Plan (30) A traveler's map gives the distances between cities along the highways, toget ...

随机推荐

  1. 如何参与 .NET 的开发和设计

    现在 dotnet 属于 dotnet 基金会,所有开发者都可以向 dotnet 贡献代码和参与 .NET 的设计,参与路线决策.本文来告诉大家一些基本玩法,带着小伙伴们入坑 注意哦,参与 dotne ...

  2. 03. go-zero简介及如何学go-zero

    目录 一.go-zero简介及如何学go-zero 1.go-zero官方文档 2.go-zero微服务框架入门教程 3.go-zero最佳实践 4.学习资料 二.go-zero环境搭建 1.GO环境 ...

  3. csapp-bomblab(自信满满版)

    反汇编bomb文件 要查看机器代码文件的内容,有一类称为反汇编器(disassembler,assembler是汇编程序,dis-加在某些词语前表示相反的意思)的程序非常有用.这些程序根据机器代码产生 ...

  4. Vue3.x+springboot集成pageoffice

    说明:由于pageoffice浏览器是ie内核,vue3不兼容ie.所以需要把页面放在后端 一,前端项目: 1.index.html页面引用pageoffice.js <script type= ...

  5. Spring IoC注解式开发无敌详细(细节丰富)

    1. Spring IoC注解式开发无敌详细(细节丰富) @ 目录 1. Spring IoC注解式开发无敌详细(细节丰富) 每博一文案 2. 注解回顾 3. Spring 声明Bean的注解 3.1 ...

  6. vue-router单页面应用的多标签页使用问题

    正常的思维 做多vue页面应用,我们的第一反应是配置多个入口点,多个vue应用,编译成多个HTML文件,由服务器来决定路由.这是正常的思维. 但谁知道单页面应用也能做到类似的效果呢.单页面不过是服务器 ...

  7. win10找回Ubuntu启动项(非EasyBCD)

    最近想对装在电脑上的Ubuntu进行更新,但是之前在BIOS里改了引导系统的文件,导致找不到Ubuntu启动项,EasyBCD程序也不起作用(整块硬盘Windows分区都是GPT,改BIOS也没什么用 ...

  8. Django——messages消息框架

    在网页应用中,我们经常需要在处理完表单或其它类型的用户输入后,显示一个通知信息给用户.对于这个需求,Django提供了基于Cookie或者会话的消息框架messages,无论是匿名用户还是认证的用户. ...

  9. 2024-06-08:用go语言,给定三个正整数 n、x和y, 表示城市中的房屋数量以及编号为x和y的两个特殊房屋。 在这座城市中,房屋通过街道相连。对于每个编号i(1 <= i < n), 存在一条

    2024-06-08:用go语言,给定三个正整数 n.x和y, 表示城市中的房屋数量以及编号为x和y的两个特殊房屋. 在这座城市中,房屋通过街道相连.对于每个编号i(1 <= i < n) ...

  10. CF1184E1题解

    CF11841E1 & blog 尽然想让第一条边最大且这条边在最小生成树中,那么这条边就需要尽量晚. 但是假如加上一条边 \(i\) 可以使 \(u_1\) 和 \(v_1\) 联通并且第 ...