NFLSOJ #917 -「lych_cys模拟题2018」橘子树(树剖+ODT+莫反统计贡献的思想+动态开点线段树)
sb 出题人不在题面里写 \(b_i=0\) 导致我挂成零蛋/fn/fn
首先考虑树链剖分将路径问题转化为序列上的问题,因此下文中简称“位置 \(i\)”表示 DFS 序为 \(i\) 的点,\(a_i,b_i\) 分别借指 DFS 为 \(i\) 的点的 \(a_i,b_i\)。那么每次操作可以看作将一段区间上的位置清空并加上这段区间上所有位置的贡献。注意到这个清空形式单一,具体来说如果设 \(t_i\) 表示上一次位置 \(i\) 被清空的时刻,那么对于一个时刻 \(T\),\(i\) 节点上橘子个数显然是 \(\min(a_i·(T-t_i),b_i)\)。而清空操作只是改变了一段区间内的点上一次被清空的时间 \(t_i\)。因此我们考虑用类似于珂朵莉树的思想,开一个 set
维护所有 \(t_i\) 相同的连续段,然后每次修改相当于将 \([l,r]\) 中所有连续段从 set
中删除并加入新的连续段,对于删除的每个形如 \((l,r,t)\) 表示 DFS 序区间为 \([l,r]\) 上一次被清空的时间为 \(t\) 的三元组,我们要统计这段区间内所有点的 \(f(\min(a_i·(T-t),b_i))\) 的和,其中 \(T\) 为这一次清空的时间,由于是计算贡献的和,我们考虑将这些询问离线并差分成两段前缀的贡献,也就是用一个三元组 \((x,v,coef)\) 表示统计 \(\sum\limits_{i=1}^xf(\min(a_i·v,b_i))·coef\)。
考虑怎样求解这个东西,我们考虑将询问挂在 \(x\) 处,然后从 \(1\) 到 \(n\) 遍历每个元素并依次累加每个询问的贡献,那么我们就要思考加入一个元素会对询问的答案产生怎样的贡献,以及怎样通过累加求得的贡献回答每个询问。首先这个 \(\min\) 很烦人,因此我们考虑怎样去掉这样的 \(\min\)。注意到当 \(\lfloor\dfrac{a_i}{b_i}\rfloor<v\) 时 \(\min\) 会取到 \(b_i\),否则 \(\min\) 会取到 \(a_i·v\),因此考虑对这两部分分别计算,对于 \(\lfloor\dfrac{a_i}{b_i}\rfloor<v\) 的部分相对来说比较容易:我们建一棵树状数组,下标为 \(\lfloor\dfrac{a_i}{b_i}\rfloor\),每加入一个元素 \(i\) 就在 \(\lfloor\dfrac{a_i}{b_i}\rfloor\) 的位置加上 \(f(b_i)\),那么统计答案相当于统计 \([0,v-1]\) 的前缀和,树状数组解决。值得注意的是此题暴力分解 \(f(b_i)\) 复杂度 \(n\sqrt{b_i}\),无法通过,因此考虑立方分解质因数求解 \(f(b_i)\):即,考虑先用 \([1,\sqrt[3]{b_i}]\) 中的质因子取除 \(b_i\),然后对于 \(>\sqrt[3]{b_i}\) 的部分,显然这样的质因子次数最多为 \(2\),因此我们只用检验剩余部分是否是完全平方数,如果是则答案乘上剩余部分即可。比较费劲的是 \(\lfloor\dfrac{a_i}{b_i}\rfloor\ge v\) 的部分,首先考虑 \(f(xy)\) 怎样独立写成 \(x,y\) 的形式,因为这里的 \(f(a_i·v)\) 如果直接硬着头皮计算则涉及到每个元素对每个询问的贡献,直接做显然复杂度太高。首先 \(f(xy)\) 中肯定有 \(f(x)\) 和 \(f(y)\) 的贡献,但是也有可能会多出来一些贡献。显然多出来的这些贡献就是所有在 \(x\) 中次数为奇数,在 \(y\) 中次数也是奇数的质数 \(p\) 的 \(p^2\) 之积,因此如果设 \(S(x)\) 表示在 \(x\) 中出现次数为奇数的质因子集合,那么有
\]
注意到这里 \(S(x)\cap S(y)\) 又涉及两个位置的贡献,不过看到集合取交我们可以很自然地与莫比乌斯反演的等于转包含的套路联系在一起,具体来说,根据
\]
令 \(S=\{p^2-1|p\in S(x)\cap S(y)\}\) 可得
\]
有了这个式子之后,答案统计就相对来说变得容易许多了。先不考虑 \(\lfloor\dfrac{a_i}{b_i}\rfloor\ge v\) 这个限制,我们每加入一个 \(a_i\),就枚举 \(S'\subseteqq S(a_i)\),然后在 \(S'\) 的位置上加上 \(\prod\limits_{p\in S'}(p^2-1)·f(a_i)\),注意到 \(S'\subseteqq S(a_i)\) 这个包含关系可以视作整除关系,因此我们也可将 \(S'\) 视作一个自然数 \(\prod\limits_{x\in S'}x\),这样就不用 map
等东西维护了,一个数组即可搞定。然后回答询问时就枚举 \(S'\subseteqq S(v)\),然后答案加上 \(S'\) 对应的数的位置上的值 \(\times f(v)\)。显然这个与我们暴力统计贡献的过程是等价的。那么加上 \(\lfloor\dfrac{a_i}{b_i}\rfloor\ge v\) 这个限制怎么办呢?此时简简单单用一个数组维护就不可取了。还是考虑将 \(\lfloor\dfrac{a_i}{b_i}\rfloor\) 作为下标,然后咱们建立 \(3\times 10^5\) 棵动态开点线段树,这样加入一个元素的贡献时,我们还是枚举 \(S'\subseteqq S(a_i)\),然后在 \(S'\) 对应的数的动态开点线段树上下标为 \(\lfloor\dfrac{a_i}{b_i}\rfloor\) 的位置加上 \(\prod\limits_{p\in S'}(p^2-1)·f(a_i)\),这样查询相当于在待查询位置的动态开点线段树上做后缀和 instead of 直接查询待查询位置上的值。
总复杂度 \(m\log^2n·2^{\omega(t)}+n\sqrt[3]{b_i}\),比标算少一个 \(n\sqrt{T}\log T\),并且支持计算每个询问的答案。
所以还是建议把标算卡掉(bushi
const int MAXN=1e5;
const int MAXV=3e5;
const int MAXP=MAXV<<8;
int pr[MAXV/6+5],prcnt=0,vis[MAXV+5],mnp[MAXV+5];
vector<int> fac[MAXV+5];
void sieve(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) pr[++prcnt]=i,mnp[i]=i;
for(int j=1;j<=prcnt&&pr[j]*i<=n;j++){
vis[pr[j]*i]=1;mnp[pr[j]*i]=pr[j];
if(i%pr[j]==0) break;
}
}
for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i) fac[j].pb(i);
}
int f[MAXV+5];
ll calc(ll x,int mx){
if(!x) return 0;
ll res=1;
for(int i=1;i<=prcnt;i++){
if(pr[i]>mx) break;
int cnt=0;
while(x%pr[i]==0){cnt++;x/=pr[i];}
for(int j=1;j<=cnt/2*2;j++) res*=pr[i];
} int lim=(int)sqrt(x+0.05);
if(1ll*lim*lim==x) res*=lim,res*=lim;
return res;
}
int n,m,a[MAXN+5],lim[MAXN+5];ll b[MAXN+5],c[MAXN+5];
link_list<int,MAXN,MAXN*2> g;
int fa[MAXN+5],dep[MAXN+5],siz[MAXN+5],wson[MAXN+5];
int dfn[MAXN+5],top[MAXN+5],tim,rid[MAXN+5];
void dfs1(int x,int f){
fa[x]=f;siz[x]=1;
for(int e=g.hd[x];e;e=g.nxt[e]){
int y=g.val[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs1(y,x);siz[x]+=siz[y];
if(siz[y]>siz[wson[x]]) wson[x]=y;
}
}
void dfs2(int x,int tp){
top[x]=tp;rid[dfn[x]=++tim]=x;
if(wson[x]) dfs2(wson[x],tp);
for(int e=g.hd[x];e;e=g.nxt[e]){
int y=g.val[e];if(y==fa[x]||y==wson[x]) continue;
dfs2(y,y);
}
}
struct itvl{
int l,r,t;
itvl(int _l=0,int _r=0,int _t=0):l(_l),r(_r),t(_t){}
bool operator <(const itvl &rhs) const{
return l<rhs.l;
}
};
set<itvl> st;
vector<pii> qv[MAXN+5];
ll res;
void add_qry(int l,int r,int v){
// printf("add_qry %d %d %d\n",l,r,v);
// for(int i=l;i<=r;i++) res+=calc(min(1ll*a[rid[i]]*v,b[rid[i]]),1259);
qv[r].pb(mp(v,1));
if(l^1) qv[l-1].pb(mp(v,-1));
}
void split(int p){
if(st.empty()) return;
set<itvl>::iterator it=st.upper_bound(itvl(p,0,0));
// printf("split %d %d\n",x,p);
if(it==st.begin()) return;--it;
itvl t=*it;//printf("%d %d\n",t.l,t.r);
if(t.r<=p) return;
st.erase(st.find(t));
st.insert(itvl(t.l,p,t.t));
st.insert(itvl(p+1,t.r,t.t));
}
void pushall(int l,int r,int t){
// printf("pushall %d %d %d\n",l,r,t);
split(l-1);split(r);
vector<itvl> del;
set<itvl>::iterator it=st.lower_bound(itvl(l,0,0));
while(it!=st.end()&&(it->r)<=r){
del.pb(*it);it++;
// printf("{%d,%d,%d}\n",it->l,it->r,it->t);
}
for(int i=0;i<del.size();i++){
st.erase(st.find(del[i]));
add_qry(del[i].l,del[i].r,t-del[i].t);
} st.insert(itvl(l,r,t));
}
void jumpath(int u,int v,int t){
while(top[u]^top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
pushall(dfn[top[u]],dfn[u],t);u=fa[top[u]];
} if(dep[u]<dep[v]) swap(u,v);
pushall(dfn[v],dfn[u],t);
}
namespace fenwick{
ll tr[MAXV+5];
void add(int x,ll v){++x;for(int i=x;i<=MAXV+2;i+=(i&(-i))) tr[i]+=v;}
ll query(int x){++x;ll ret=0;for(int i=x;i;i&=(i-1)) ret+=tr[i];return ret;}
}
int rt[MAXV+5],ncnt=0,ch[MAXP+5][2];ll val[MAXP+5];
void insert(int &k,int l,int r,int p,ll v){
if(!k) k=++ncnt;val[k]+=v;if(l==r) return;
int mid=l+r>>1;
if(p<=mid) insert(ch[k][0],l,mid,p,v);
else insert(ch[k][1],mid+1,r,p,v);
}
ll query(int k,int l,int r,int ql,int qr){
if(!k) return 0;if(ql<=l&&r<=qr) return val[k];
int mid=l+r>>1;
if(qr<=mid) return query(ch[k][0],l,mid,ql,qr);
else if(ql>mid) return query(ch[k][1],mid+1,r,ql,qr);
else return query(ch[k][0],l,mid,ql,mid)+query(ch[k][1],mid+1,r,mid+1,qr);
}
int main(){
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
scanf("%d%d",&n,&m);sieve(MAXV);
for(int i=1;i<=MAXV;i++) f[i]=calc(i,100);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%lld",&b[i]),c[i]=calc(b[i],2000);
// for(int i=1;i<=n;i++) printf("%lld\n",c[i]);
for(int i=1;i<=n;i++) lim[i]=(!a[i])?(MAXV+1):min(b[i]/a[i],1ll*(MAXV+1));
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g.ins(u,v),g.ins(v,u);
dfs1(1,0);dfs2(1,1);st.insert(itvl(1,n,0));
for(int i=1,u,v,t;i<=m;i++){
scanf("%d%d%d",&u,&v,&t);
jumpath(u,v,t);
}
for(int i=1;i<=n;i++){
if(a[rid[i]]){
fenwick::add(lim[rid[i]],c[rid[i]]);
int val=f[a[rid[i]]],rst=a[rid[i]]/val;
vector<int> pr;
while(rst^1){
int p=mnp[rst];rst/=p;
assert(rst%p!=0);pr.pb(p);
}
for(int j=0;j<(1<<pr.size());j++){
ll mul=val,d=1;
for(int k=0;k<pr.size();k++) if(j>>k&1) mul*=(1ll*pr[k]*pr[k]-1),d*=pr[k];
insert(rt[d],0,MAXV+1,lim[rid[i]],mul);
}
}
for(pii p:qv[i]){
int v=p.fi,coef=p.se,ff=f[v];
int r=v/ff;res+=coef*fenwick::query(v-1);
for(int fc:fac[r])
res+=1ll*ff*query(rt[fc],0,MAXV+1,v,MAXV+1)*coef;
}
}
printf("%lld\n",res);
return 0;
}
/*
4 1
2 3 4 27
1000000000 1000000000 1000000000 1000000000
1 2
2 3
3 4
1 4 6
*/
NFLSOJ #917 -「lych_cys模拟题2018」橘子树(树剖+ODT+莫反统计贡献的思想+动态开点线段树)的更多相关文章
- 牛客集训 湖南省赛E题 Grid 动态开点线段树
国庆牛客集训的题,正好准备好好训练线段树,想起来就补一下. 题意很简单,两种操作行合并或者列合并,每个操作后计算有多少个子块. 这题应该先推导公式,行操作或者列操作只有一种的时候,很简单,总数就是n* ...
- 动态开点线段树(陕西师范18k题)---get new skill
思想: 每次开点的时候:左右孩子都开辟新空间 注意懒惰标记tag: 因为会向下传递 提前在值中减去懒惰标记,避免重复计算 链接:https://www.nowcoder.com/acm/ ...
- 【NOIP模拟赛】天神下凡 动态开点线段树
这些圆一定是在同一水平面上的,由于他们没有相交,因此我们发现他们每个人与外界关系可以分为,1.存在并圈圈 2.存在圈圈并被割,因此我们把所有的圆都加1,把被割的在加1,就可以啦,因此我们开一个线段树, ...
- Solution -「树上杂题?」专练
主要是记录思路,不要被刚开始错误方向带偏了 www 「CF1110F」Nearest Leaf 特殊性质:先序遍历即为 \(1 \to n\),可得出:叶子节点编号递增或可在不改变树形态的基础上调整为 ...
- LOJ#121. 「离线可过」动态图连通性(线段树分治)
题意 板子题,题意很清楚吧.. Sol 很显然可以直接上LCT.. 但是这题允许离线,于是就有了一个非常巧妙的离线的做法,好像叫什么线段树分治?? 此题中每条边出现的位置都可以看做是一段区间. 我们用 ...
- 【luogu4145】上帝造题的七分钟2 / 花神游历各国--区间开根-线段树
题目背景 XLk觉得<上帝造题的七分钟>不太过瘾,于是有了第二部. 题目描述 "第一分钟,X说,要有数列,于是便给定了一个正整数数列. 第二分钟,L说,要能修改,于是便有了对一段 ...
- 【模拟】HHHOJ#251. 「NOIP模拟赛 伍」高精度
积累模拟经验 题目描述 维护一个二进制数,支持如下操作 "+" 该数加 11 "-" 该数减 11 "*" 该数乘 22 "\&q ...
- 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题
大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...
- 「10.19」最长不下降子序列(DP)·完全背包问题(spfa优化DP)·最近公共祖先(线段树+DFS序)
我又被虐了... A. 最长不下降子序列 考场打的错解,成功调了两个半小时还是没A, 事实上和正解的思路很近了,只是没有想到直接将前$D$个及后$D$个直接提出来 确实当时思路有些紊乱,打的时候只是将 ...
随机推荐
- Python学习系列之一: python相关环境的搭建
前言 学习python和使用已经一年多了,这段时间抽空整理了一下以前的笔记,方便日后查阅. Python介绍 Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. Pytho ...
- JDK中的SPI机制
前言 最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader, 之前自己不了解这个机制,甚是惭愧... 什么是SPI SPI全称为(Servi ...
- Beta阶段第七次会议
Beta阶段第七次会议 时间:2020.5.23 完成工作 姓名 工作 难度 完成度 ltx 1.修改小程序页面无法加载bug2.修改条件语句,使得页面能够正常显示 中 90% xyq 1.根据api ...
- 使用jave2实现将wav格式的音频转换成mp3格式
最近需要用到语音合成功能,网上查阅了一番,发现可以使用腾讯云的语音合成API来完成这个功能,但是腾讯云的api返回的是wav格式的音频文件,这个格式的文件有些不通用,因此需要转换成mp3格式的文件. ...
- createContext 你用对了吗?
目录 前言 性能问题的根源 问题1(整体重复渲染):Provider组件包裹的子组件全部渲染 问题2(局部重复渲染):使用useContext导致组件渲染 解决方案 解决问题1 解决问题2 参考 前言 ...
- MD5函数(公共方法)
1 #region MD5函数 2 /// <summary> 3 /// MD5函数 4 /// </summary> 5 /// <param name=" ...
- 修改openstack镜像--支持root密码登陆
一.前言 从openstack官方下载的云镜像一般都是普通用户密钥登陆,比如centos镜像的普通用户为centos,ubuntu镜像的普通用户为ubuntu,虽然密钥登陆系统相比密码登陆来说比较方便 ...
- "迷途"的野指针,都快找不着北了
指针,C语言开发者表示很淦,指针的使用,很多人表示不敢直面ta,不像Java一样,有垃圾自动回收功能,我们不用担心那么多内存泄漏等问题,那C语言里边呢,指针又分为了"野指针",&q ...
- Mac sourceTree每次都输入密码
打开终端 依次输入以下三条命令 curl http://github-media-downloads.s3.amazonaws.com/osx/git-credential-osxkeychain - ...
- Redis源码分析(sds)
源码版本:redis-4.0.1 源码位置:https://github.com/antirez/sds 一.SDS简介 sds (Simple Dynamic String),Simple的意思是简 ...