【拉格朗日优化dp】P4365 [九省联考 2018] 秘密袭击 coat

题目简述

  • 求树上所有连通块第 \(k\) 大点权(不足 \(k\) 点记为 \(0\))的和。
  • \(1\leq k\leq n\leq1666\),\(d_i\leq W\leq 1666\)。

解题思路

我们对第 \(k\) 大的和的这种问题,通常的处理方法是对值域枚举一个阈值 \(i\)。对于 \(\geq i\) 的点视作为 \(1\),\(< i\) 的点视作为 \(0\)。这样子问题就变成了了所有 \(i\),连通块第 \(k\) 大是 \(1\)(也就是有 \(\geq k\) 个 \(1\))的数目的和。

假设 \(f[u][j]\) 表示连通块以 \(u\) 为根且有 \(j\) 个 \(1\) 节点,我们可以把问题转成树上背包。

通过经典结论,合理控制枚举范围我们可以做到 \(O(n^2)\) 来转移,总得时间复杂度 \(O(Wn^2)\),随便常数优化一下跑得比正解还要快。

我们下面讨论正解。

众所周知的结论,树上背包可以转为生成函数,也就是说假设 \(F_u(x)=f[u][0]+f[u][1]x+f[u][2]x^2+\dots\)。那么就会有:

\[F_u(x)=x^{d[u]\geq i}\prod_{v\in u} (F_v(x)+1)
\]

多项式卷积很慢,而且不便于转移,所以我们不应该卷积,考虑点值表示法。

假设我们选取的点为 \(t\),那么转移变为:

\[F_u=t^{d[u]\geq i}\prod_{v\in u}(F_v+1)
\]

不难发现阈值 \(i\) 对整个 \(F\) 的影响比较小,我们不妨考虑换一种思考方式,也就是枚举点 \(t\),然后快速搞定每一个阈值。我们期望每一次枚举 \(t\) 可以做到 \(O(n\log W)\) 得到所有阈值的值。

我们考虑每个位置维护一个当前值数组 \(val\),那么每一个节点相当于是他所有叶节点的对应 \(val\) 位 \(+1\) 再相乘,然后再给 \([1,d[u]]\) 的位乘上 \(t\)。暴力乘积很慢,但是可以发现我们需要维护的东西是:

  • 全局加。
  • 区间乘。
  • 合并两个 \(val\),对应位相乘。

这个结构是很显然的线段树合并,这里就可以做到 \(O(n\log W)\) 得到所有阈值了。

还没完,有一个小的问题,就是一个连通块的根不是 \(1\) 时由于线段树合并了,所以他的 \(val\) 会被他的祖先修改掉导致不可用,解决这个问题,我们需要一个辅助生成函数 \(G_u(x)\) 表示所有 \(u\) 的子树节点的 \(val\),那我们的 \(G(x)\) 需要维护一个:

  • 合并两个 \(G\),对应位相加。
  • 把 \(F\) 的对应位相加到 \(G\)。

感觉对 \(G\) 单独再开个动态开点线段树可以做,但是写起来超级麻烦,介绍一个相对简单的方法。

考虑矩阵乘法:

\[\begin{bmatrix}f&g&1\end{bmatrix}\times \begin{bmatrix}a&b&0\\0 &1&0\\c&d&1\end{bmatrix}=\begin{bmatrix}af+c&bf+g+d&1\end{bmatrix}
\]

矩阵 \(\begin{bmatrix}a&b&0\\0 &1&0\\c&d&1\end{bmatrix}\) 具有可乘形式,具体来说:

\[\begin{bmatrix}a&b&0\\0 &1&0\\c&d&1\end{bmatrix}\times \begin{bmatrix}a'&b'&0\\0 &1&0\\c'&d'&1\end{bmatrix}=\begin{bmatrix}aa'&ab'+b&0\\0 &1&0\\ca'+c'&cb'+d+d'&1\end{bmatrix}
\]

我们可以维护这种矩阵,可以发现这种矩阵可以维护:

  • 全局加 \(1\):\(a=c=1,b=d=0\)。
  • 把 \(f\) 加到 \(g\) 上:\(a=b=1,c=d=0\)。

其他操作需要线段树合并。我们就完成了本题的求点值部分。

对于转回多项式,我们有拉格朗日插值法:

\[f(x)=\sum_{i=1}^n y_i\prod_{j\not =i} \dfrac{x-j}{i-j}
\]

不难发现这个式子每个 \(i\) 的常数可以先预处理出来,多项式卷积部分我们可以先求暴力 \(\prod(x-j)\) 然后每次除 \(x-i\) 即可,暴力除是 \(O(n)\) 的,原不及瓶颈线段树合并的大常数 \(O(n^2\log n)\)。

参考代码

#include<iostream>
#include<vector>
using namespace std;
#define ll long long
const int MOD=64123;
const int MAXN=1667;
int n,k,w;
ll d[MAXN],f[MAXN];
ll fac[MAXN],inf[MAXN];
int rt[MAXN];
vector<int> ve[MAXN];
int add(int x){return (x%MOD+MOD)%MOD;}
ll inv(ll a,int b=MOD-2){ll res=1;while(b){if(b&1) res=res*a%MOD;a=a*a%MOD;b>>=1;}return res;}
struct matrix{
ll a,b,c,d;
matrix(ll aa=0,ll bb=0,ll cc=0,ll dd=0){a=aa,b=bb,c=cc,d=dd;}
matrix operator *(matrix i){return matrix(1ll*a*i.a%MOD,add(1ll*i.a*b%MOD+i.b),
add(1ll*a*i.c%MOD+c),add(1ll*b*i.c%MOD+add(d+i.d)));}
bool operator ==(matrix i){return (i.a==a)&&(i.b==b)&&(i.c==c)&&(i.d==d);}
};
matrix I=(matrix){1,0,0,0};
struct node{
int ls,rs;
matrix val;
}t[MAXN*40];int tot=0;
struct poly{
vector<ll> ve;
void init(int s){ve.resize(s);}
poly operator +(poly x){
poly y;y.init(max(x.ve.size(),ve.size()));
for(int i=0;i<y.ve.size();i++)
y.ve[i]=add(((i<ve.size())?ve[i]:0)+
((i<x.ve.size())?x.ve[i]:0));
return y;
}
poly operator *(ll x){
poly y;y.init(ve.size());
for(int i=0;i<ve.size();i++)
y.ve[i]=ve[i]*x%MOD;
return y;
}
poly operator *(poly x){
poly y;y.init(x.ve.size()+ve.size()-1);
for(int i=0;i<x.ve.size();i++)
for(int j=0;j<ve.size();j++)
y.ve[i+j]=add(y.ve[i+j]+x.ve[i]*ve[j]%MOD);
return y;
}
poly operator /(ll x){
poly y;y.init(ve.size()-1);
ll res=ve[ve.size()-1];
for(int i=ve.size()-2;i>=0;i--){
y.ve[i]=res;
res=add(ve[i]+x*res%MOD);
}
return y;
}
};
void newnode(int &k){k=++tot;t[k]=node{0,0,I};}
void pushdown(int k){
if(t[k].val==I) return;
if(!t[k].ls) newnode(t[k].ls);
if(!t[k].rs) newnode(t[k].rs);
t[t[k].ls].val=t[t[k].ls].val*t[k].val;
t[t[k].rs].val=t[t[k].rs].val*t[k].val;
t[k].val=I;
}
void modify(int &id,int l,int r,int L,int R,matrix val){
if(l>R||r<L) return;
if(!id) newnode(id);
if(L<=l&&r<=R) {t[id].val=t[id].val*val;return;}
int mid=l+r>>1;
pushdown(id);
modify(t[id].ls,l,mid,L,R,val);
modify(t[id].rs,mid+1,r,L,R,val);
}
void merge(int &x,int y,int l,int r){
if(!t[x].ls&&!t[x].rs) swap(x,y);
if(!t[y].ls&&!t[y].rs){
t[x].val=t[x].val*matrix{t[y].val.b,0,0,t[y].val.d};
return;
}
int mid=l+r>>1;
pushdown(x);pushdown(y);
merge(t[x].ls,t[y].ls,l,mid);
merge(t[x].rs,t[y].rs,mid+1,r);
}
void dfs(int u,int fa,int x){
modify(rt[u],1,w,1,d[u],matrix{0,x,0,0});
modify(rt[u],1,w,d[u]+1,w,matrix{0,1,0,0});
for(int v:ve[u]) if(v!=fa) {
dfs(v,u,x);
modify(rt[v],1,w,1,w,matrix{1,1,0,0});
merge(rt[u],rt[v],1,w);
}
modify(rt[u],1,w,1,w,matrix{1,0,1,0});
return;
}
int query(int id,int l,int r){
if(l==r) return t[id].val.d;
int mid=l+r>>1;
pushdown(id);
return add(query(t[id].ls,l,mid)+query(t[id].rs,mid+1,r));
}
signed main(){
cin>>n>>k>>w;
for(int i=1;i<=n;i++)
cin>>d[i];
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
ve[u].push_back(v);
ve[v].push_back(u);
}
for(int i=0;i<=n;i++){
tot=0;
for(int j=1;j<=n;j++) rt[j]=0;
dfs(1,0,i);
f[i]=query(rt[1],1,w);
}
poly p;p.init(1);p.ve[0]=1;
poly v;v.init(2);v.ve[1]=1;
for(int i=0;i<=n;i++){
v.ve[0]=(MOD-i)%MOD;
p=p*v;
}
poly F;F.init(n+1);
fac[0]=inf[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%MOD,inf[i]=inv(fac[i]);
for(int i=0;i<=n;i++){
int val=1ll*f[i]*inf[i]%MOD*inf[n-i]%MOD;
if((n-i)&1) val=MOD-val;
poly y=(p/i)*val;
F=F+y;
}
int res=0;
for(int i=k;i<=n;i++)
res=add(res+F.ve[i]);
cout<<res;
return 0;
}

【拉格朗日优化dp】P4365 [九省联考 2018] 秘密袭击 coat的更多相关文章

  1. P4365 [九省联考2018]秘密袭击coat

    $ \color{#0066ff}{ 题目描述 }$ Access Globe 最近正在玩一款战略游戏.在游戏中,他操控的角色是一名C 国士 兵.他的任务就是服从指挥官的指令参加战斗,并在战斗中取胜. ...

  2. luogu P4365 [九省联考2018]秘密袭击coat

    luogu 这里不妨考虑每个点的贡献,即求出每个点在多少个联通块中为第\(k\)大的(这里权值相同的可以按任意顺序排大小),然后答案为所有点权值\(*\)上面求的东西之和 把比这个点大的点看成\(1\ ...

  3. [九省联考2018]秘密袭击coat

    [九省联考2018]秘密袭击coat 研究半天题解啊... 全网几乎唯一的官方做法的题解:链接 别的都是暴力.... 要是n=3333暴力就完了. 一.问题转化 每个联通块第k大的数,直观统计的话,会 ...

  4. 并不对劲的复健训练-bzoj5250:loj2473:p4365:[九省联考2018]秘密袭击

    题目大意 有一棵\(n\)(\(n\leq 1666\))个点的树,有点权\(d_i\),点权最大值为\(w\)(\(w\leq 1666\)).给出\(k\)(\(k\leq n\)),定义一个选择 ...

  5. 解题:九省联考2018 秘密袭击CoaT

    题面 按照*Miracle*的话来说,网上又多了一篇n^3暴力的题解 可能是因为很多猫题虽然很好,但是写正解性价比比较低? 直接做不可做,转化为统计贡献:$O(n)$枚举每个权值,直接统计第k大大于等 ...

  6. [九省联考 2018]秘密袭击coat

    Description 题库链接 给出一棵 \(n\) 个点的树,每个点有点权.求所有联通块的权值 \(k\) 大和,对 \(64123\) 取模. \(1\leq n,k\leq 1666\) So ...

  7. [LOJ #2473] [九省联考2018] 秘密袭击coat

    题目链接 洛谷. LOJ,LOJ机子是真的快 Solution 我直接上暴力了...\(O(n^2k)\)洛谷要\(O2\)才能过...loj平均单点一秒... 直接枚举每个点为第\(k\)大的点,然 ...

  8. 【BZOJ5250】[九省联考2018]秘密袭击(动态规划)

    [BZOJ5250][九省联考2018]秘密袭击(动态规划) 题面 BZOJ 洛谷 给定一棵树,求其所有联通块的权值第\(k\)大的和. 题解 整个\(O(nk(n-k))\)的暴力剪剪枝就给过了.. ...

  9. [BZOJ5250][九省联考2018]秘密袭击(DP)

    5250: [2018多省省队联测]秘密袭击 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 3  Solved: 0[Submit][Status][D ...

  10. LuoguP4365 [九省联考2018]秘密袭击

    https://zybuluo.com/ysner/note/1141136 题面 求一颗大小为\(n\)的树取联通块的所有方案中,第\(k\)个数之和. \(n\leq1,667,k\leq n\) ...

随机推荐

  1. GitHub要求2FA?不慌,有它们帮你

    近日,GitHub宣布,到 2023 年底,所有用户都必须要启用双因素身份验证 (2FA),不能只用密码啦. 正如GitHub的首席安全官Mike Hanley所指出的那样,保护开发者账号是确保软件供 ...

  2. 强化学习从基础到进阶-案例与实践[4.1]:深度Q网络-DQN项目实战CartPole-v0

    强化学习从基础到进阶-案例与实践[4.1]:深度Q网络-DQN项目实战CartPole-v0 1.定义算法 相比于Q learning,DQN本质上是为了适应更为复杂的环境,并且经过不断的改良迭代,到 ...

  3. Linux服务器Crontab定时任务配置

    1.检查linux系统是否有crontab rpm -qa | grep crontab 2.如果未安装进行安装 yum -y install vixie-cron yum -y install cr ...

  4. C++ GDAL提取多时相遥感影像中像素随时间变化的数值数组

      本文介绍基于C++语言GDAL库,批量读取大量栅格遥感影像文件,并生成各像元数值的时间序列数组的方法.   首先,我们来明确一下本文所需实现的需求.现在有一个文件夹,其中包含了很多不同格式的文件, ...

  5. Android Graphics 显示系统 - 如何模拟多(物理)显示屏?

    " 本着花小钱办大事,不花钱也办事的原则,为了避免花钱买设备,那如何更便捷地学习/测试Android多屏显示的内容呢?本文就给大家介绍一种模拟Android多个物理屏幕显示的方法." ...

  6. Java并发(五)----线程常见方法总结

    常见方法 方法名 static 功能说明 注意 start()   启动一个新线程,在新的线程运行 run 方法中的代码 start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还 ...

  7. 树莓派安装freeswitch

    树莓派版本: Raspberry Pi 4B 操作系统 : Ubuntu Server 20.04_x64 freeswitch版本 : 1.10.3 1.下载freeswitch源代码 wget h ...

  8. Go语言的100个错误使用场景(40-47)|字符串&函数&方法

    目录 前言 5. 字符串 5.5 无用的字符串转换(#40) 5.6 获取子字符串操作和内存泄漏(#41) 6. 函数和方法 6.1 不知道选择哪种类型的方法接受者(#42) 6.2 从来不使用命名的 ...

  9. NC14545 经商

    题目链接 题目 题目描述 小d是一个搞房地产的土豪.每个人经商都有每个人经商的手段,当然人际关系是需要放在首位的. 小d每一个月都需要列出来一个人际关系表,表示他们搞房地产的人的一个人际关系网,但是他 ...

  10. 【Unity3D】分离路面导航

    1 前言 ​ 导航系统中介绍了导航的基本用法,但是不能解决分离路面导航问题.如下图,胶囊体需要导航到立方体位置,但是路面不连续,烘焙(Bake)导航网格后,断开处也没有导航网格,因此,胶囊体导航到分离 ...