[HEOI2018] 秘密袭击coat
Description
给定一棵 \(n\) 个点的树,每个点有点权 \(d_i\) ,请对于树上所有大于等于 \(k\) 个点的联通块,求出联通块中第 \(k\) 大的点权之和。\(n\le 1666,d_i\leq 1666\)。对 \(64123\) 取模。
Sol
先转化一下题目:
如果权值为 \(i\) 的点在某个联通块中是第 \(k\) 大,那么它对该联通块的贡献就是 \(i\),不妨对于 \(1\sim i\),统计一下有多少联通块的第 \(k\) 大是 \(\ge i\) 的,发现对于每个联通块,设它的第 \(k\) 大为 \(j\),那么这个 \(j\) 被统计了 \(j\) 遍,惊奇的发现这就是它本应有的贡献。这就等价于 求有多少个联通块,使得 \(\ge i\) 的数有 \(\ge k\) 个。
所以问题变成了,对于每个权值 \(i\),求有多少联通块有 \(\ge k\) 个 \(\ge i\) 的数。
可以设计一个\(\text{DP}\),\(f[i][j][k]\) 表示点 \(i\) 为根的子树中,有 \(k\) 个 \(\ge j\) 的包含点 \(i\) 的联通块数。
那么转移就是
f[i][j][k] &= \prod_{v \in son[i]} (f[v][j][k']+1) \ \ \ \ (d[i]<j,\sum k'=k)\\
f[i][j][k] &= \prod_{v \in son[i]} (f[v][j][k']+1) \ \ \ \ (d[i] \geqslant j,\sum k'=k-1)
\end{align}
\]
树上背包可以优化为 \(O(n^3)\)。卡卡常即可通过。
考虑生成函数。
外层先枚举一个权值 \(j\) ,设 \(F_a(x)=\sum\limits_{i=0}^n f[a][j][i]\cdot x^i\) ,这是一个 \(n\) 次多项式。
求答案时可以令 \(G_a(x)\) 表示 \(a\) 子树中所有点的 \(F(x)\) 的和,然后求 \(G_{root}(x)\) 的第 \(k,\dots,n\) 项和。
那么转移就变成了
\]
如果我们给定 \(x_0\),求出 \(F_a(x_0),G_a(x_0)\),那么可以直接 \(O(n)\;\text{DP}\) 。
那我们可以枚举 \(x_0=1\sim n+1\),每次\(\text{DP}\)一下求出点值,最后再用拉格朗日插值求答案不就好了。然而这样并没有跑的更快。
算一下现在的复杂度,外层枚举权值 \(O(n)\),枚举 \(x_0\) \(O(n)\),树形\(\text{DP}\;O(n)\),总复杂度 \(O(n^3)\)。
写一下伪代码:
DP(now,i,x0)
(f,g)=(1,0)
for to in son(now)
(f0,g0)=DP(to,i,x0)
(f,g)=(f*(1+f0),g+g0)
if(d[now]>=i)
(f,g)=(f*x0,g)
(f,g)=(f,g+f)
return (f,g)
可不可以把枚举权值这个复杂度优化一下呢?
有个非常牛逼的科技叫整体\(\text{DP}\)。大概意思就是把许多次\(\text{DP}\)放在一起做。
一般用线段树维护每个询问,线段树的第 \(i\) 个叶子结点存储的值就是第 \(i\) 个询问的答案,在合并的时候使用线段树合并,更新一些\(\text{DP}\)值。
当然如果每个节点的线段树都是一颗满线段树的话复杂度显然不对,所以有个优化:如果线段树上一个节点 \(x\) 的子树中的所有询问的答案都一样,那么只需要保留 \(x\) 这个节点即可。
那回到这道题,就可以把枚举 \(W\) 个权值当做 \(W\) 次询问,然后就能用整体\(\text{DP}\)维护了。
具体来说,现在外层只需要枚举 \(x_0\),那么在树形\(\text{DP}\)到点 \(i\) 的时候,点 \(i\) 的线段树中第 \(j\) 个叶子结点存储的值 \(v_1,v_2\) 的含义就是,当 \(x=x_0\) 时,\(F_i(x)\) 的点值为 \(v_1\),\(G_i(x)\) 的点值为 \(v_2\)。
那我们看一下伪代码中的每个操作都对应着线段树的什么操作:
(f,g)=(1,0)
整体赋值(f,g)=(f*(1+f0),g+g0)
对应项合并if d[a]>=i / (f,g)=(f*x0,g)
给 \(1\sim d[a]\) 项整体打标记(f,g)=(f,g+f)
整体打标记
所以问题就变成了,如何在线段树上维护好标记。
考虑我们需要做什么:
- 维护
(f,g)
- 给
f
整体加 \(1\) - 给
f
乘上f0
- 把
f
加到g
上
因为对应项相乘并不好做,所以我们考虑定义一个类似于矩阵乘法一样的变换,\((a,b,c,d)\) 表示当前节点维护的(f,g)=(a+b*f,g+c+d*f)
为什么这么定义大概是xjb凑出来的?
然后变换的乘法就可以根据定义轻松推出来了懒得写了
单位变换就是 \((0,1,0,0)\),任何变换乘上该变换还为本身。
而每个点的(f,g)
实际上就是维护出来的 \((b,d)\)。\((a,c)\) 的存在大概是维护标记的需要?
于是有了这个就能求出 \(1\sim n+1\) 的点值来了。
最后一步,就是插值,求出原多项式的系数了。
这一步可以多项式快速插值实现,但是太难写,而且复杂度瓶颈不在这里。直接拉格朗日插值就好。
然而怎么求出来每项的系数呢?
拉格朗日的式子长这样:\(\sum\limits_{i=1}^{n+1} y_i\left(\prod_{j\ne i}\frac{(x-x_j)}{(x_i-x_j)} \right)\)
观察到分子之间的差别很小,可以提前背包算出来 \(\prod_j (x-x_j)\),转移是枚举当前选前面的 \(x\) 还是后边的 \(-x_j\),\(f[i]=f[i-1]-f[i]*x_j\),分母可以预处理逆元求出来。
那现在问题就只剩下了,分子多乘了一个 \(x-x_i\),我们要把这个退背包回去。
实际上也很简单,因为 \(f[j]=f'[j-1]-f'[j]*x_i\),我们实际上要求的是 \(f'[j]\),那把 \(j\) 从小到大枚举,然后移项一下就行了。
那求出来每项的系数之后,第 \(k\sim n\) 项的和就是答案了。
Code
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned int ui;
const int N=1670;
const int M=N*200;
const ui mod=64123;
#define ls ch[x][0]
#define rs ch[x][1]
int head[N],tot,dp[M],d[N];
ui ans[N],f[N],in[N],a[N],b[N];
int n,k,W,cnt,dc,rt[N],ch[M][2];
ui inc(ui x,ui y){
return x+y>=mod?x+y-mod:x+y;
}
struct Node{
ui a,b,c,d;
Node(){}
Node(ui aa,ui bb,ui cc,ui dd){a=aa,b=bb,c=cc,d=dd;}
friend Node operator+(Node x,Node y){
return Node(inc(y.a,x.a*y.b%mod),x.b*y.b%mod,inc(x.c+y.c,x.a*y.d%mod),inc(x.d,x.b*y.d%mod));
}
}sum[M];
struct Edge{
int to,nxt;
}edge[N<<1];
void add(int x,int y){
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
head[x]=cnt;
}
int newnode(){
int t=dc?dp[dc--]:++tot;
return sum[t]=Node(0,1,0,0),t;
}
void del(int x){
if(!x) return;
dp[++dc]=x;
del(ls),del(rs);
ls=rs=0;
}
void pushdown(int x){
if(!ls) ls=newnode();
if(!rs) rs=newnode();
sum[ls]=sum[ls]+sum[x];
sum[rs]=sum[rs]+sum[x];
sum[x]=Node(0,1,0,0);
}
void merge(int &x,int &y){
if(!ch[x][0] and !ch[x][1])
swap(x,y);
if(!ch[y][0] and !ch[y][1])
return sum[x]=sum[x]+Node(0,sum[y].a,sum[y].c,0),void();
pushdown(x),pushdown(y);
merge(ch[x][0],ch[y][0]),merge(ch[x][1],ch[y][1]);
}
void modify(int x,int l,int r,int ql,int qr,Node p){
if(ql<=l and r<=qr) return sum[x]=sum[x]+p,void();
int mid=l+r>>1; pushdown(x);
if(ql<=mid) modify(ls,l,mid,ql,qr,p);
if(mid<qr) modify(rs,mid+1,r,ql,qr,p);
}
void dfs(int now,int x0,int fa=0){
rt[now]=newnode();sum[rt[now]]=Node(1,0,0,0);
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(to==fa) continue;
dfs(to,x0,now);
merge(rt[now],rt[to]);
del(rt[to]);
}
modify(rt[now],1,W,1,d[now],Node(0,x0,0,0));
modify(rt[now],1,W,1,W,Node(0,1,0,1));
modify(rt[now],1,W,1,W,Node(1,1,0,0));
}
ui query(int x,int l,int r){
if(l==r) return sum[x].c;
int mid=l+r>>1; pushdown(x);
return (query(ls,l,mid)+query(rs,mid+1,r))%mod;
}
ui Lagrange(){
in[1]=1; f[0]=1;
for(int i=2;i<=n+1;i++)
in[i]=(mod-mod/i)*in[mod%i]%mod;
for(int i=1;i<=n+1;i++)
for(int j=n+1;~j;j--){
if(j) f[j]=inc(f[j-1],f[j]*(mod-i)%mod);
else f[j]=f[j]*(mod-i)%mod;
}
for(int i=1;i<=n+1;i++){
ui res=ans[i]; memcpy(b,f,sizeof 4*(n+2));
for(int j=1;j<=n+1;j++){
if(i==j) continue;
if(i>j) res=res*in[i-j]%mod;
else res=res*(mod-in[j-i])%mod;
}
for(int j=0;j<=n+1;j++){
if(!j) b[j]=mod-b[j]*in[i]%mod;
else b[j]=inc(mod,b[j-1]-b[j])*in[i]%mod;
}
for(int j=0;j<=n;j++)
a[j]=inc(a[j],b[j]*res%mod);
} ui ans=0;
for(int i=k;i<=n;i++) ans=inc(ans,a[i]);
return ans;
}
signed main(){
scanf("%d%d%d",&n,&k,&W);
for(int i=1;i<=n;i++)
scanf("%d",&d[i]);
for(int x,y,i=1;i<n;i++)
scanf("%d%d",&x,&y),add(x,y),add(y,x);
for(int i=1;i<=n+1;i++){
dfs(1,i);
ans[i]=query(rt[1],1,W);
del(rt[1]);
} printf("%u\n",Lagrange()); return 0;
}
[HEOI2018] 秘密袭击coat的更多相关文章
- [九省联考2018]秘密袭击coat
[九省联考2018]秘密袭击coat 研究半天题解啊... 全网几乎唯一的官方做法的题解:链接 别的都是暴力.... 要是n=3333暴力就完了. 一.问题转化 每个联通块第k大的数,直观统计的话,会 ...
- P4365 [九省联考2018]秘密袭击coat
$ \color{#0066ff}{ 题目描述 }$ Access Globe 最近正在玩一款战略游戏.在游戏中,他操控的角色是一名C 国士 兵.他的任务就是服从指挥官的指令参加战斗,并在战斗中取胜. ...
- [luogu]P4365[九省联考]秘密袭击coat(非官方正解)
题目背景 警告:滥用本题评测者将被封号 We could have had it all. . . . . . 我们本该,拥有一切 Counting on a tree. . . . . . 何至于此 ...
- 解题:九省联考2018 秘密袭击CoaT
题面 按照*Miracle*的话来说,网上又多了一篇n^3暴力的题解 可能是因为很多猫题虽然很好,但是写正解性价比比较低? 直接做不可做,转化为统计贡献:$O(n)$枚举每个权值,直接统计第k大大于等 ...
- [九省联考 2018]秘密袭击coat
Description 题库链接 给出一棵 \(n\) 个点的树,每个点有点权.求所有联通块的权值 \(k\) 大和,对 \(64123\) 取模. \(1\leq n,k\leq 1666\) So ...
- [LOJ #2473] [九省联考2018] 秘密袭击coat
题目链接 洛谷. LOJ,LOJ机子是真的快 Solution 我直接上暴力了...\(O(n^2k)\)洛谷要\(O2\)才能过...loj平均单点一秒... 直接枚举每个点为第\(k\)大的点,然 ...
- luogu P4365 [九省联考2018]秘密袭击coat
luogu 这里不妨考虑每个点的贡献,即求出每个点在多少个联通块中为第\(k\)大的(这里权值相同的可以按任意顺序排大小),然后答案为所有点权值\(*\)上面求的东西之和 把比这个点大的点看成\(1\ ...
- [BZOJ5250][九省联考2018]秘密袭击(DP)
5250: [2018多省省队联测]秘密袭击 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 3 Solved: 0[Submit][Status][D ...
- 【BZOJ5250】[九省联考2018]秘密袭击(动态规划)
[BZOJ5250][九省联考2018]秘密袭击(动态规划) 题面 BZOJ 洛谷 给定一棵树,求其所有联通块的权值第\(k\)大的和. 题解 整个\(O(nk(n-k))\)的暴力剪剪枝就给过了.. ...
随机推荐
- js活jQuery实现动态添加、移除css/js文件
下面是在项目中用到的,直接封装好的函数,拿去在js中直接调用就可以实现css.js文件的动态引入与删除.代码如下 动态加载,移除,替换css/js文件 // 动态添加css文件 function ad ...
- centos7安装kubeadm
安装配置docker v1.9.0版本推荐使用docker v1.12, v1.11, v1.13, 17.03也可以使用,再高版本的docker可能无法正常使用. 测试发现17.09无法正常使用,不 ...
- 在浏览器输入URL时发生了什么
浏览器器检查cache,如果请求对象已经缓存并且是最新的,执行第9步. 浏览器询问操作系统,请求服务器的IP地址 操作系统进行DNS查找,然后告诉浏览器服务器的IP 浏览器和服务器简历一个TCP连接( ...
- 数组的初始化&缩窄转换
1.初始化: 初始化就是在声明变量的同时给变量赋值,而不是声明后再赋值. 先声明,后赋值: int a; //先声明,由于没有初始化,所以当前a的值是变量a创建前,相应的内存单元中保留的值,是未知的 ...
- java简单的双色球摇号程序
import java.util.HashSet; import java.util.Random; import java.util.Set; /** * LotteryClient * @auth ...
- 神经网络_线性神经网络 1 (Nerual Network_Linear Nerual Network 1)
2019-04-08 16:59:23 1 学习规则(Learning Rule) 1.1 赫布学习规则(Hebb Learning Rule) 1949年,Hebb提出了关于神经网络学习机理的“突触 ...
- changXY
changXY <!DOCTYPE html> <html> <head> <link rel="shortcut icon" href= ...
- turtle库的学习
Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动, ...
- json格式 (JavaScipt Object Notation)
json格式 json语法规则: 01.对象表现形式 key:value 键值对 02.如果有多个数据,之间使用逗号隔开 k1:v1,k2:v2 03.把对象写在大括号中 var student={a ...
- 最快效率求出乱序数组中第k小的数
题目:以尽量高的效率求出一个乱序数组中按数值顺序的第k 的元素值 思路:这里很容易想到直接排序然后顺序查找,可以使用效率较高的快排,但是它的时间复杂度是O(nlgn),我们这里可以用一种简便的方法,不 ...