首页 > 其他分享 >Codeforces 1868C/1869E Travel Plan 题解 | 巧妙思路与 dp

Codeforces 1868C/1869E Travel Plan 题解 | 巧妙思路与 dp

时间:2023-09-13 22:34:08浏览次数:52  
标签:return log int 题解 Travel 路径 Codeforces ll dp

题目链接: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{path contribution}_t \times m^{n-t} \]

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

#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) {
    return ((u << 1ll) > n && (u << 1ll | 1ll) > n) || (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;

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 ((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();
    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;
}

标签:return,log,int,题解,Travel,路径,Codeforces,ll,dp
From: https://www.cnblogs.com/bringlu/p/17700958.html

相关文章

  • 网络规划设计师真题解析--独立磁盘冗余阵列RAID(一)
    RAID1中数据冗余是通过什么技术实现的()。A.OXR运算    B.海明码校验    C.P+Q双校验    D.镜像答案:D解析:RAID1,磁盘镜像,可并行读数据,由于在不同的两块磁盘写入相同数据,写入数据比RAID0慢一些。安全性最好,但空间利用率最低。实现RAID1至少需要2块硬盘。《网络规划设......
  • Codeforces Round 781 (Div. 2) B. Array Cloning Technique
    给一个长度为\(n\)的数组\(a\)。开始只有一份所给\(a\)的副本。你可以做以下两种操作:选择任意一个副本并且克隆它,然后将会多出一个克隆副本。交换两个元素,他们属于任意两个副本(可能是同一个)。需要判断最小操作数,使有一个副本的所有元素相同。观察一:只需要在开始的副本......
  • CF1043D Mysterious Crime 题解
    CF1043DMysteriousCrime题解题意给定\(m\)个长为\(n\)的序列,问它们的公共子串的个数。\(n\le10^5,m\le10\)。已经死掉的做法一眼广义后缀自动机。建出后缀自动机,然后在parenttree上面跑dfs。正确性会在下面证明。但是因为广义SAM巨大的常数,蒟蒻的代码跑了1......
  • Codeforces Round 787 (Div. 3) B. Make It Increasing
    给一个长为\(n\)的数组\(a_1,a_2,\cdots,a_n\quad(0\leqa_i\leq10^9)\)。可以执行以下操作任意次:选择任意一个\(a_i\)并且执行\(a_i=\lfloor\frac{a_i}{2}\rfloor\)。输出最小操作次数,使得数组所有元素变为严格递增。观察:数组一些位置变小,将数组变为严......
  • Codeforces Round 791 (Div. 2) A. AvtoBus
    已知有\(n\)个轮子,会有一个车队车来换轮,且恰好使用完这些轮子。只知道这些车中有\(4\)轮车和\(6\)轮车。你需要估计这个车队最少可能有多少车和最多可能有多少车,或判断这是完全不可能的。观察:\(4x+6y=n\),由裴蜀定理,当\(2\midn\)有解且\(2x+3y=\frac{n}{2}\)......
  • 2023短学期0913题解
    将字符串作为输入流来处理(提取单词)【C系列4.7】函数训练之暗号Descriptioncyn小朋友今天参加了小学举办的侦探活动,她的任务是从暗号纸条的内容上找出特工Q给出的所有的暗号(即Q开头的单词)Input输入一串含有空格的字符串,字符串的长度不超过300。Output按顺序每行......
  • 【题解】[POI2015] MOD
    传送门挺恶心的感觉这题代码,就来写写题解。题目分析假设我们现在要删掉\((x,y)\)这条边,思考这样能贡献的最大或最小直径。不难发现,此时一棵树分裂成了两棵树\(a,b\),我们令它们的直径分别为\(la,lb\)。将两棵树内直径的任意端点连起来,发现\(maxi=la+lb+1\)。将两棵树内直......
  • 洛谷 CF707C Pythagorean Triples の 题解
    这道题是一道数论题,不可用暴力通过,因为输入范围极大,基本上循环是不能在这道题上使用的了。前面大佬们讲的我听不懂,于是在教练的帮助下,我利用题面给出的多组样例找到了规律。在此之前,我们先设输入的数为\(n\)。\(n\)分三种情况。\(n\)是奇数;\(n\)是偶数;\(n\)小于等于......
  • 洛谷 P9503『MGOI』Simple Round I | B. 魔法照相馆 の 题解
    这道题是一道模拟题,坑点不多,但是细节特多,所以导致大部分人\(A\)不了这道题。这道题我也写了注释,如果思路没明白可以看代码和注释的。先创建一个长度为\(3\)的字符串\(s1\),这个字符串的意思就是模拟现在的这几个幕布的情况,这里分了四个字符代表着四种情况,详细如下该字符串......
  • 洛谷 P9502 『MGOI』Simple Round I | A. 魔法数字 の 题解
    直接用pow()函数暴力判断即可,一旦不符合条件就立即跳出循环,要注意开longlong或unsignedlonglong。#include<iostream>#include<cmath>usingnamespacestd;unsignedlonglongn,num;intmain(){cin>>n;for(unsignedlonglongi=2;i<=n;i+=......