高一上八月上旬日记
8.1
闲话
- 上午 \(7:30 \sim 11:30\) @Delov | @joke3579 学长安排了一场模拟赛。
- 午休 \(miaomiao\) 查宿。
- 下午分瓜。
- 晚休 \(miaomiao\) 查宿时进我们宿舍问这十几套模拟赛打完了感觉怎么样,我们称遇到以前没见过的题、套路就会挂得很惨, \(miaomiao\) 说这是正常现象, \(miaomiao\) 说今年 \(NOI\) 出了个 \(pretest\) ,基本和最后得分就没什么区别了,很考验选手心态,估计以后就 \(CSP\) 和 \(NOIP\) 是 \(OI\) 赛制了,然后问我们今天 \(T4\) 是改不动了吗,我们说题太过逆天导致读懂题就不错了,题解一点都看不懂。
做题纪要
luogu P4170 [CQOI2007] 涂色
-
多倍经验: CF1132F Clear the String
-
从起始到目标染色等价与从目标向起始染色。
-
设 \(f_{l,r}\) 表示将 \([l,r]\) 染色的最小步数,状态转移方程为 \(f_{l,r}=\begin{cases} \min(f_{l,r-1},f_{l+1,r}) & s_{l}=s_{r} \\ \min\limits_{k=l}^{r-1} \{ f_{l,k}+f_{k+1,r} \} & s_{l} \ne s_{r} \end{cases}\) ,边界为 \(f_{i,i}=1\) 。
点击查看代码
ll f[60][60]; char s[60]; int main() { ll n,len,l,r,i; cin>>(s+1); n=strlen(s+1); memset(f,0x3f,sizeof(f)); for(i=1;i<=n;i++) { f[i][i]=1; } for(len=2;len<=n;len++) { for(l=1,r=l+len-1;r<=n;l++,r++) { if(s[l]==s[r]) { f[l][r]=min(f[l][r-1],f[l+1][r]); } else { for(i=l;i<=r-1;i++) { f[l][r]=min(f[l][r],f[l][i]+f[i+1][r]); } } } } cout<<f[1][n]<<endl; return 0; }
luogu P4587 [FJOI2016] 神秘数
-
将 \(a_{l \sim r}\) 升序排序,然后顺序插入多重集。
-
若当前 \([1,x]\) 均能被表示,将要插入 \(a_{k}\) 。若 \(a_{k} \le x+1\) 则下一步 \([1,x+a_{k}]\) 均能被表示,神秘数变为 \(x+a_{k}+1\) ;否则 \(x+1\) 无法被表示,神秘数仍为 \(x+1\) 。
- \(x\) 的上界实际上是一段 \(\{ a \}\) 相加。
-
考虑询问时中模拟插入过程,枚举 \(x+1\) ,初始时 \(x+1=1\) 。若 \(\le x+1\) 的数之和 \(<x+1\) ,此时 \(x+1\) 即为所求;否则,令 \(x+1\) 等于 \(\le x+1\) 的数之和加一。
点击查看代码
ll a[100010]; struct PDS_SMT { ll root[100010],rt_sum=0; struct SegmentTree { ll ls,rs,sum; }tree[100010<<6]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs ll build_rt() { rt_sum++; return rt_sum; } void update(ll pre,ll &rt,ll l,ll r,ll pos,ll val) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum+=val; if(l==r) { return; } ll mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos,val); } else { update(rson(pre),rson(rt),mid+1,r,pos,val); } } ll query(ll rt1,ll rt2,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt2].sum-tree[rt1].sum; } ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt1),lson(rt2),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y); } return ans; } }T; int main() { ll n,m,l,r,ans,sum,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; T.update(T.root[i-1],T.root[i],1,1000000000,a[i],a[i]); } cin>>m; for(i=1;i<=m;i++) { cin>>l>>r; for(ans=1;;ans=sum+1) { sum=T.query(T.root[l-1],T.root[r],1,1000000000,1,ans); if(sum<ans) { break; } } cout<<ans<<endl; } return 0; }
[ABC278F] Shiritori
LibreOJ 6669.Nauuo and Binary Tree
SP1043 Can you answer these queries I
-
猫树板子。
- 猫树是一种不支持修改,仅支持快速区间询问的一种静态线段树。要求询问信息支持结合率和快速合并。
- 构造一棵这样的静态线段树需要 \(O(n \log n )\) 次合并操作,而询问仅需 \(O(1)\) 次合并操作。
- 建树时对于线段树上的一个节点,设其代表的区间为 \([l,r]\) 。除保存 \([l,r]\) 的和外,我们还需要额外保存 \([l,mid]\) 的后缀答案及 \([mid+1,r]\) 的前缀答案。
- 询问时若 \(l=r\) 则直接返回答案。否则将代表 \([l,l]\) 和代表 \([r,r]\) 的节点的 \(\operatorname{LCA}\) 求出来,记为 \(p\) 。容易发现 \([l,r]\) 一定在 \(p\) 所包含的区间 \([L,R]\) 之内,且跨越了 \(p\) 的中点 \(mid\) ,用 \([l,mid]\) 的后缀答案和 \([mid+1,r]\) 的前缀答案合并即可。
- 问题在于如何迅速找到 \(p\) 。考虑进行堆式建树法,令 \(n\) 是一个二的整数次幂,,此时对于两个深度相同的点 \(x,y\) 有 \(\operatorname{LCA}(x,y)=\operatorname{LCP}(x,y)=\)
x>>log[x^y]
。- 对于两个深度相同的点,第一次分开的位置必定导致此刻最后一位二进制不同,而前面都是相同的。
- 本质上是将分治进行离线,数据结构来存储分治结构及先前按位置分治的答案。关键在于寻找特定问题的分治方案。
-
记录从 \(mid\) 出发和从 \(mid\) 结尾的区间最大子段和(可不选 \(mid\) ),再记录一个强制选 \(mid\) 的区间最大子段和。
点击查看代码
int a[500010]; struct Mao_Tree { int pos[500010],sum1[25][500010],sum2[25][500010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void build(int rt,int l,int r,int dep) { if(l==r) { pos[l]=rt; return; } int mid=(l+r)/2,num,maxx; sum2[dep][mid]=sum1[dep][mid]=num=maxx=a[mid]; maxx=max(maxx,0); for(int i=mid-1;i>=l;i--) { num+=a[i]; maxx+=a[i]; sum1[dep][i]=max(sum1[dep][i+1],maxx); sum2[dep][i]=max(sum2[dep][i+1],num); maxx=max(maxx,0); } sum2[dep][mid+1]=sum1[dep][mid+1]=num=maxx=a[mid+1]; maxx=max(maxx,0); for(int i=mid+2;i<=r;i++) { num+=a[i]; maxx+=a[i]; sum1[dep][i]=max(sum1[dep][i-1],maxx); sum2[dep][i]=max(sum2[dep][i-1],num); maxx=max(maxx,0); } build(lson(rt),l,mid,dep+1); build(rson(rt),mid+1,r,dep+1); } int query(int l,int r) { if(l==r) { return a[l]; } else { int d=(int)log2(pos[l])-(int)log2(pos[l]^pos[r]);//找到是第几层 return max(max(sum1[d][l],sum1[d][r]),sum2[d][l]+sum2[d][r]); } } }T; int main() { int n,nn=2,m,l,r,i; cin>>n; while(nn<n)//二的整数次幂 { nn*=2; } for(i=1;i<=n;i++) { cin>>a[i]; } T.build(1,1,nn,1); cin>>m; for(i=1;i<=m;i++) { cin>>l>>r; cout<<T.query(l,r)<<endl; } return 0; }
luogu P6240 好吃的题目
luogu P3302 [SDOI2013] 森林
-
连边时暴力重建主席树,并更新 \(fa\) 数组。
- \(fa\) 数组一定要更新彻底。
-
合并时启发式合并维护即可。
-
查询第 \(k\) 小的部分就和 luogu P2633 Count on a tree 。
点击查看代码
struct PDS_SMT { int root[160010],rt_sum=0; struct SegmentTree { int ls,rs,cnt; }tree[160010<<6]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs int build_rt() { rt_sum++; return rt_sum; } void build_tree(int &rt,int l,int r) { rt=build_rt(); if(l==r) { return; } int mid=(l+r)/2; build_tree(lson(rt),l,mid); build_tree(rson(rt),mid+1,r); } void update(int pre,int &rt,int l,int r,int pos) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].cnt++; if(l==r) { return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos); } else { update(rson(pre),rson(rt),mid+1,r,pos); } } int query(int rt1,int rt2,int rt3,int rt4,int l,int r,int k) { if(l==r) { return l; } int mid=(l+r)/2,sum=tree[lson(rt1)].cnt+tree[lson(rt2)].cnt-tree[lson(rt3)].cnt-tree[lson(rt4)].cnt; if(k<=sum) { return query(lson(rt1),lson(rt2),lson(rt3),lson(rt4),l,mid,k); } else { return query(rson(rt1),rson(rt2),rson(rt3),rson(rt4),mid+1,r,k-sum); } } }T; struct DSU { int fa[160010]; int find(int x) { return (fa[x]==x)?x:fa[x]=find(fa[x]); } }D; struct node { int nxt,to; }e[160010]; int head[160010],dep[160010],siz[160010],a[160010],b[160010],vis[160010],fa[160010][25],cnt=0,N; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int father,int rt) { vis[x]=1; siz[rt]++; dep[x]=dep[father]+1; D.fa[x]=fa[x][0]=father; T.update(T.root[father],T.root[x],1,b[0],lower_bound(b+1,b+1+b[0],a[x])-b); for(int i=1;i<=N;i++)//更新到 N { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs(e[i].to,x,rt); } } } int lca(int x,int y) { if(dep[x]>dep[y]) { swap(x,y); } for(int i=N;i>=0;i--) { if(dep[x]+(1<<i)<=dep[y]) { y=fa[y][i]; } } if(x==y) { return x; } else { for(int i=N;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } return fa[x][0]; } } void dsu_merge(int x,int y) { int u=D.find(x),v=D.find(y); if(siz[u]>siz[v])//启发式合并维护即可 { swap(u,v); swap(x,y); } dfs(x,y,v); } int main() { int testcase,n,m,t,u,v,k,rt,ans=0,i; char pd; cin>>testcase>>n>>m>>t; N=log2(n)+1; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]; D.fa[i]=i; } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); T.build_tree(T.root[0],1,b[0]); for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } for(i=1;i<=n;i++) { if(vis[i]==0) { dfs(i,0,i); D.fa[i]=i; } } for(i=1;i<=t;i++) { cin>>pd>>u>>v; u^=ans; v^=ans; if(pd=='Q') { cin>>k; k^=ans; rt=lca(u,v); ans=b[T.query(T.root[u],T.root[v],T.root[rt],T.root[fa[rt][0]],1,b[0],k)]; cout<<ans<<endl; } else { add(u,v); add(v,u); dsu_merge(u,v); } } return 0; }
8.2
闲话
- 早上让 \(6:40\) 去吃饭,吃饭回来把电脑重启一下。
- 上午 \(7:15 \sim 12:10\) 打欢乐赛。
- 下午 @LYinMX 学长讲了网络流和决策单调性、平行四边形不等式优化 \(DP\) ,网络流是和多校一起讲的,说后面大部分就是他带我们了;听 @Delov 学长讲上午的题。学会了怎么杀掉极域的强制锁屏,貌似还杀干净了,运行文件都没了。
- 临吃晚饭时, \(huge\) 跟我们说改完上午的题后就趁着下午刚讲了网络流赶紧打下网络流,顺便预习下明天字符串的课件。
- 晚上临下课时 \(field\) 突然进来和外面说刚才副校长来机房外转悠,称让我们讨论声音小点,还有明天早上查迟到会查得很严,千万别迟到。
- 回到宿舍后发现物奥的回来了,就住在楼下。
做题纪要
[AGC005B] Minimum Sum
- 详见 欢欢乐乐赛赛 H P207. 烙印融合 。
P194. 可持久化非确定性有穷状态决策自动机
luogu P10678 『STA - R6』月
- 详见 欢欢乐乐赛赛 A P184. 树构造 。
T179. persona
8.3
闲话
- 早上没有人迟到。
- 吃完早饭后, \(huge\) 突然黑着脸站在门口,生气地质问“新高一的昨天谁参与联欢了,赶紧出来”,第一次没听清遂故作镇定,第二次听清后乖乖出来挨 \(D\) 。原因是昨晚玩狼人杀声音太大被楼下宿管(学生家长)举报了,一开始说让我们集体去给家长道歉,后来决定站一上午。期间还给我们“合影”留下证据,声称要发到家长群,回到家后发现没有发。
- 然后就站着去听 @Delov 给多校讲字符串基础,以为好好表现 \(huge\) 就会让我们坐着,但 @Charlie_ljk 去厕所时发现 \(huge\) 在看 @joke3579 学长
颓,遂打消了这个想法,乖乖罚坐。因为 @wkh2008 昨天上课时氵贴吧看南梁被 @Delov 抓了,所以只能上学校 \(OJ\) 。 - 讲完课后看隔壁机房人没继续站,加上 \(miaomiao\) 也来了,遂坐了会儿。然后 \(huge\) 就进来了说让我们接着站着, \(miaomiao\) 没说啥。
- \(11:30\) 让回宿舍收拾行李,因为收拾行李比较迅速遂 \(11:45\) 就出校了。
做题纪要
[ARC067E] Grouping
T711. 随
- 详见 欢欢乐乐赛赛 L T711. 随 。
luogu P10835 『FLA - I』冲云霄
-
当 \(m\) 为奇数时,令 \(a_{1 \sim m}=n\) 即可;否则 \(\bigoplus\limits_{i=1}^{m}a_{i}=n\) 。
点击查看代码
int main() { ll t,n,m,i; cin>>t; for(i=1;i<=t;i++) { cin>>n>>m; if(m%2==1) { if(n>=1) { cout<<"Yes"<<endl; } else { cout<<"No"<<endl; } } else { if(n>=1) { cout<<"No"<<endl; } else { cout<<"Yes"<<endl; } } } return 0; }
SP26017 GCDMAT - GCD OF MATRIX
-
做完二维差分后等价于查询 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\gcd(i,j)\) ,现筛出 \(\varphi\) 的前缀和后整除分块维护。
点击查看代码
const ll p=1000000007; ll prime[50010],vis[50010],phi[50010],sum[50010],len=0; void isprime(ll n) { memset(vis,0,sizeof(vis)); phi[1]=1; for(ll i=2;i<=n;i++) { if(vis[i]==0) { len++; prime[len]=i; phi[i]=i-1; } for(ll j=1;j<=len&&i*prime[j]<=n;j++) { vis[i*prime[j]]=1; if(i%prime[j]==0) { phi[i*prime[j]]=phi[i]*prime[j]; break; } else { phi[i*prime[j]]=phi[i]*(prime[j]-1); } } } for(ll i=1;i<=n;i++) { sum[i]=sum[i-1]+phi[i]; } } ll ask(ll n,ll m) { if(n>m) { swap(n,m); } ll ans=0,l,r; for(l=1,r;l<=n;l=r+1) { r=min(n/(n/l),m/(m/l)); ans=(ans+((n/l)*(m/l)%p)*(sum[r]-sum[l-1])%p)%p; } return ans; } int main() { ll t,n,m,a,b,c,d,i; cin>>t>>n>>m; isprime(50000); for(i=1;i<=t;i++) { cin>>a>>c>>b>>d; cout<<(ask(b,d)-ask(a-1,d)-ask(b,c-1)+ask(a-1,c-1)+2*p)%p<<endl; } return 0; }
8.4
闲话
- 一边听牛客的课,一边打 PVZ 。
做题纪要
luogu P8796 [蓝桥杯 2022 国 AC] 替换字符
-
发现值域很小,线段树每个节点开 \(26\) 个 \(lazy\) ,分别表示这个字符现在变成了什么。
-
注意下
pushdown
的写法。点击查看代码
char s[100010]; int val(char x) { return x-'a'+1; } struct SMT { struct SegmentTree { int l,r,val,lazy[27]; }tree[400010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; for(int i=1;i<=26;i++) { tree[rt].lazy[i]=i; } if(l==r) { tree[rt].val=val(s[l]); return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushdown(int rt) { for(int i=1;i<=26;i++) { tree[lson(rt)].lazy[i]=tree[rt].lazy[tree[lson(rt)].lazy[i]]; } for(int i=1;i<=26;i++) { tree[rson(rt)].lazy[i]=tree[rt].lazy[tree[rson(rt)].lazy[i]]; } for(int i=1;i<=26;i++) { tree[rt].lazy[i]=i; } } void update(int rt,int x,int y,int val1,int val2) { if(x<=tree[rt].l&&tree[rt].r<=y) { for(int i=1;i<=26;i++) { tree[rt].lazy[i]=(tree[rt].lazy[i]==val1)?val2:tree[rt].lazy[i]; } return; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val1,val2); } if(y>mid) { update(rson(rt),x,y,val1,val2); } } void print(int rt) { if(tree[rt].l==tree[rt].r) { cout<<(char)(tree[rt].lazy[tree[rt].val]+'a'-1); return; } pushdown(rt); print(lson(rt)); print(rson(rt)); } }T; int main() { int n,m,l,r,i; char x,y; cin>>(s+1)>>m; n=strlen(s+1); T.build(1,1,n); for(i=1;i<=m;i++) { cin>>l>>r>>x>>y; T.update(1,l,r,val(x),val(y)); } T.print(1); return 0; }
luogu P10839 【MX-J2-T0】Turtle and Equations
-
分支结构。
点击查看代码
int main() { ll a,b,c,d; cin>>a>>b>>c>>d; if((a+b)+c==d||(a+b)-c==d||(a+b)*c==d||(a-b)+c==d||(a-b)-c==d||(a-b)*c==d||(a*b)+c==d||(a*b)-c==d||(a*b)*c==d) { cout<<"Yes"<<endl; } else { cout<<"No"<<endl; } return 0; }
8.5
闲话
- 下午要求 \(17:00 \sim 18:00\) 进校,但实际上是不到 \(17:00\) 校门外就挤满人了,门卫要求教练领进去。登了会儿, \(huge\) 和 \(feifei\) 就把我们领进去了。
- 回宿舍后发现因为装暖气管的缘故,电梯能用了。收拾完行李,混了个 @Charlie_ljk 的苞米吃,把 @lty_ylzsx 先前给我的鸭掌(给我时声称是鸡爪)吃了。
- 来机房后发现头顶空调冷凝管又露出来了,正对着我的桌面滴水。
- 吃晚饭时看 @wkh2008 给 @jijidawang 拍了张高清写真。
- 回到机房白嫖了 @jijidawang 的一个勋章,有点小。
- 晚上 \(huge\) 跟我们说要调整作息时间表,经他和奥赛主任交涉,早上去 \(whk\) 教室上早读背古文或读蓝书不太现实,直至 \(8.20\) 左右早上均安排小体活,如有迟到停半天奥赛课;宿舍内务保持好,机房卫生由上次开联欢的人进行“劳动改造”来维护;楼下估计还有学生家长,夜聊时间别太长,声音别太大,再被提醒但不听或通报直接开回家;他跟学长说了要给我们多做点特别恶心和注重思维的题目,尽量别出现注重思维的不会打(想不到),思维比较简单的也不会打(码力不行)。但 \(huge\) 觉得我们 \(10 \min\) 能吃完早饭是真的难崩,虽然初三下半学期 \(6:45\) 吃饭,等我 \(7:03\) 左右回到班里时基本人已经来全了吧。
做题纪要
AT_abl_e Replace Digits
-
线段树板子。
点击查看代码
const ll p=998244353; ll mi[200010],num[12][200010]; struct SMT { struct SegmentTree { ll l,r,len,sum,lazy; }tree[800010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].len=tree[lson(rt)].len+tree[rson(rt)].len; tree[rt].sum=(tree[lson(rt)].sum*mi[tree[rson(rt)].len]+tree[rson(rt)].sum)%p; } void build(ll rt,ll l,ll r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=0; if(l==r) { tree[rt].len=tree[rt].sum=1; return; } ll mid=(tree[rt].l+tree[rt].r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(ll rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].lazy=tree[rt].lazy; tree[lson(rt)].sum=num[tree[rt].lazy][tree[lson(rt)].len]; tree[rson(rt)].lazy=tree[rt].lazy; tree[rson(rt)].sum=num[tree[rt].lazy][tree[rson(rt)].len]; tree[rt].lazy=0; } } void update(ll rt,ll x,ll y,ll val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lazy=val; tree[rt].sum=num[val][tree[rt].len]; return; } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } pair<ll,ll> query(ll rt,ll x,ll y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return make_pair(tree[rt].sum,tree[rt].len); } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2; pair<ll,ll>lp=make_pair(0,0),rq=make_pair(0,0); if(x<=mid) { lp=query(lson(rt),x,y); } if(y>mid) { rq=query(rson(rt),x,y); } return make_pair((lp.first*mi[rq.second]%p+rq.first)%p,lp.second+rq.second); } }T; int main() { ll n,q,l,r,x,i,j; cin>>n>>q; mi[0]=1; for(i=1;i<=n;i++) { mi[i]=mi[i-1]*10%p; for(j=1;j<=9;j++) { num[j][i]=(num[j][i-1]*10+j)%p; } } T.build(1,1,n); for(i=1;i<=q;i++) { cin>>l>>r>>x; T.update(1,l,r,x); cout<<T.query(1,1,n).first<<endl; } return 0; }
CF1891F A Growing Tree
-
观察到在所有操作完成后才有一次全局查询,且一个点只有被加进来后修改才会对它有影响,考虑将所有操作离线下来,然后倒序处理。遇到操作 \(1\) 直接记录答案,否则按 \(DFS\) 序进行区间修改。
-
维护一个区间修改、单点查询的树状数组即可。
-
手动清空来保证复杂度正确。
点击查看代码
vector<ll>e[1000010]; ll dfn[1000010],out[1000010],ans[1000010],tot; struct ask { ll pd,x,v; }q[1000010]; struct BIT { ll c[1000010]; ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; void add(ll u,ll v) { e[u].push_back(v); } void dfs(ll x) { tot++; dfn[x]=tot; for(ll i=0;i<e[x].size();i++) { dfs(e[x][i]); } out[x]=tot; } void update(ll l,ll r,ll val,ll n) { T.add(n,l,val); T.add(n,r+1,-val); } int main() { ll t,n,m,i,j; cin>>t; for(j=1;j<=t;j++) { n=1; tot=0; cin>>m; for(i=1;i<=m;i++) { cin>>q[i].pd>>q[i].x; if(q[i].pd==1) { n++; q[i].v=n; add(q[i].x,q[i].v); } else { cin>>q[i].v; } } dfs(1); for(i=m;i>=1;i--) { if(q[i].pd==1) { ans[q[i].v]=T.getsum(dfn[q[i].v]); } else { update(dfn[q[i].x],out[q[i].x],q[i].v,n); } } ans[1]=T.getsum(dfn[1]); for(i=1;i<=n;i++) { cout<<ans[i]<<" "; e[i].clear(); T.c[i]=0; } cout<<endl; } return 0; }
8.6
闲话
- \(6:38\) 到食堂时发现已经开门了。
- 上午 \(7:30 \sim 11:30\) @H_Kaguya | @LYinMX 学长安排了一场模拟赛,和 \(GXYZ,WFLS\) 的一起打。
- 下午组织和 \(GXYZ,WFLS\) 线上讲题,看到了 \(bobo\) 的“怎么说呢”和 \(NOI\) 头像。讲题时 @LYinMX 学长深感我们的基础有多差,该学的知识点都没怎么学,套路见得也少。
- 机房前 \(rk3\) 发了北冰洋。
- 头顶还在漏水,拿机房前人放夹子的盒子承着,因为有多个点下落水所以北冰洋罐子显得有点鸡肋。
- 晚上 \(feifei\) 给我们发了明天下午 \(WFLS\) 要讲的题,让提前想下做法,题目有点抽象。
做题纪要
牛客 NC276921 竹鼠饲养物语
-
饲料等级 \(\le n\) 才有用,统计出现次数后维护连续段。
点击查看代码
ll cnt[100010]; int main() { ll n,m,x,ans=0,sum,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>x; if(x<=n) { cnt[x]++; } } sum=n; for(i=1;i<=n;i++) { ans+=min(sum,cnt[i]); sum=min(sum,cnt[i]); } cout<<ans<<endl; return 0; }
牛客 NC276622 清楚姐姐的布告规划
-
背包 \(DP\) ,限制条件为区间长度。
点击查看代码
int a[5010],f[2][5010]; int main() { int t,n,i,j,k; cin>>t; for(k=1;k<=t;k++) { cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } memset(f,0x3f,sizeof(f)); f[0][0]=0; for(i=1;i<=n;i++) { for(j=0;j<=n;j++) { f[i&1][j]=f[(i-1)&1][j]; if(j-a[i]>=0&&j-a[i]+1<=i&&i<=j) { f[i&1][j]=min(f[i&1][j],f[(i-1)&1][j-a[i]]+1); } } } cout<<((f[n&1][n]==0x3f3f3f3f)?-1:f[n&1][n])<<endl; } return 0; }
P209.BA
luogu P9133 [THUPC 2023 初赛] 大富翁
luogu P3722 [AH2017/HNOI2017] 影魔
-
贡献的产生有点抽象,翻译一下。
- 对于一个区间 \([l,r]\) ,若 \(l,r\) 各对应为 \([l,r]\) 的最大值和次大值则产生 \(p_{1}\) 的贡献,若恰好有一个是最大值则产生 \(p_{2}\) 的贡献。
-
单调栈预处理出第 \(i\) 个数左右各比它大的第一个数,分别记为 \(l_{i},r_{i}\) 。
-
第 \(i\) 个数会对 \([i,i+1],[l_{i},r_{i}]\) 产生 \(p_{1}\) 的贡献,对 \([k,r_{i}](k \in [l_{i}+1,i-1])\) 和 \([l_{i},k](k \in [i+1,r_{i}-1])\) 产生 \(p_{2}\) 的贡献。
-
扫描线加主席树维护即可。由于需要区间修改需要标记永久化,不需要
pushup
和pushdown
。点击查看代码
struct quality { ll l,r,w; }; vector<quality>e[200010]; ll a[200010],l[200010],r[200010]; stack<ll>s; void add(ll u,ll l,ll r,ll w) { e[u].push_back((quality){l,r,w}); } struct PDS_SMT { ll root[200010],rt_sum; struct SegmentTree { ll ls,rs,sum,lazy; }tree[200010<<6]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs ll build_rt() { rt_sum++; return rt_sum; } void build_tree(ll &rt,ll l,ll r) { rt=build_rt(); if(l==r) { return; } ll mid=(l+r)/2; build_tree(lson(rt),l,mid); build_tree(rson(rt),mid+1,r); } void update(ll pre,ll &rt,ll l,ll r,ll x,ll y,ll val) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum+=val*(min(r,y)-max(l,x)+1); if(x<=l&&r<=y) { tree[rt].lazy+=val; return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(pre),lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(pre),rson(rt),mid+1,r,x,y,val); } } ll query(ll rt1,ll rt2,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt2].sum-tree[rt1].sum; } ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt1),lson(rt2),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y); } return ans+(tree[rt2].lazy-tree[rt1].lazy)*(min(r,y)-max(l,x)+1); } }T; int main() { ll n,m,p1,p2,x,y,i,j; cin>>n>>m>>p1>>p2; for(i=1;i<=n;i++) { cin>>a[i]; while(s.empty()==0&&a[s.top()]<=a[i]) { s.pop(); } l[i]=(s.empty()==0)?s.top():0; s.push(i); } while(s.empty()==0) { s.pop(); } for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]<=a[i]) { s.pop(); } r[i]=(s.empty()==0)?s.top():n+1; s.push(i); } for(i=1;i<=n;i++) { if(i!=n) { add(i,i+1,i+1,p1); } if(1<=l[i]&&r[i]<=n) { add(l[i],r[i],r[i],p1); } if(l[i]+1<=i-1&&r[i]<=n) { add(r[i],l[i]+1,i-1,p2); } if(1<=l[i]&&i+1<=r[i]-1) { add(l[i],i+1,r[i]-1,p2); } } T.build_tree(T.root[0],1,n); for(i=1;i<=n;i++) { T.root[i]=T.root[i-1]; for(j=0;j<e[i].size();j++) { T.update(T.root[i],T.root[i],1,n,e[i][j].l,e[i][j].r,e[i][j].w); } } for(i=1;i<=m;i++) { cin>>x>>y; cout<<T.query(T.root[x-1],T.root[y],1,n,x,y)<<endl; } return 0; }
SP11470 TTM - To the moon
-
标记永久化板子。
- 标记永久化可以避免下传懒惰标记,只需在进行询问时把标记的影响加入到答案中,从而降低程序常数。
- 常用在树套树或可持久化数据结构中保障空间复杂度正确。
点击查看代码
ll a[100010]; struct PDS_SMT { ll root[100010],rt_sum; struct SegmentTree { ll ls,rs,sum,lazy; }tree[100010<<5]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs ll build_rt() { rt_sum++; return rt_sum; } void pushup(ll rt) { tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } void build_tree(ll &rt,ll l,ll r) { rt=build_rt(); if(l==r) { tree[rt].sum=a[l]; return; } ll mid=(l+r)/2; build_tree(lson(rt),l,mid); build_tree(rson(rt),mid+1,r); pushup(rt); } void update(ll pre,ll &rt,ll l,ll r,ll x,ll y,ll val) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum+=val*(min(r,y)-max(l,x)+1);//直接更新 if(x<=l&&r<=y) { tree[rt].lazy+=val;//仅在重合区间更改懒惰标记 return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(pre),lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(pre),rson(rt),mid+1,r,x,y,val); } } ll query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt].sum; } ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),mid+1,r,x,y); } return ans+tree[rt].lazy*(min(r,y)-max(l,x)+1);//加上标记 } }T; int main() { ll n,m,l,r,d,t=0,i; char pd; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } T.build_tree(T.root[t],1,n); for(i=1;i<=m;i++) { cin>>pd; if(pd=='C') { cin>>l>>r>>d; t++; T.update(T.root[t-1],T.root[t],1,n,l,r,d); } if(pd=='Q') { cin>>l>>r; cout<<T.query(T.root[t],1,n,l,r)<<endl; } if(pd=='H') { cin>>l>>r>>d; cout<<T.query(T.root[d],1,n,l,r)<<endl; } if(pd=='B') { cin>>t; } } return 0; }
8.7
闲话
- 早上因 @Charlie_ljk 再次卡点到机房加上其他人在 \(7:00\) 前到机房但去厕所, \(miaomiao\) 好像有点不满意。 \(miaomiao\) 称早上调整到 \(7:00\) 是因为考虑到机房离宿舍、食堂太远,吃早饭前也最多有 \(20 \min\) 在机房,还不如把这时间给我们,但调整时间不是让我们卡着点来机房的; \(7:00\) 到机房指 \(7:00\) 进入状态,生理问题在 \(7:00\) 之前解决;之前不管宿舍违纪是因为在 HS 的时候没领导四处转,没跟别科奥赛的在一起自然也没有宿管,教练一次假期平均只查两次宿舍(开始时说一下分宿舍的事情,结束的时候说让把宿舍收拾干净),打牌、玩狼人杀、玩三国杀之类的都是别人告诉他的,他理解我们刚熄灯时睡不着,就先夜聊到 \(22:30\) ,等都有睡意了再睡,所以夜聊他不怎么管,但不能顶风作案,他也没明确表示对打牌、玩狼人杀、玩三国杀之类的不反对或禁止态度。
- 上午 \(7:30 \sim 11:30\) @LYinMX 学长安排了一场模拟赛,和 \(GXYZ,WFLS\) 的一起打。
- 吃午饭前分苹果。
- 下午先是 \(WFLS\) 的讲杂题。然后将模拟赛题。
-
\(T1\) 是 Gym 103687K 。
-
\(T2\) 是 CF1774G Segment Covering 。
-
\(T3,T4,T5\) 没找到原题。
-
\(T6\) 是 [ARC158D] Equation 。
-
做题纪要
luogu P3810 【模板】三维偏序(陌上花开)
-
\(CDQ\) 分治解决和点对有关的问题板子。
- 同以往的分治过程,对于不跨越中点的区间递归处理,然后设法处理跨越中点的区间。
- 一般思想是排序省掉一维,数据结构维护一维,枚举一维。
-
首先将序列按 \(\{ a \}\) 升序排序,那么 \(a_{j} \le a_{i}\) 就转化为了 \(j \le i\) ,接着我们仅需要考虑 \(b_{j} \le b_{i},c_{j} \le c_{i}\) 条件。将 \([l,mid]\) 和 \([mid+1,r]\) 分别按照 \(b\) 升序排序,那么就可以双指针维护 \(\{ b \}\) 的大小关系,接着权值树状数组维护 \(\{ c \}\) 即可。操作完及时撤销对树状数组的影响。
- 此过程可以同时手写实现归并排序来减小常数。
-
进行去重来保证对本质相同的花的转移正确。
- 若不去重,会导致本质相同的花内部的等级递增。
点击查看代码
int ans[200010]; struct node { int a,b,c,cnt,ans; }a[200010],b[200010]; bool cmpb(node a,node b) { return (a.b==b.b)?(a.c<b.c):(a.b<b.b); } bool cmpa(node a,node b) { return (a.a==b.a)?cmpb(a,b):(a.a<b.a); } struct BIT { int c[200010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; void cdq(int l,int r,int k) { if(l==r) { return; } int mid=(l+r)/2,x,y; cdq(l,mid,k); cdq(mid+1,r,k); sort(b+l,b+mid+1,cmpb); sort(b+mid+1,b+r+1,cmpb); for(x=l,y=mid+1;y<=r;y++)//双指针 { for(;b[x].b<=b[y].b&&x<=mid;x++) { T.add(k,b[x].c,b[x].cnt); } b[y].ans+=T.getsum(b[y].c); } x--;//此时的 x 已经不满足要求了 for(int i=l;i<=x;i++) { T.add(k,b[i].c,-b[i].cnt); } } int main() { int n,m=0,cnt=0,k,i; cin>>n>>k; for(i=1;i<=n;i++) { cin>>a[i].a>>a[i].b>>a[i].c; } sort(a+1,a+1+n,cmpa); for(i=1;i<=n;i++) { cnt++; if(a[i].a!=a[i+1].a||a[i].b!=a[i+1].b||a[i].c!=a[i+1].c) { m++; b[m]=a[i]; b[m].cnt=cnt; cnt=0; } } cdq(1,m,k); for(i=1;i<=m;i++) { ans[b[i].ans+b[i].cnt-1]+=b[i].cnt;//加上除自己以外(自己不算入贡献)的相同的花的贡献 } for(i=0;i<=n-1;i++) { cout<<ans[i]<<endl; } return 0; }
P215. 排排
luogu P5446 [THUPC2018] 绿绿和串串
luogu P5443 [APIO2019] 桥梁
8.8
闲话
- 上午 \(7:30 \sim 11:30\) @Muel_imj 学长安排了一场模拟赛,和 \(GXYZ,WFLS\) 的一起打。
- 下午讲题然后分桃。讲完题后 \(huge\) 提醒 \(miaomiao\) 处理我们当中违纪的人,让 @xrlong 制定值日表,说我们要遵照执行,相互提醒,别三天打鱼,两天晒网的。
- 头顶上的冷凝管恢复原位了,收拾前几天用来接水的盒子的时候,发现用来固定的夹子和标签的相接处生锈得很严重(全锈在了标签上),标签也泡软了。
做题纪要
P216. 九次九日九重色
luogu P3709 大爷的字符串题
CF570D Tree Requests
-
字符重排后可以形成回文串当且仅当至多有一个字符的出现次数为奇数。
-
树上启发式合并板子。
- 考虑将询问离线下来,预处理后 \(O(1)\) 输出答案。
- 算法流程
- 先遍历 \(x\) 的轻儿子,统计只在轻子树内部的答案,并及时清空对答案的影响。
- 接着遍历 \(x\) 的重儿子,统计只在重子树内部的答案。
- 加入 \(x\) 的贡献,再次遍历轻儿子,统计在 \(x\) 子树内部的答案。
点击查看代码
struct node { int nxt,to; }e[1000010]; int head[1000010],c[1000010],ans[1000010],cnt=0; vector<pair<int,int> >q[1000010]; char s[1000010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } int val(char x) { return x-'a'+1; } struct DSU_Tree { int fa[1000010],siz[1000010],dfn[1000010],out[1000010],pos[1000010],son[1000010],dep[1000010],cnt[1000010][28],tot=0; void add_rt(int x) { cnt[dep[x]][c[x]]++; } void del_rt(int x) { cnt[dep[x]][c[x]]--; } void add_tree(int x) { for(int i=dfn[x];i<=out[x];i++)//减小常数 { add_rt(pos[i]); } } void del_tree(int x) { for(int i=dfn[x];i<=out[x];i++)//减小常数 { del_rt(pos[i]); } } int ask(int x) { int flag=0; for(int i=1;i<=26;i++) { flag+=cnt[x][i]%2; } return flag<=1; } void dfs1(int x) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; dep[x]=dep[fa[x]]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]) { dfs1(e[i].to); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } out[x]=tot; } void dfs2(int x,int flag)//flag=0/1 表示是轻儿子/重儿子 { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,0);//递归轻儿子 } } if(son[x]!=0) { dfs2(son[x],1);//递归重儿子 } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { add_tree(e[i].to); } } add_rt(x);//加入 x 的贡献 for(int i=0;i<q[x].size();i++) { ans[q[x][i].first]=ask(q[x][i].second); } if(flag==0)//如果是轻儿子则清空影响 { del_tree(x); } } }T; int main() { int n,m,x,d,i; char pd; scanf("%d%d",&n,&m); for(i=2;i<=n;i++) { scanf("%d",&T.fa[i]); add(T.fa[i],i); } scanf("%s",s+1); for(i=1;i<=n;i++) { c[i]=val(s[i]); } for(i=1;i<=m;i++) { scanf("%d%d",&x,&d); q[x].push_back(make_pair(i,d)); } T.dfs1(1); T.dfs2(1,1); for(i=1;i<=m;i++) { if(ans[i]==1) { printf("Yes\n"); } else { printf("No\n"); } } return 0; }
CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
CF1709E XOR Tree
-
将 \(x\) 的权值更改为任意正整数等价于经过 \(x\) 的所有简单路径各自的异或和都不等于 \(0\) 。
-
设 \(sum_{x}\) 表示从 \(1\) 到 \(x\) 的路径上的点的异或和,则 \(x \to y\) 经过的点的异或和为 \(sum_{x} \bigoplus sum_{y} \bigoplus a_{\operatorname{LCA}(x,y)}\) ,若其不合法则有 \(sum_{x} \bigoplus a_{\operatorname{LCA}(x,y)}=sum_{y}\) 。
-
枚举 \(\operatorname{LCA}(x,y)\) 的过程中树上启发式合并维护即可。
-
若遇到不合法的清空则答案加一,并删掉整棵子树。
点击查看代码
struct node { int nxt,to; }e[400010]; int head[400010],a[400010],cnt=0,ans=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct DSU_Tree { int sum[400010]; map<int,int>vis[400010]; map<int,int>::iterator it; int merge(int u,int v) { int flag=0; if(vis[u].size()<vis[v].size()) { swap(vis[u],vis[v]); } for(it=vis[v].begin();it!=vis[v].end();it++) { flag|=(vis[u].find((it->first)^a[u])!=vis[u].end()); } for(it=vis[v].begin();it!=vis[v].end();it++) { vis[u][it->first]=1; } return flag; } void dfs(int x,int fa) { int flag=0; sum[x]=sum[fa]^a[x]; vis[x][sum[x]]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); flag|=merge(x,e[i].to); } } if(flag==1) { vis[x].clear(); ans++; } } }T; int main() { int n,u,v,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } T.dfs(1,0); cout<<ans<<endl; return 0; }
8.9
闲话
- 上午 @H_Kaguya 讲了 \(CDQ\) 分治和整体二分, @Muel_imj 讲了虚树,全程掉线。
- 下午分瓜时 \(huge\) 以为我们只吃一块是因为太腼腆,让我们出三四个人多吃块,不然就坏了,我们不为所动。然后以要干活为由让 @HANGRY_sol 多吃了两块,让 @CuFeO4 多吃了一块。
- 晚上因下雨下得很大, \(feifei\) 让我们提前下课了 \(5 \min\) 。
- 晚休 \(feifei\) 查宿。
做题纪要
CF246E Blood Cousins Return
-
树上启发式合并板子。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],ans[200010],cnt=0; string s[200010]; vector<pair<int,int> >q[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct DSU_Tree { int fa[200010],siz[200010],dfn[200010],out[200010],pos[200010],son[200010],dep[200010],tot=0; set<string>vis[400010]; void add_rt(int x) { vis[dep[x]].insert(s[x]); } void del_rt(int x) { vis[dep[x]].clear(); } void add_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { add_rt(pos[i]); } } void del_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { del_rt(pos[i]); } } int ask(int x) { return vis[x].size(); } void dfs1(int x) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; dep[x]=dep[fa[x]]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]) { dfs1(e[i].to); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } out[x]=tot; } void dfs2(int x,int flag) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,0); } } if(son[x]!=0) { dfs2(son[x],1); } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { add_tree(e[i].to); } } add_rt(x); for(int i=0;i<q[x].size();i++) { ans[q[x][i].first]=ask(q[x][i].second); } if(flag==0) { del_tree(x); } } }T; int main() { int n,m,v,d,i; cin>>n; for(i=1;i<=n;i++) { cin>>s[i]>>T.fa[i]; add(T.fa[i],i); } for(i=1;i<=n;i++) { if(T.fa[i]==0) { T.dfs1(i); } } cin>>m; for(i=1;i<=m;i++) { cin>>v>>d; q[v].push_back(make_pair(i,T.dep[v]+d)); } for(i=1;i<=n;i++) { if(T.fa[i]==0) { T.dfs2(i,0); } } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
CF208E Blood Cousins
-
找到 \(k\) 级祖先后就是问除自己外这个祖先有几个 \(k\) 级祖孙,树上启发式合并即可。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],ans[200010],cnt=0; vector<pair<int,int> >q[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct DSU_Tree { int fa[200010][25],siz[200010],dfn[200010],out[200010],pos[200010],son[200010],dep[200010],cnt[200010],N,tot=0; void add_rt(int x) { cnt[dep[x]]++; } void del_rt(int x) { cnt[dep[x]]--; } void add_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { add_rt(pos[i]); } } void del_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { del_rt(pos[i]); } } int ask(int x) { return cnt[x]-1; } void dfs1(int x) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; dep[x]=dep[fa[x][0]]+1; for(int i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x][0]) { dfs1(e[i].to); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } out[x]=tot; } void dfs2(int x,int flag) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x][0]) { dfs2(e[i].to,0); } } if(son[x]!=0) { dfs2(son[x],1); } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x][0]) { add_tree(e[i].to); } } add_rt(x); for(int i=0;i<q[x].size();i++) { ans[q[x][i].first]=ask(q[x][i].second); } if(flag==0) { del_tree(x); } } int kth_fa(int x,int k) { for(int i=N;i>=0;i--) { if((1<<i)<=k) { k-=(1<<i); x=fa[x][i]; } } return x; } }T; int main() { int n,m,x,d,i; cin>>n; T.N=log2(n)+1; for(i=1;i<=n;i++) { cin>>T.fa[i][0]; add(T.fa[i][0],i); } for(i=1;i<=n;i++) { if(T.fa[i][0]==0) { T.dfs1(i); } } cin>>m; for(i=1;i<=m;i++) { cin>>x>>d; x=T.kth_fa(x,d); q[x].push_back(make_pair(i,T.dep[x]+d)); } for(i=1;i<=n;i++) { if(T.fa[i][0]==0) { T.dfs2(i,0); } } for(i=1;i<=m;i++) { cout<<ans[i]<<" "; } return 0; }
CF343D Water Tree
-
树剖板子。
点击查看代码
struct node { int nxt,to; }e[1000010]; int head[1000010],c[1000010],cc[1000010],siz[1000010],fa[1000010],dep[1000010],son[1000010],top[1000010],dfn[1000010],out[1000010],tot=0,cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int father,int id) { top[x]=id; tot++; dfn[x]=tot; cc[tot]=c[x]; if(son[x]!=0) { dfs2(son[x],x,id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father&&e[i].to!=son[x]) { dfs2(e[i].to,x,e[i].to); } } } out[x]=tot; } struct SMT { struct SegmentTree { int l,r,sum,lazy; }tree[4000010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=-1; if(l==r) { tree[rt].sum=cc[l]; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushdown(int rt) { if(tree[rt].lazy!=-1) { tree[lson(rt)].lazy=tree[rson(rt)].lazy=tree[rt].lazy; tree[lson(rt)].sum=tree[rson(rt)].sum=tree[rt].lazy; tree[rt].lazy=-1; } } void update(int rt,int x,int y,int val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].sum=tree[rt].lazy=val; return; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } } int query(int rt,int pos) { if(tree[rt].l==tree[rt].r) { return tree[rt].sum; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(pos<=mid) { return query(lson(rt),pos); } else { return query(rson(rt),pos); } } }T; void update1(int u,int val) { T.update(1,dfn[u],out[u],val); } void update2(int u,int v,int val) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { T.update(1,dfn[top[u]],dfn[u],val); u=fa[top[u]]; } else { T.update(1,dfn[top[v]],dfn[v],val); v=fa[top[v]]; } } if(dep[u]<dep[v]) { T.update(1,dfn[u],dfn[v],val); } else { T.update(1,dfn[v],dfn[u],val); } } int query1(int u) { return T.query(1,dfn[u]); } int main() { int n,m,pd,u,v,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,0,1); T.build(1,1,n); cin>>m; for(i=1;i<=m;i++) { cin>>pd>>u; if(pd==1) { update1(u,1); } if(pd==2) { update2(u,1,0); } if(pd==3) { cout<<query1(u)<<endl; } } return 0; }
SP1684 FREQUENT - Frequent values
-
多倍经验: UVA11235 Frequent values
点击查看代码
int a[100010],b[100010],pos[100010],L[100010],R[100010],ans[100010],num[100010],cnt[100010],klen,ksum; struct ask { int l,r,id; }q[100010]; bool q_cmp(ask a,ask b) { return (pos[a.l]==pos[b.l])?((pos[a.l]%2==1)?(a.r<b.r):(a.r>b.r)):(a.l<b.l); } void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } } } void add(int x,int &sum) { num[cnt[a[x]]]--; cnt[a[x]]++; num[cnt[a[x]]]++; sum=max(sum,cnt[a[x]]); } void del(int x,int &sum) { num[cnt[a[x]]]--; sum-=(sum==cnt[a[x]]&&num[cnt[a[x]]]==0); cnt[a[x]]--; num[cnt[a[x]]]++; } int main() { int n,m,l=1,r=0,sum=0,i; while(cin>>n) { if(n==0) { break; } else { cin>>m; l=1; r=0; sum=0; memset(num,0,sizeof(num)); memset(cnt,0,sizeof(cnt)); for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]; } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); for(i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+1+b[0],a[i])-b; } init(n,m); for(i=1;i<=m;i++) { cin>>q[i].l>>q[i].r; q[i].id=i; } sort(q+1,q+1+m,q_cmp); for(i=1;i<=m;i++) { while(l>q[i].l) { l--; add(l,sum); } while(r<q[i].r) { r++; add(r,sum); } while(l<q[i].l) { del(l,sum); l++; } while(r>q[i].r) { del(r,sum); r--; } ans[q[i].id]=sum; } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } } } return 0; }
CF375D Tree and Queries
-
对桶开个桶表示 \(\ge\) 某个数的个数,然后树上启发式合并维护即可。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],c[200010],ans[200010],cnt=0; vector<pair<int,int> >q[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct DSU_Tree { int fa[200010],siz[200010],dfn[200010],out[200010],pos[200010],son[200010],dep[200010],cnt[200010],num[200010],tot=0; void add_rt(int x) { cnt[c[x]]++; num[cnt[c[x]]]++; } void del_rt(int x) { num[cnt[c[x]]]--; cnt[c[x]]--; } void add_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { add_rt(pos[i]); } } void del_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { del_rt(pos[i]); } } int ask(int x) { return num[x]; } void dfs1(int x,int father) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } out[x]=tot; } void dfs2(int x,int flag) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x]) { dfs2(e[i].to,0); } } if(son[x]!=0) { dfs2(son[x],1); } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x]) { add_tree(e[i].to); } } add_rt(x); for(int i=0;i<q[x].size();i++) { ans[q[x][i].first]=ask(q[x][i].second); } if(flag==0) { del_tree(x); } } }T; int main() { int n,m,u,v,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } T.dfs1(1,0); for(i=1;i<=m;i++) { cin>>u>>v; q[u].push_back(make_pair(i,v)); } T.dfs2(1,1); for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
AT_past202010_m 筆塗り
-
边权转点权,树剖板子。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],c[200010],ans[200010],cnt=0; vector<pair<int,int> >q[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct DSU_Tree { int fa[200010],siz[200010],dfn[200010],out[200010],pos[200010],son[200010],dep[200010],cnt[200010],num[200010],tot=0; void add_rt(int x) { cnt[c[x]]++; num[cnt[c[x]]]++; } void del_rt(int x) { num[cnt[c[x]]]--; cnt[c[x]]--; } void add_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { add_rt(pos[i]); } } void del_tree(int x) { for(int i=dfn[x];i<=out[x];i++) { del_rt(pos[i]); } } int ask(int x) { return num[x]; } void dfs1(int x,int father) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } out[x]=tot; } void dfs2(int x,int flag) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x]) { dfs2(e[i].to,0); } } if(son[x]!=0) { dfs2(son[x],1); } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x]) { add_tree(e[i].to); } } add_rt(x); for(int i=0;i<q[x].size();i++) { ans[q[x][i].first]=ask(q[x][i].second); } if(flag==0) { del_tree(x); } } }T; int main() { int n,m,u,v,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } T.dfs1(1,0); for(i=1;i<=m;i++) { cin>>u>>v; q[u].push_back(make_pair(i,v)); } T.dfs2(1,1); for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
luogu P10814 【模板】离线二维数点
-
拆成前缀和形式,然后一维为下标,一维为权值,然后就转化成了二维偏序问题。
-
双指针加权值树状数组维护即可。
点击查看代码
struct node { int pos,x,val,id; }q[4000010]; int a[2000010],ans[2000010],cnt=0; bool cmp(node a,node b) { return a.pos<b.pos; } void add(int pos,int x,int val,int id) { cnt++; q[cnt].pos=pos; q[cnt].x=x; q[cnt].val=val; q[cnt].id=id; } struct BIT { int c[2000010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; int main() { int n,m,l,r,x,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=m;i++) { cin>>l>>r>>x; add(l-1,x,-1,i); add(r,x,1,i); } sort(q+1,q+1+cnt,cmp); for(i=1,j=1;i<=cnt;i++) { for(;j<=q[i].pos;j++)//双指针 { T.add(2000000,a[j],1); } ans[q[i].id]+=q[i].val*T.getsum(q[i].x); } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
luogu P4390 [BalkanOI2007] Mokia 摩基亚
-
进行二维差分,将一个询问拆成四个询问,然后就仅需要统计左下角为 \((1,1)\) ,右上角为 \((n,m)\) 的矩形。
-
对于修改操作额外加入一维为时间戳,然后就转化为了三维偏序问题, \(CDQ\) 分治维护即可。
点击查看代码
struct node { int a,b,c,val,ans,id; }q[660010]; int cnt=0; bool cmpb(node a,node b) { return (a.b==b.b)?(a.c<b.c):(a.b<b.b); } bool cmpa(node a,node b) { return (a.a==b.a)?cmpb(a,b):(a.a<b.a); } void add(int b,int c,int val,int id) { cnt++; q[cnt].a=cnt; q[cnt].b=b; q[cnt].c=c; q[cnt].val=val; q[cnt].id=id; } struct BIT { int c[2000010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; void cdq(int l,int r,int k) { if(l==r) { return; } int mid=(l+r)/2,x,y; cdq(l,mid,k); cdq(mid+1,r,k); sort(q+l,q+mid+1,cmpb); sort(q+mid+1,q+r+1,cmpb); for(x=l,y=mid+1;y<=r;y++) { for(;q[x].b<=q[y].b&&x<=mid;x++) { T.add(k,q[x].c,q[x].val); } q[y].ans+=T.getsum(q[y].c); } x--; for(int i=l;i<=x;i++) { T.add(k,q[i].c,-q[i].val); } } int main() { int pd,x1,y1,x2,y2,k,a,i; while(cin>>pd) { if(pd==0) { cin>>k; k++;//特判 -1 后等于 0,所以整体加一 } if(pd==1) { cin>>x1>>y1>>a; x1++; y1++; add(x1,y1,a,0); } if(pd==2) { cin>>x1>>y1>>x2>>y2; x1++; y1++; x2++; y2++; add(x1-1,y1-1,0,1); add(x2,y2,0,1); add(x2,y1-1,0,1); add(x1-1,y2,0,1); } if(pd==3) { break; } } cdq(1,cnt,k); sort(q+1,q+1+cnt,cmpa); for(i=1;i<=cnt;i++) { if(q[i].id==1) { cout<<q[i].ans+q[i+1].ans-q[i+2].ans-q[i+3].ans<<endl; i+=3; } } return 0; }
[ABC309F] Box in Box
-
钦定 \(h_{i} \le w_{i} \le d_{i}\) ,因为是严格大于,所以普通 \(CDQ\) 分治做不了。
-
仍将 \(\{ a \}\) 进行排序,然后维护一段连续的 \(a_{i}\) 相同的段,另外两维就好处理了,插到权值树状数组里维护前缀 \(\min\) 即可。
点击查看代码
struct node { int a,b,c,ans; }a[200010]; int ls[4],b[200010]; bool cmpb(node a,node b) { return (a.b==b.b)?(a.c<b.c):(a.b<b.b); } bool cmpa(node a,node b) { return (a.a==b.a)?cmpb(a,b):(a.a<b.a); } struct BIT { int c[200010]; void init() { memset(c,0x3f,sizeof(c)); } int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]=min(c[i],val); } } int getsum(int x) { int ans=0x3f3f3f3f; for(int i=x;i>=1;i-=lowbit(i)) { ans=min(ans,c[i]); } return ans; } }T; int main() { int n,l,r,flag=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>ls[1]>>ls[2]>>ls[3]; sort(ls+1,ls+4); a[i].a=ls[1]; b[i]=a[i].b=ls[2]; a[i].c=ls[3]; } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); for(i=1;i<=n;i++) { a[i].b=lower_bound(b+1,b+1+b[0],a[i].b)-b; } sort(a+1,a+1+n,cmpa); T.init(); for(l=1;l<=n;l=r+1) { r=l; while(a[r+1].a==a[l].a) { r++; } for(i=l;i<=r;i++) { if(T.getsum(a[i].b-1)<a[i].c) { flag=1; break; } } if(flag==1) { break; } for(i=l;i<=r;i++) { T.add(b[0],a[i].b,a[i].c); } } if(flag==0) { cout<<"No"<<endl; } else { cout<<"Yes"<<endl; } return 0; }
luogu P8575 「DTOI-2」星之河
-
增加一维为 \(DFS\) 序,然后就转化成了三维偏序问题。
-
此时等价于求 \(red_{j} \le red_{i},blue_{j} \le blue_{i},dfn_{i}<dfn_{j} \le out_{i}\) 的 \(j\) 的数量。第三个限制条件前缀和维护即可。
-
注意按 \(DFS\) 序排序时,为保证子树内部节点能更新到根节点,需要降序排序。
点击查看代码
struct node { int a,b,dfn,out,id; }a[200010]; int ans[200010],tot=0; vector<int>e[200010]; bool cmpb(node a,node b) { return (a.b==b.b)?(a.dfn>b.dfn):(a.b<b.b); } bool cmpa(node a,node b) { return (a.a==b.a)?cmpb(a,b):(a.a<b.a); } void add(int u,int v) { e[u].push_back(v); } void dfs(int x,int fa) { tot++; a[x].dfn=tot; for(int i=0;i<e[x].size();i++) { if(e[x][i]!=fa) { dfs(e[x][i],x); } } a[x].out=tot; } struct BIT { int dfn[200010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { dfn[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=dfn[i]; } return ans; } }T; void cdq(int l,int r,int k) { if(l==r) { return; } int mid=(l+r)/2,x,y; cdq(l,mid,k); cdq(mid+1,r,k); sort(a+l,a+mid+1,cmpb); sort(a+mid+1,a+r+1,cmpb); for(x=l,y=mid+1;y<=r;y++) { for(;a[x].b<=a[y].b&&x<=mid;x++) { T.add(k,a[x].dfn,1); } ans[a[y].id]+=T.getsum(a[y].out)-T.getsum(a[y].dfn); } x--; for(int i=l;i<=x;i++) { T.add(k,a[i].dfn,-1); } } int main() { int n,u,v,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); for(i=1;i<=n;i++) { cin>>a[i].a>>a[i].b; a[i].id=i; } sort(a+1,a+1+n,cmpa); cdq(1,n,n); for(i=1;i<=n;i++) { if(ans[i]!=0) { cout<<ans[i]<<endl; } } return 0; }
luogu P3157 [CQOI2011] 动态逆序对
-
此时等价于求 \(a_{i}>a_{j},i<j,t_{i} \le t_{j}\) 或 \(a_{i}<a_{j},i>j,t_{i} \le t_{j}\) 的数量, \(CDQ\) 分治维护即可。
- 跑两遍是因为一对点对只会产生一次贡献,但一次统计可能统计不全贡献。
点击查看代码
struct node { ll t,pos,a,id,val; }a[200010]; ll pos[200010],ans[200010]; bool cmp(node a,node b) { return a.pos<b.pos; } struct BIT { ll c[200010]; ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; void cdq(ll l,ll r,ll k) { if(l==r) { return; } ll mid=(l+r)/2,x,y; cdq(l,mid,k); cdq(mid+1,r,k); sort(a+l,a+mid+1,cmp); sort(a+mid+1,a+r+1,cmp); for(x=l,y=mid+1;y<=r;y++) { for(;a[x].pos<=a[y].pos&&x<=mid;x++) { T.add(k,a[x].a,a[x].val); } ans[a[y].id]+=a[y].val*(T.getsum(k)-T.getsum(a[y].a)); } x--; for(ll i=l;i<=x;i++) { T.add(k,a[i].a,-a[i].val); } for(x=mid,y=r;y>=mid+1;y--) { for(;a[x].pos>=a[y].pos&&x>=l;x--) { T.add(k,a[x].a,a[x].val); } ans[a[y].id]+=a[y].val*T.getsum(a[y].a-1); } x++; for(ll i=mid;i>=x;i--) { T.add(k,a[i].a,-a[i].val); } } int main() { ll n,m,x,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i].a; a[i].val=1; a[i].pos=i; a[i].id=0; a[i].t=i; pos[a[i].a]=i; } for(i=1;i<=m;i++) { cin>>x; a[n+i].a=x; a[n+i].val=-1; a[n+i].pos=pos[x]; a[n+i].id=i; a[n+i].t=n+i; } cdq(1,n+m,n); for(i=1;i<=m;i++) { ans[i]+=ans[i-1];//单次修改的贡献,若为负数说明逆序对减少了 } for(i=0;i<=m-1;i++)//删除前 { cout<<ans[i]<<endl; } return 0; }
luogu P3374 【模板】树状数组 1
-
\(CDQ\) 分治将动态问题转化成静态问题板子。
- 基础思想是将时间/操作序列折半后递归处理点对间的关系。
- 将所有操作离线下来,对操作序列进行分治,处理修改与询问之间的关系。
- 假设当前正在分治的序列为 \([l,r]\) ,已经递归处理完了 \([l,mid]\) 和 \([mid+1,r]\) 内部的修改与询问的关系,然后处理 \([l,mid]\) 内的修改对 \([mid+1,r]\) 的询问的影响。
- 此过程可以同时手写实现归并排序来减小常数。
- 若修改之间是相互独立的(例如加减法),可以不用管
cdq(l,mid)
和cdq(mid+1,r)
以及 \([l,mid]\) 内的修改对 \([mid+1,r]\) 的询问的影响的时序关系。但若修改之间不相互独立(例如赋值),那么处理 \([l,mid]\) 内的修改对 \([mid+1,r]\) 的询问的影响必须放在cdq(l,mid)
和cdq(mid+1,r)
之间,从而保证修改严格按照时间顺序执行。
-
将询问化成前缀和的形式。
点击查看归并排序代码
struct node { int pd,pos,val,id; bool operator < (const node &another) const { return (pos==another.pos)?(pd>another.pd):(pos<another.pos); } }a[1500010],tmp[1500010]; int ans[1500010],cnt=0,q_cnt=0; void add(int pd,int pos,int val,int id) { cnt++; a[cnt].pd=pd; a[cnt].pos=pos; a[cnt].val=val; a[cnt].id=id; } void cdq(int l,int r) { if(l>=r)//学校 OJ 有 n=m=0 的数据,特判区间不存在即可 { return; } int mid=(l+r)/2,sum=0,x=l,y=mid+1,pos=l; cdq(l,mid); cdq(mid+1,r); while(x<=mid&&y<=r) { if(a[x]<a[y]) { sum+=a[x].pd*a[x].val;//pd=1 说明是修改 tmp[pos]=a[x];//归并排序 pos++; x++; } else { ans[a[y].id]+=a[y].val*sum; tmp[pos]=a[y]; pos++; y++; } } while(x<=mid)//复制左边子序列剩余 { tmp[pos]=a[x]; pos++; x++; } while(y<=r)//复制右边子序列剩余 { ans[a[y].id]+=a[y].val*sum; tmp[pos]=a[y]; pos++; y++; } for(int i=l;i<=r;i++) { a[i]=tmp[i]; } } int main() { int n,m,pd,x,y,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>x; add(1,i,x,0);//修改的查询挂给了 0 注意可能会爆 long long } for(i=1;i<=m;i++) { cin>>pd>>x>>y; if(pd==1) { add(1,x,y,0); } else { q_cnt++; add(0,x-1,-1,q_cnt); add(0,y,1,q_cnt); } } cdq(1,cnt); for(i=1;i<=q_cnt;i++) { cout<<ans[i]<<endl; } return 0; }
点击查看快速排序代码
struct node { int pd,pos,val,id; bool operator < (const node &another) const { return (pos==another.pos)?(pd>another.pd):(pos<another.pos); } }a[1500010]; int ans[1500010],cnt=0,q_cnt=0; void add(int pd,int pos,int val,int id) { cnt++; a[cnt].pd=pd; a[cnt].pos=pos; a[cnt].val=val; a[cnt].id=id; } void cdq(int l,int r) { if(l>=r) { return; } int mid=(l+r)/2,sum=0,x=l,y=mid+1,pos=l; cdq(l,mid); cdq(mid+1,r); sort(a+l,a+mid+1); sort(a+mid+1,a+r+1); for(x=l,y=mid+1;y<=r;y++) { for(;a[x]<a[y]&&x<=mid;x++) { sum+=a[x].pd*a[x].val; } ans[a[y].id]+=a[y].val*sum; } } int main() { int n,m,pd,x,y,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>x; add(1,i,x,0); } for(i=1;i<=m;i++) { cin>>pd>>x>>y; if(pd==1) { add(1,x,y,0); } else { q_cnt++; add(0,x-1,-1,q_cnt); add(0,y,1,q_cnt); } } cdq(1,cnt); for(i=1;i<=q_cnt;i++) { cout<<ans[i]<<endl; } return 0; }
CF641E Little Artem and Time Machine
-
以时间轴作为分治序列,然后 \(CDQ\) 分治维护即可。
点击查看代码
struct node { int pd,pos,val,col,id; bool operator < (const node &another) const { return (pos==another.pos)?(pd>another.pd):(pos<another.pos); } }a[300010],tmp[300010]; int b[300010],ans[300010],sum[300010],cnt=0,q_cnt=0; void add(int pd,int pos,int val,int col,int id) { cnt++; a[cnt].pd=pd; a[cnt].pos=pos; a[cnt].val=val; a[cnt].col=col; a[cnt].id=id; } void cdq(int l,int r) { if(l>=r) { return; } int mid=(l+r)/2,x=l,y=mid+1,pos=l; cdq(l,mid); cdq(mid+1,r); while(x<=mid&&y<=r) { if(a[x]<a[y]) { sum[a[x].col]+=a[x].pd*a[x].val; tmp[pos]=a[x]; pos++; x++; } else { ans[a[y].id]+=a[y].val*sum[a[y].col]; tmp[pos]=a[y]; pos++; y++; } } while(x<=mid) { tmp[pos]=a[x]; pos++; x++; } while(y<=r) { ans[a[y].id]+=a[y].val*sum[a[y].col]; tmp[pos]=a[y]; pos++; y++; } for(int i=l;i<=mid;i++) { sum[a[i].col]=0; } for(int i=l;i<=r;i++) { a[i]=tmp[i]; } } int main() { int n,pd,t,x,i; cin>>n; for(i=1;i<=n;i++) { cin>>pd>>t>>x; b[i]=x; if(pd==1) { add(1,t,1,x,0); } if(pd==2) { add(1,t,-1,x,0); } if(pd==3) { q_cnt++; add(0,t,1,x,q_cnt); } } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); for(i=1;i<=n;i++) { a[i].col=lower_bound(b+1,b+1+b[0],a[i].col)-b; } cdq(1,cnt); for(i=1;i<=q_cnt;i++) { cout<<ans[i]<<endl; } return 0; }
8.10
闲话
- 上午 \(7:30 \sim 11:30\) @joke3579 学长安排了一场模拟赛,和 \(GXYZ,WFLS\) 的一起打。
- 下午讲题。
- 体活的时候 \(miaomiao\) 说别在机房乱搞。 @CuFeO4 问 \(huge\) 能不能开网,反被问开了网想干什么,说看电影就好好看。
- 晚上去四机房看《学爸》,快看完的时候 \(miaomiao\) 进来了,可能要查谷歌小恐龙?
- 晚休 \(miaomiao\) 查宿。
做题纪要
luogu P4169 [Violet] 天使玩偶/SJY摆棋子
-
将绝对值拆成四个方向的询问,各跑一遍 \(CDQ\) 分治。以左下角为例,有 \(|x-x'|+|y-y'|=x+y-(x'+y')\) ,维护 \(x'+y'\) 的最大值即可。
-
四个方向旋转坐标轴即可。
-
略带卡常。
- 进行下一次 \(CDQ\) 分治时,时间戳一维已经乱序,再排序一次不如复制初始数组一份并加以处理。
- \(CDQ\) 分治途中采用归并排序写法。
点击查看代码
struct node { int t,x,y,pd,id; bool operator < (const node &another) const { return (x==another.x)?((y==another.y)?(pd>another.pd):(y<another.y)):(x<another.x); } }a[1500010],b[1500010],tmp[1500010]; int ans[1500010],cnt=0,q_cnt=0; void add(int t,int x,int y,int pd,int id) { cnt++; b[cnt].t=t; b[cnt].x=x; b[cnt].y=y; b[cnt].pd=pd; b[cnt].id=id; } struct BIT { int c[1000010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]=max(c[i],val); } } void del(int n,int x) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]=0; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans=max(ans,c[i]); } return ans; } }T; void cdq(int l,int r,int k) { if(l==r) { return; } int mid=(l+r)/2,x=l,y=mid+1,pos=l,sum; cdq(l,mid,k); cdq(mid+1,r,k); while(x<=mid&&y<=r) { if(a[x]<a[y]) { T.add(k,a[x].y,(a[x].x+a[x].y)*a[x].pd); tmp[pos]=a[x]; pos++; x++; } else { sum=T.getsum(a[y].y); if(sum!=0) { ans[a[y].id]=min(ans[a[y].id],a[y].x+a[y].y-sum);//不能自己到自己 } tmp[pos]=a[y]; pos++; y++; } } while(x<=mid) { tmp[pos]=a[x]; pos++; x++; } while(y<=r) { sum=T.getsum(a[y].y); if(sum!=0) { ans[a[y].id]=min(ans[a[y].id],a[y].x+a[y].y-sum);//不能自己到自己 } tmp[pos]=a[y]; pos++; y++; } for(int i=l;i<=mid;i++)//理论上是不应该删除至 mid 的,而是处理左右剩余部分前的 x ,但因为是清零所以无伤大雅 { T.del(k,a[i].y);//删除影响 } for(int i=l;i<=r;i++) { a[i]=tmp[i]; } } int main() { int n,m,pd,x,y,i; cin>>n>>m; memset(ans,0x7f,sizeof(ans)); for(i=1;i<=n;i++) { cin>>x>>y; x++; y++; add(i,x,y,1,0); } for(i=1;i<=m;i++) { cin>>pd>>x>>y; x++; y++; if(pd==1) { add(n+i,x,y,1,0); } else { q_cnt++; add(n+i,x,y,0,q_cnt); } } for(i=1;i<=n+m;i++) { a[i]=b[i];//左下 } cdq(1,n+m,1000002); for(i=1;i<=n+m;i++) { a[i]=b[i];//避免排序 a[i].x=1000002-b[i].x;//右下 } cdq(1,n+m,1000002); for(i=1;i<=n+m;i++) { a[i]=b[i]; a[i].y=1000002-b[i].y;//左上 } cdq(1,n+m,1000002); for(i=1;i<=n+m;i++) { a[i]=b[i]; a[i].x=1000002-b[i].x; a[i].y=1000002-b[i].y;//右上 } cdq(1,n+m,1000002); for(i=1;i<=q_cnt;i++) { cout<<ans[i]<<endl; } return 0; }
[ABC364E] Maximum Glutton
[ABC081D] Non-decreasing
luogu P10102 [GDKOI2023 提高组] 矩阵
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18335551,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。