题意:给一棵树,两个参数 k,Lk,Lk,L,需要选择 kkk 个连通块,使得这 kkk 个连通块存在一个公共点,且该公共点到 kkk 个连通块内的任意一点的距离不超过 LLL,求方案数 模 998244353998244353998244353。两种方案不同当且仅当连通块的集合不同。

n≤106,k≤10n\leq 10^6,k\leq 10n≤106,k≤10

已经写绝望了

对于一种连通块的集合,合法的钦定的点一定是一个连通块。所以可以通过 点数−边数=1点数-边数=1点数−边数=1 来容斥。即考虑每个点的贡献,再减去对于每条边,两个端点都合法的方案。

然后考虑暴力 dp。

设 f(u,L)f(u,L)f(u,L) 为以 uuu 为根的子树内,包含 uuu,到的距离不超过 LLL 的连通块个数 +1+1+1(为了方便转移,也可理解为允许为空)。

g(u,L)g(u,L)g(u,L) 表示 uuu 往上走,即必须包含 uuu,且不能包含 uuu 子树内其他结点,到 uuu 的距离不超过 LLL 的连通块个数。(注意 +1+1+1,即不能为空。)

得到转移

f(u,L)=∏v∈son(u)f(v,L−1)+1f(u,L)=\prod_{v\in son(u)}f(v,L-1)+1f(u,L)=v∈son(u)∏​f(v,L−1)+1

边界 f(u,0)=1f(u,0)=1f(u,0)=1

g(u,L)=g(fau,L−1)∏v∈son(fau),v≠uf(v,L−2)+1g(u,L)=g(fa_u,L-1)\prod_{v\in son(fa_u),v\neq u}f(v,L-2)+1g(u,L)=g(fau​,L−1)v∈son(fau​),v​=u∏​f(v,L−2)+1

边界 f(u,0)=f(u,−1)=1f(u,0)=f(u,-1)=1f(u,0)=f(u,−1)=1。后面这个 +1+1+1 表示 {u}\{u\}{u} 这个连通块。

最终答案为

∑u=1n(f(u,L)−1)kg(u,L)k−[u≠rt](f(u,L−1)−1)k(g(u,L)−1)k\sum_{u=1}^n(f(u,L)-1)^kg(u,L)^k-[u\neq rt](f(u,L-1)-1)^k(g(u,L)-1)^ku=1∑n​(f(u,L)−1)kg(u,L)k−[u​=rt](f(u,L−1)−1)k(g(u,L)−1)k

发现状态和深度有关,考虑长链剖分。以下设 mxumx_umxu​ 表示 uuu 到子树内最远点经过的 点数,简称深度。


f(u,L)=∏v∈son(u)f(v,L−1)+1f(u,L)=\prod_{v\in son(u)}f(v,L-1)+1f(u,L)=v∈son(u)∏​f(v,L−1)+1

这个是经典的长链剖分的形式,直接继承长儿子的信息,短儿子暴力转移。

然后状态定义的是不超过,所以你维护的只是 DP 数组里 [0,mxu)[0,mx_u)[0,mxu​) 的信息, [mxu,+∞)[mx_u,+\infin)[mxu​,+∞) 也是有值的。如果暴力到长儿子的深度会让复杂度退化。

不过注意到 [mxu,+∞)[mx_u,+\infin)[mxu​,+∞) 内的值都是 f(u,mxu−1)f(u,mx_u-1)f(u,mxu​−1),所以相当于是个后缀乘法。然后 DP 式子后面还有个 +1 ,相当于要维护以下操作:

  1. 单点修改,要求 O(1)O(1)O(1)
  2. 全局加,要求 O(1)O(1)O(1)
  3. [x,+∞)[x,+\infin)[x,+∞) 乘,要求 O(x)O(x)O(x)

这可以通过打全局标记来实现。具体来讲,我们对当前点 uuu 维护两个标记 mulu,plsumul_u,pls_umulu​,plsu​,表示存储的一个数 xxx 表示的真实值为 mulux+plsumul_u x+pls_umulu​x+plsu​。

2 操作直接改标记,3操作修改 mulumul_umulu​ 后把 [0,x)[0,x)[0,x) 乘上逆元,1 操作改完后倒着把存储的值算出来放进去,就可以做到 O(n)O(n)O(n)。

你以为这就完了?奶义务!

乘上的这个数可能在模意义下为 000,是没有逆元的,并且不像一年后的某道莫反矩阵树缝合怪题,这个东西非常好构造,直接连长度分别为 2,2,⋯,2⏟23,6,16\begin{matrix} \underbrace{ 2,2,\cdots,2 } \\ 23\end{matrix},6,162,2,⋯,2​23​,6,16 的链就可以了。

所以我为什么没在 CSP 前看到这个东西

所以我们需要再开两个标记 limu,valulim_u,val_ulimu​,valu​,表示 [limu,+∞)[lim_u,+\infin)[limu​,+∞) 这一段的存储的值是 valuval_uvalu​。如果这个数是 000,相当于后缀赋值,把 limulim_ulimu​ 赋值为 xxx,valuval_uvalu​ 赋值为真实值为 000 时对应的存储值。

Q:为什么不能定义为"limulim_ulimu​ 及之后的数都是 000",还可以少开个标记?

A:因为这里只是暂时为 000,之后的全局加对这里是有影响的。

这样做到了 O(nlog⁡P)O(n\log P)O(nlogP)。注意到每次求逆元的都是 f(v,mxv−1)f(v,mx_v-1)f(v,mxv​−1) ,即不限制距离的方案数,所以可以先做一个简单的 DP 算出来,然后 O(n)O(n)O(n) 离线求逆元,注意要跳过为 000 的。维护 mulumul_umulu​ 标记的时候顺便维护一下它的逆元,就可以 O(n)O(n)O(n) 了。


g(u,L)=g(fau,L−1)∏v∈son(fau),v≠uf(v,L−2)+1g(u,L)=g(fa_u,L-1)\prod_{v\in son(fa_u),v\neq u}f(v,L-2)+1g(u,L)=g(fau​,L−1)v∈son(fau​),v​=u∏​f(v,L−2)+1

大家可能会觉得很奇怪,这个往上走的 DP 怎么能用长链剖分优化呢?

注意到我们答案需要的只有 g(u,L)g(u,L)g(u,L),所以对于一个叶子结点,它没有儿子需要它的其他信息,所以只需要维护 g(u,L)g(u,L)g(u,L) 这一个位置。类似的,对于点 uuu ,我们只需要维护 [L−mxu+1,L][L-mx_u+1,L][L−mxu​+1,L] 中的值。

也就是说我们规定 g(u,…)g(u,\dots)g(u,…) 的定义域只有 [max⁡(L−mxu+1,0),L][\max(L-mx_u+1,0),L][max(L−mxu​+1,0),L],这样状态数就和深度正相关了。

把信息直接继承给长儿子,短儿子暴力转移,再乘上一个 f(u,L−1)−1f(v,L−2)\frac{f(u,L-1)-1}{f(v,L-2)}f(v,L−2)f(u,L−1)−1​

然后你又错了,因为 f(v,L−2)f(v,L-2)f(v,L−2) 可能没有逆元。所以我们只能算前缀积和后缀积了。

前缀积在遍历的时候可以顺便维护。为了方便实现,可以把每个结点的轻儿子按深度从小到大排序,这样你只需要记 333 个标记。严格意义上需要桶排保证复杂度,不过直接 sort 也能过。之后假装这个排序是 O(n)O(n)O(n) 的。

然后开一个数组 preprepre ,用 preipre_iprei​ 记录 f(v,i)f(v,i)f(v,i) 的前缀积就可以了,配合后缀赋值标记就可以维护整个前缀积。

对于后缀积是不能跑一遍记下来的,因为开不下……

但我们在计算 fff 的时候做了一遍这东西,怎么能浪费了呢?

我们在计算 fff 的时候倒着做,即按轻儿子深度从大到小排序。对于 dpdpdp 值和 555 个标记的修改,把它修改的过程记录下来,对就是可撤销并查集的那个东西。

然后在算 ggg 的时候不断把修改撤销,这样 f(u,L−1)f(u,L-1)f(u,L−1) 维护的就是后缀积。

这样只能算出轻儿子,重儿子因为撤回不了,所以需要再利用之前你算的前缀积单独搞一下。

因为还是有乘法和全局加操作,所以你还是得维护一堆标记。并且尽管定义域很有限,为了保证复杂度,你还是得维护后缀赋值标记。注意这个标记和前缀积的标记没有关系

需要注意的细节:

  1. 因为有边界情况,需要手动把 f(u,0)f(u,0)f(u,0) 改成 111。注意因为定义不同,fff 需要先改 000 再全局加,而 ggg 是全局加了再改 000。并且 ggg 还要判断 000 在不在定义域内。
  2. 边界情况 limulim_ulimu​ 需要维护准确值,或者用其他一些骚操作,不然之前的值会出问题。
  3. 因为定义域的问题,不能偷懒把前缀积挂在 g(u,L−1)g(u,L-1)g(u,L−1) 上。
  4. 回退时的 f(u,L−1)f(u,L-1)f(u,L−1) 实际上维护的是 f(v,L−2)f(v,L-2)f(v,L−2) 的后缀积,在 L=1L=1L=1 的时候是未定义的,需要特判。

复杂度 O(nlog⁡k)O(n\log k)O(nlogk)

用尽各种毒瘤方法把一个不可做的计数题做到线性,最后却因为一个 101010 的快速幂无法把复杂度写成 O(n)O(n)O(n),真是悲壮……

代码中的迷惑部分都有注释。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
#include <utility>
#include <list>
#include <algorithm>
#define MAXN 1000005
using namespace std;
inline int read()
{int ans=0;char c=getchar();while (!isdigit(c)) c=getchar();while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();return ans;
}
const int MOD=998244353;
typedef long long ll;
inline int add(const int& x,const int& y){return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){return x<y? x-y+MOD:x-y;}
inline int qpow(int a,int p)
{int ans=1;while (p){if (p&1) ans=(ll)ans*a%MOD;a=(ll)a*a%MOD,p>>=1;}return ans;
}
vector<int> T[MAXN],e[MAXN];//T 是所有相邻的点,e 是所有轻儿子
int fa[MAXN],son[MAXN],mx[MAXN],s[MAXN],sinv[MAXN],n,L,k;
void dfs(int u,int f)
{fa[u]=f,s[u]=1;for (int i=0;i<(int)T[u].size();i++)if (T[u][i]!=f){dfs(T[u][i],u);if (mx[T[u][i]]>mx[son[u]]) son[u]=T[u][i];s[u]=(ll)s[u]*s[T[u][i]]%MOD;}mx[u]=mx[son[u]]+1;s[u]=add(s[u],1);
}
int fac[MAXN],finv[MAXN];
inline bool cmp(const int& x,const int& y){return mx[x]>mx[y];}
inline void init()
{fac[0]=1;for (int i=1;i<=n;i++)if (s[i]) fac[i]=(ll)fac[i-1]*s[i]%MOD;else fac[i]=fac[i-1];finv[n]=qpow(fac[n],MOD-2);for (int i=n-1;i>=1;i--)if (s[i+1]) finv[i]=(ll)finv[i+1]*s[i+1]%MOD;//跳过 0,后同else finv[i]=finv[i+1];for (int i=1;i<=n;i++) if (s[i]) sinv[i]=(ll)finv[i]*fac[i-1]%MOD;for (int i=1;i<=n;i++) stable_sort(e[i].begin(),e[i].end(),cmp);//stable 是为了方便调试
}
void dfs(int u)
{if (son[u]) dfs(son[u]);for (int i=0;i<(int)T[u].size();i++)if (T[u][i]!=fa[u]&&T[u][i]!=son[u])e[u].push_back(T[u][i]),dfs(T[u][i]);
}
int F1[MAXN],F2[MAXN],G1[MAXN];
struct BackDS
{typedef pair<int*,int> pi;list<pi> his;inline void modify(int& x,int v){his.push_back(make_pair(&x,x)),x=v;}inline void undo(){while (!his.empty()) *his.back().first=his.back().second,his.pop_back();}
}q[MAXN];
namespace F
{int buf[MAXN],*cur=buf;int* dp[MAXN];inline int* newbuf(int x){int* p=cur;cur+=x;return p;}int mul[MAXN],inv[MAXN],pls[MAXN],lim[MAXN],val[MAXN];inline int calc(int u,int i)//计算真实值{if (i<lim[u]) return ((ll)mul[u]*dp[u][i]+pls[u])%MOD;else return ((ll)mul[u]*val[u]+pls[u])%MOD;}inline int clac(int u,int v){return (ll)dec(v,pls[u])*inv[u]%MOD;}//根据真实值的得到应该存储的值void dfs(int u){if (son[u]){dp[son[u]]=dp[u]+1;dfs(son[u]);mul[u]=mul[son[u]],inv[u]=inv[son[u]],pls[u]=pls[son[u]];lim[u]=lim[son[u]]+1,val[u]=val[son[u]];dp[u][0]=clac(u,1);     }else{mul[u]=inv[u]=lim[u]=1,pls[u]=F1[u]=F2[u]=2;return;}int las=0;for (int k=0;k<(int)e[u].size();k++){int v=las=e[u][k];dp[v]=newbuf(mx[v]),dfs(v);for (int i=1;i<=mx[v];i++){if (i==lim[u])  q[v].modify(dp[u][i],val[u]), q[v].modify(lim[u],lim[u]+1);q[v].modify(dp[u][i],clac(u,(ll)calc(u,i)*calc(v,i-1)%MOD));}if (s[v]){q[v].modify(mul[u],(ll)mul[u]*s[v]%MOD);q[v].modify(inv[u],(ll)inv[u]*sinv[v]%MOD);q[v].modify(pls[u],(ll)pls[u]*s[v]%MOD);for (int i=0;i<=mx[v];i++)  q[v].modify(dp[u][i],clac(u,(ll)calc(u,i)*sinv[v]%MOD));}else  q[v].modify(lim[u],mx[v]+1),q[v].modify(val[u],clac(u,0));}if (las) q[las].modify(pls[u],add(pls[u],1));//把全局加挂在最后一个轻儿子上,这样一来就会撤回else pls[u]=add(pls[u],1);//没有轻儿子的话反正都没有用,随便加F1[u]=calc(u,L),F2[u]=calc(u,L-1);}inline void solve(){dp[1]=newbuf(mx[1]),dfs(1);}
}
namespace G
{int buf[MAXN],pre[MAXN],*cur=buf;int* dp[MAXN];inline int* newbuf(int x){int* p=cur;cur+=x;return p;}int mul[MAXN],inv[MAXN],pls[MAXN],lim[MAXN],val[MAXN];inline int calc(int u,int i){if (i<lim[u]) return ((ll)mul[u]*dp[u][i]+pls[u])%MOD;return ((ll)mul[u]*val[u]+pls[u])%MOD;}inline int clac(int u,int v){return (ll)dec(v,pls[u])*inv[u]%MOD;}void dfs(int u){G1[u]=calc(u,L);pre[0]=1;int pos=1,cur=1,cinv=1;for (int k=(int)e[u].size()-1;k>=0;k--)//按深度从小到达枚举{int v=e[u][k];q[v].undo();dp[v]=newbuf(mx[v])-max(0,L-mx[v]+1);mul[v]=inv[v]=1,lim[v]=L+1;for (int i=max(0,L-mx[v]+1);i<=L;i++){int t=1;if (i) t=(ll)t*calc(u,i-1)%MOD;if (i>1){t=(ll)t*F::calc(u,i-1)%MOD;//见细节4if (i-2<pos) t=(ll)t*pre[i-2]%MOD;else t=(ll)t*cur%MOD;    }dp[v][i]=clac(v,t);}pls[v]=add(pls[v],1);if (L-mx[v]+1<=0) dp[v][0]=clac(v,1);//是否在定义域内for (int i=0;i<=mx[v];i++){if (i<pos) pre[i]=(ll)pre[i]*F::calc(v,i)%MOD;else pre[i]=(ll)cur*F::calc(v,i)%MOD;}pos=mx[v]+1;cur=(ll)cur*s[v]%MOD,cinv=(ll)cinv*sinv[v]%MOD;}int v=son[u];if (v){mul[v]=mul[u],inv[v]=inv[u],pls[v]=pls[u],lim[v]=lim[u]+1,val[v]=val[u];dp[v]=dp[u]-1;for (int i=max(2,L-mx[v]+1);i<=pos+1;i++){if (i==lim[v]) dp[v][lim[v]++]=val[v];dp[v][i]=clac(v,(ll)calc(v,i)*pre[i-2]%MOD);    } if (cur){mul[v]=(ll)mul[v]*cur%MOD;pls[v]=(ll)pls[v]*cur%MOD;inv[v]=(ll)inv[v]*cinv%MOD;for (int i=max(0,L-mx[v]+1);i<=pos+1;i++) dp[v][i]=clac(v,(ll)calc(v,i)*cinv%MOD);}else lim[v]=pos+1,val[v]=clac(v,0);pls[v]=add(pls[v],1);if (L-mx[v]+1<=0) dp[v][0]=clac(v,1);dfs(v);}for (int i=0;i<(int)e[u].size();i++) dfs(e[u][i]);//算完再递归,避免 pre 冲突}inline void solve(){dp[1]=newbuf(mx[1])-max(L-mx[1]+1,0),mul[1]=inv[1]=pls[1]=1,lim[1]=L+1,dfs(1);}
}
int main()
{n=read(),L=read(),k=read();if (!L) return printf("%d\n",n),0;for (int i=1;i<n;i++) {int u,v;u=read(),v=read();T[u].push_back(v),T[v].push_back(u);}dfs(1,0),dfs(1);init();F::solve(), G::solve();int ans=0;for (int i=1;i<=n;i++){ans=add(ans,qpow((ll)dec(F1[i],1)*G1[i]%MOD,k));if (i>1) ans=dec(ans,qpow((ll)dec(F2[i],1)*dec(G1[i],1)%MOD,k));}cout<<ans;return 0;
}

【十二省联考2019】希望【点边容斥】【换根dp】【长链剖分】【线性数据结构】【回退数据结构】【离线逆元】相关推荐

  1. 十二省联考 2019 题解

    [十二省联考2019]异或粽子 首先异或转前缀和,类似超级钢琴,将三元组 ( l , r , p ) (l,r,p) (l,r,p) 插入堆,表示 s u m [ p ] sum[p] sum[p] ...

  2. 【BZOJ5498】[十二省联考2019]皮配(动态规划)

    [BZOJ5498][十二省联考2019]皮配(动态规划) 题面 BZOJ 洛谷 题解 先考虑暴力\(dp\),设\(f[i][j][k]\)表示前\(i\)所学校,有\(j\)人在某个阵营,有\(k ...

  3. [十二省联考2019]春节十二响——长链剖分+堆

    题目链接: [十二省联考2019]春节十二响 可以发现每条链上的所有点都要放在不同的段里,那么最多只需要树的深度这么多段就够了. 因为这样可以保证每条链上的点可以放在不同的段中而且一个点放在这些段中一 ...

  4. 【十二省联考2019】春节十二响

    题面 https://www.luogu.org/problem/P5290 题解 真的是我傻逼,十二省联考$day2$至今还是我的噩梦.$day1$起码一直在调可持久化$trie$树,$day2$真 ...

  5. 「十二省联考 2019」皮配——dp

    题目 [题目描述] #### 题目背景 一年一度的综艺节目<中国好码农>又开始了.本季度,好码农由 Yazid.Zayid.小 R.大 R 四位梦想导师坐镇,他们都将组建自己的梦想战队,并 ...

  6. 「十二省联考 2019」希望

    Solution 题意简述:选出 k k k 个树上连通块,使得存在一个点 u u u 满足: 1. u u u 在这 k k k 个连通块的交集之中. 2.对于这 k k k 个连通块中的任意一点 ...

  7. 十二省联考2019酱油记

    在中考前去省选玩一趟. Day -1 对于一个还没有学会所有省选内容的初三Oier来说,这一趟真的是去打酱油的啊.但还是要认真复习. 最近几天在字符串的路上越走越远-晚上才开始复习图论.还有一大堆没有 ...

  8. 十二省联考 2019 简要题解

    xor 暴力. #include <bits/stdc++.h>using namespace std;typedef unsigned int uint;const int N = 52 ...

  9. HAOI(十二省联考)2019 qwq记

    \(\large{Day\ -1}:\) 放假了,白天大概是抱着最后一次在机房的心态复习着板子过去的.看着机房里的各位神仙丝毫不慌的颓倒是有点慌了,敲了一下多项式的板子感觉写的相当自闭,感觉AFO应该 ...

最新文章

  1. 若谷歌实用量子计算机难产,拉里·佩奇会把它砍掉吗?
  2. YOLOX——Win10下训练自定义VOC数据集
  3. 从去除噪点的说起,有OpenCV要什么PS?
  4. MySql分表、分库、分片和分区知识(转载)
  5. .NET 现代化动态 LINQ 库 Gridify
  6. [C++11]使用using和typedef给模板定义别名
  7. 4位大佬解读:“医疗人工智能、信息化、政策与科研”的新风向与新趋势
  8. 递归——黑白棋子的移动(洛谷 P1259)
  9. Node.js 抓取电影天堂新上电影节目单及ftp链接
  10. 显示收货地址页面html,收货地址.html
  11. 助力泵嗡嗡响解决方法_突破不可能,3D打印革命性的制造高效高扭矩的径向活塞泵...
  12. 6.4 Ext.core.DomQuery 单实例查询选择器,通过正则表达式查找DOM或者HTM
  13. java执行脚本命令 学习笔记
  14. 反地理编码 高德地图_由中文地址返回点位坐标-地理编码脚本分享
  15. 重磅!《中华人民共和国个人信息保护法》今日起施行!
  16. Win11打印机任务在哪里?Win11查看打印机任务列表的方法
  17. 关于服务端渲染/预渲染/数据直出/页面直出的学习总结
  18. 稻盛和夫:中国企业如何在萧条中实现大飞跃
  19. vue-awesome-swiper滑动失效的问题解决方案
  20. 彩色图像加密matlab算法,彩色图像混沌加密算法

热门文章

  1. 百般受虐!“波士屯动力”机器人这一次枪口对准人类
  2. 一个人开始废掉的3种迹象
  3. 扎克伯格做了26张PPT,员工效率提10倍,已被疯狂传阅!
  4. android 横向stepview,Android 流程指示器 StepView
  5. 电脑温度检测软件哪个好_实时检测Mac电脑的温度
  6. lumen 配置数据库结果自动转数组_lumen 数据库操作 Cannot use object of type stdClass as array...
  7. 刷新mac地址命令_配置好Cisco交换机需要熟悉IOS命令及相关的知识
  8. 中南民族大学c语言报告,中南民族大学信C语言实验报告.doc
  9. C++ 指向类成员的指针
  10. 高等数学下-赵立军-北京大学出版社-题解-练习8.5