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$个直接提出来 确实当时思路有些紊乱,打的时候只是将 ...
随机推荐
- DL4J实战之六:图形化展示训练过程
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<DL4J实战>系列的第六 ...
- javascript-jquery-文档处理
一.移动元素 1.append():向每个匹配元素的内部追加内容.例如:$("选择器1").qppend("选择器2"):将会匹配选择器2的元素,移动到匹配选择 ...
- 六步教你如何用PADS进行PCB设计?
在使用PADS进行PCB设计的过程中,需要对印制板的设计流程以及相关的注意事项进行重点关注,这样才能更好的为工作组中的设计人员提供系统的设计规范,同时也方便设计人员之间进行相互的交流和检查. 02 设 ...
- repo学习总结
转载:https://blog.csdn.net/salmon_zhang/article/details/79180075 1. repo简介 repo是Google开发的用于管理Android版本 ...
- linux shell 函数返回值问题(超过255)
最近再写一个shell测试的时候出现问题,函数返回值异常 用shell计算斐波那契数列数列,写了一个shell函数,然后调用的,验证的时候我只随便计算了几个数(10以内),确认结果是正确的就提交了,后 ...
- sum-root-to-leaf-numbers leetcode C++
Given a binary tree containing digits from0-9only, each root-to-leaf path could represent a number. ...
- 记录 mysql 使用时遇到的问题
1,linux平台上mysqld和mysql的区别. 首先,mysql数据库是标准的c/s架构,yum安装时注意到了,有mysql和mysql-server包 mysql是客户端的工具,mysqld ...
- 优客源创会 西安站 西邮Linux兴趣小组
2016年5月19日晚7:00,优客源创会西安站在西安邮电大学长安校区东区教学楼FF305如期举行,西安邮电大学计算机学院教授.西邮Linux兴趣小组指导老师陈莉君.王小银老师和来自开源中国的周凯先生 ...
- 17.彻底解决Jmap在mac版本无法使用的问题
彻底解决Jmap在mac版本无法使用的问题 看了网上很多帖子,都说一半,说的都是大家说过的,根本没有解决问题.说jdk8不行,换成jdk9或者jdk11,我都试了,还是不行,最后说是mac的问题.换成 ...
- node获取请求我的客户端的地址
node获取请求我的客户端的地址 const http = require('http'); //创建 Server const server = http.createServer() // 监听r ...