11 月做题记录
11.01-11.03
AT_arc153_c [ARC153C] ± Increasing Sequence
先赋值为 ,然后找到一个 等于 且代价相反的即可。
CF1103D Professional layer
首先只有不超过 个质因子,然后我们每次暴力枚举消除的质因子集合,保留代价前 小的即可,然后就能过了???
CF1476F Lanterns
经典老番,设 表示前 个灯的能覆盖的最长前缀是多少。
- 向右,,。
- 向左,找到一个 满足 ,则转移有 。
- 虽然朝向了一个方向,但是贡献如有,。
CF713E Sonya Partymaker
首先我们来一手二分,然后不难发现如果是一条链的话就是上一个题了,但是我们发现还有 的方法不好确定,我们考虑由于答案不超过 ,所以我们直接找出最大的区间当作 的间隔,然后分讨 向左还是 向左即可。
CF1366F Jog Around The Graph
首先我们肯定是走到某个边,然后来回刷分,我们直接设 表示走了 条边,在 时最大经过的权值和即可。
然后后面的贡献是一个一次函数,建凸包即可。
CF1450H1 Multithreading (Easy Version)
首先,根据魔怔的贪心,我们可以首先每次选出相邻的两个颜色相同的配对,最后会剩下 abababab 的环,交点数量是 ,然后我们如果 在奇数下标出现 次,偶数下标出现 次,则答案是 。
然后假设奇数下标有 个,偶数有 个,确定的 奇偶下标之差为 ,则
然后做完了。
CF878D Magic Breeding
我们枚举答案 ,把 变成 ,然后我们考虑 种列,bitset 维护每种列的答案,转移可以 bitset 优化转移,复杂度 。
11.04-11.10
P7324 [WC2021] 表达式求值
首先我们对于每个下标分开考虑,考虑预处理出来 种集合 每种集合最后为 的方案数,然后每位计算的时候,我们差分后计算每个位置的答案即可,复杂度
AT_arc155_d [ARC155D] Avoid Coprime Game
我们考虑 dp,设我们当前在 ,那么我们考虑一个 和 的关系。
- , 变成 。
- 否则我们考虑 的倍数的奇偶以及操作次数的奇偶性即可。
我们考虑 dp,设 表示当前为 ,操作了奇数/偶数次即可。
转移我们考虑对于 的每个因数 ,容斥计算是否存在 使得 即可。
有点抽象,放个代码:
Code
#include<bits/stdc++.h> #define pb push_back using namespace std; const int N=2e5,M=N+5; int n,a[M],f[M][2],cnt1[M],cnt2[M]; vector<int>vec[M]; signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(int i=1;i<=n;++i) cin>>a[i],++cnt1[a[i]]; for(int i=2;i<=N;++i) for(int j=i;j<=N;j+=i) vec[j].pb(i),cnt1[i]+=cnt1[j]*(i<j); for(int i=2;i<=N;++i){ int sz=vec[i].size(),fl=0; for(auto v:vec[i]) cnt2[v]=cnt1[v]; for(int j=sz-1;~j;--j){ int x=vec[i][j]; if(j<sz-1&&cnt2[x]!=0){ if(!f[x][1]) f[i][0]=fl=1; if(!f[x][0]) f[i][1]=fl=1; } for(auto v:vec[x]) cnt2[v]-=cnt2[x]; } if(!fl) f[i][cnt1[i]%2]=1; } for(int i=1;i<=n;++i) puts(f[a[i]][0]?"Aoki":"Takahashi"); }
P8180 「EZEC-11」Unmemorable
实际上,根据这个只需要根据 数组,我们就能得到唯一的笛卡尔树形态。
我们对于 前缀和得到 数组。
我们考虑全局最小值,发现他是最后一个满足 的 。
然后后面的问题是类似的,递归即可,然后我们就建出笛卡尔树了,dp 是简单的。
P2481 [SDOI2010] 代码拍卖会
拆成若干段 ,寻找循环节然后背包。
P3634 [APIO2012] 守卫
首先把不能放人的扔了,特判剩下全部放的情况。
然后套用经典贪心,只有这些被选择的点可能成为答案。
考虑怎么判断,把包含的区间删了,变成互不包含的,直接预处理一下前后缀信息即可。
LOJ 6268 分拆数
我们考虑 Ferrers 图,引一条 的线,考虑多久离开图形,不妨设为 ,则两端分别是大小不超过 的拆分数,即:
我们不难发现,当我们计算第 项时,,然后后面的生成函数我们直接算就行了,代码非常短,复杂度 。
常数非常小,计算 中每个数的拆分数本机下只需要 770 ms,如果只需要第 项这一项,只需要 460ms
P8340 [AHOI2022] 山河重整
某些人不会 求互异拆分数还想打山河重整 /cf
考虑如何判断一个集合合法,当且仅当
然后我们可以暴力 dp,。
然后我们考虑怎么做到更低复杂度,我们来手容斥,设 表示在第一次在 处不满足条件的方案数,即对于 都满足上面的条件, 不满足,则答案就是:
然后我们考虑如何计算这个,首先我们考虑如果 都满足, 不满足,则我们当前的权值和一定是 ,设我们凑出 的方案数是 ,这个 就是互异拆分数。
如何计算呢,考虑 Ferrers 图。

然后我们发现,对于互异拆分数,我们的列数即选的数的个数只有 级别,然后以及还有性质,每一行单调不增,且相邻两者变化量不超过 ,即最大的行大小往下每种行大小都存在。
然后我们就可以考虑每一种大小的行有多少种,对这个东西做一个类似完全背包的东西即可,复杂度 。
然后我们考虑知道了 如何计算 ,依然考虑容斥,用能凑出 的总方案数,减去之前就已经不满足条件过的方案数,即:
然后我们考虑 是什么,很明显就是在 中,凑出 的方案数。
我们设后面减去的一坨为 ,首先我们考虑 的不同,我们的初值从 ,变成了 ,以及我们假设选了 个数,后者得到的权值加上了 ,也就是赋值初值的部分变成了 。
不难知道 还是 级别的,然后我们可以背包转移,但是现在的问题变成了我们背包转移的时候实际上同时转移了 的每个 ,但是我们要算出 后才能给 赋初值,然后就寄了。
一个观察是由于 ,所以我们可以每次先算出 中的每个 ,然后来计算 中的每个 。
复杂度是 的。
放个代码:
Code
##include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5+5; int lim,n,H,p2[N],t[N],f[N]; inline void add(int &a,int b){if((a+=b)>=H) a-=H;} inline void solve(int n){ if(n<=1) return; solve(n>>1),lim=sqrt(2*n); for(int i=0;i<=n;++i) t[i]=0; for(int i=lim;i;--i){ for(int j=n;j>=i;--j) t[j]=t[j-i]; for(int j=0;j+(j+2)*i<=n;++j) add(t[j+(j+2)*i],f[j]); for(int j=i;j<=n;++j) add(t[j],t[j-i]); } for(int i=(n>>1)+1;i<=n;++i) add(f[i],H-t[i]); } signed main(){ cin>>n>>H,p2[0]=1; for(int i=1;i<N;++i) p2[i]=p2[i-1]*2%H; lim=sqrt(2*n),f[0]=1; for(int i=lim;i;--i){ for(int j=n;j>i;--j) f[j]=f[j-i]; for(int j=i;j<=n;++j) add(f[j],f[j-i]); } solve(n); int ans=p2[n]; for(int i=1;i<=n;++i) ans+=H-f[i-1]*p2[n-i]%H; cout<<ans%H<<"\n"; }
AT_agc029_f [AGC029F] Construction of a tree
考察无解条件,如果对于 的一个子集 ,能连通的所有点大小 就寄了,因为一定有环,所以 。
我们先找一个二分图最大匹配,如果不是 无解。
然后找到没有匹配的点 ,对于他找到他连着另外一段没有访问过的边 及其匹配的点 ,连接 。
如何证明这个不会把有解变成无解:直接考虑我们右边没有遍历到的集合 ,由于我们遍历到的点数比边数大 ,而且 连接的点 也一定没有遍历过,所以 ,所以本身就是无解的。
然后就做完了。
AT_agc024_f [AGC024F] Simple Subsequence Problem
建出子序列自动机,然后构造的状态 是已经匹配了 在自动机上还剩下 ,由于 ,所以状态数是 ,写法需要精细实现。
放个贺的代码:
Code
#include<bits/stdc++.h> using namespace std; const int N=21; int n,K,ans,hb[1<<N],tr[1<<N][2],f[N][1<<N]; char c; signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>K; for(int i=0;i<=n;++i) for(int x=0;x<(1<<i);++x) cin>>c,f[i][(1<<i)|x]=c-'0'; hb[0]=-1; for(int S=1;S<(2<<n);++S) hb[S]=hb[S>>1]+1; for(int S=2;S<(2<<n);++S){ if(S>>(hb[S]-1)&1) tr[S][0]=tr[tr[S][1]=S^(1<<hb[S])][0]; else tr[S][1]=tr[tr[S][0]=S^(3<<(hb[S]-1))][1]; } for(int i=n;i;--i) for(int A=1<<i;A<(2<<n);++A) if(f[i][A]){ int _=A>>i,B=(A&((1<<i)-1))|1<<i; if(tr[B][0]) f[hb[tr[B][0]]][((_<<1)<<hb[tr[B][0]])^tr[B][0]^(1<<hb[tr[B][0]])]+=f[i][A]; if(tr[B][1]) f[hb[tr[B][1]]][((_<<1|1)<<hb[tr[B][1]])^tr[B][1]^(1<<hb[tr[B][1]])]+=f[i][A]; f[0][_]+=f[i][A]; } ans=1; for(int S=2;S<(2<<n);++S) if(f[0][S]>=K&&hb[S]>hb[ans]) ans=S; int j=n; while(~ans>>j&1) --j; while(j--) cout<<(ans>>j&1); }
CF794G Replace All
*3400 uob 直接切了,有点牛了。
首先考虑没有 ? 的情况。
如果 A=B,则答案就是 。
否则我们考虑去掉前缀相同的 ,我们现在两个串分别是 开头,设 ,则我们可以令 ,然后两者可以去掉相同的前缀 ,然后如果 B 后面还是 B,则我们会 再次减去一个 ,否则我们不难知道 是 的前缀,然后就是若干个 的循环,我们发现这个过程类似辗转相除,最后我们得到的长度就是 。
我们设上下差了 个 , 个 ,则 。
- ,枚举 ,答案是 。
- ,先约分成 ,所以 ,然后 不超过 ,答案是 。
然后我们设这个答案是 。
设本来差了 个 ,两个串分别有 个 ?则:
然后就做完了。
CF2003F Turtle and Three Sequences
似乎叫 color-coding,我记得 CSP2022 的时候就有老哥 T1 用这个方法过了,给了我巨大震撼。
由于有互不相同的限制,考虑对于每个点赋值一种 的颜色,然后我们状压选了哪些,做 次,树状数组优化,复杂度 。
考虑正确率,由于只要最后成为答案的 个数颜色不同就成功,所以一次的正确率是 ,正确率大概是 。
还有一种方法是不枚举子集,分配颜色的同时钦定顺序,则复杂度为 ,正确率变成 。
CF1951H Thanos Snap
考虑二分,然后我们设 表示在操作 子树前至少要提前操作多少次才合法。
首先是假设子树内有 个 ,。
然后就是 ,这三种情况。
CF1305F Kuroni and the Punishment
首先不难发现答案不超过 ,所以有 个点是 三种情况,随机 次,每次找到一个 ,找出 的质因数,扔进一个 set 里,最后对于 set 里面的每个数计算答案即可。
CF1951G Clacking Balls
停时板子题,首先考虑如何刻画这一坨,因为这个直接维护下标比较抽象,我们考虑维护相邻两个的位置差 ,然后由于 时删除不好计算,我们直接令 即可。
然后我们考虑计算这个,设当前有 个点:
由于 ,所以我们令 即可。
我们可以令 ,此时,此时 。
由于 ,所以 ,然后就做完了。
CF566C Logistical Questions
首先我们不难发现这玩意是凸的。
所以第一个做法就是每次找到代价最小的点,走过去。
这样复杂度是 的,考虑优化。
由于代价不好计算,但是我们只需要找到代价减少的那一个,我们考虑对于代价求导,算出每个节点对应的代价变化率,而这个变化率是可以简单判断的,而且计算变化率只需要考虑子树内部的点即可,此时走一步就是 的了。
然后我们要减少步数,点分治即可。
CF1221G Graph And Numbers
容斥,然后有一个独立集计数的部分,暴搜+记忆化是 的。
CF325E The Red Button
神秘 *2800 的构造题,hanghang 直接 2 min 切了。
首先考察对于 为奇数的情况,我们不难知道 入点为 , 入点为 ,显然寄了。
然后对于 为偶数的情况,我们可以知道 和 的出点情况是相同的,都是 ,且 的入点只有 ,我们设这四个点是 。
那么我们只有 和 两种情况。
然后我们随意找一种方法连边,则会连出若干有向环,然后有一个结论
- 如果存在 个环,则一定存在一个组合分开在两个环内。
反证法。若所有组合在同一环里,则组合为 对应的 也在同一个环里,进一步得到任意 其与 在同一个环里,由二进制拆分,我们可以从 开始走到任意点,所以所有点在同一个环里,矛盾,原命题成立。
CF582D Number of Binominal Coefficients
考虑库莫尔定理, 恰好为在 进制下进位的次数,然后简单从高到滴数位 dp 即可, 表示考虑到第 位,进位 次,是否达到上界,下一位是否给这一位进位。
CF1569F Palindromic Hamiltonian Path
暴搜出匹配长什么样,这个是 ,然后枚举全排列判断行不行,暴搜每个串的形态,这个是贝尔数。
CF193D Two Segments
从值域上考虑,计算对于对于当前的 ,每个 形成的连续段数量,线段树维护。
CF1625E2 Cats on the Upgrade (hard version)
建出括号序列树,然后就简单 DS 维护了。
CF2021E3 Digital Village (Extreme Version)
首先建出 Kruskal 重构树,然后我们考虑对于已经在若干个点建出了服务器后的代价,首先把每个服务器及其祖先的 置为 ,然后对于每个关键点找到最深的 为 的祖先 ,则答案就是 。
这个最深的限制不香,我们拆成 ,则此时设每个点内部的关键点个数是 ,我们知道代价是 。
然后我们每次找到代价最小的一个叶子即可。
更好的做法是建立 Kruskal 重构树的时候直接维护代价最小的叶子,然后建出一个树形结构,然后每次堆找到最小代价的即可。
LOJ547 「LibreOJ β Round #7」匹配字符串
首先 dp 是简单的,设 表示考虑前 个位置合法 为 的方案数。
然后我们做一下前缀和,方程变成:
首先如果 比较小,我们直接启动线性递推,。
否则我们考虑这个东西的组合意义:
要么从 走到 ,权值乘上 ,也可以 走到 ,权值乘上 。
然后枚举走了多少 ,则:
这个是 的,拼起来就是 的,但是模数 ,所以似乎 NTT 长度不能超过 .
11.11-11.17
CF798E Mike and code of a permutation
不难发现我们根据这个关系可以建出一个 DAG,然后优化建图是简单的。
CF436E Cardboard Box
经典反悔贪心题目,我们考虑我们的增加一颗星的决策有哪些。
- 增加一个 。
- 在 状态为 的情况下选择一个 。
- 在 状态为 的时候变成 ,代价为
- 在 状态为 时变成 ,代价为
然后用五个堆维护对应方案即可。
Code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=3e5+5; int n,k,a[N],b[N],ans,vis[N]; struct Node{int val,id;}; inline bool operator<(const Node a,const Node b){return a.val>b.val;} struct queue{ int op; priority_queue<Node>Q; inline void up(){while(Q.size()&&vis[Q.top().id]!=op) Q.pop();} inline int size(){return up(),Q.size();} inline Node top(){return up(),Q.top();} inline void push(Node x){Q.push(x);} }q1,q2,q3,q4,q5; /* vis=0 ax vis=0 bx vis=1 bx-ax vis=1 -ax vis=2 -(bx-ax) */ inline void push0(int x){ vis[x]=0,q1.push({a[x],x}),q2.push({b[x],x}); } inline void push1(int x){ vis[x]=1,q3.push({b[x]-a[x],x}),q4.push({-a[x],x}); } inline void push2(int x){ vis[x]=2,q5.push({-(b[x]-a[x]),x}); } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>k; for(int i=1;i<=n;++i) cin>>a[i]>>b[i]; q1.op=q2.op=0,q3.op=q4.op=1,q5.op=2; for(int i=1;i<=n;++i) push0(i); for(int i=1;i<=k;++i){ int res=1e9+7,op=0,p=0,q=0; if(q1.size()&&q1.top().val<res) res=q1.top().val,p=q1.top().id,op=1; if(q3.size()&&q3.top().val<res) res=q3.top().val,p=q3.top().id,op=2; if(q2.size()&&q4.size()&&(q2.top().val+q4.top().val)<res) res=q2.top().val+q4.top().val,p=q2.top().id,q=q4.top().id,op=3; if(q2.size()&&q5.size()&&(q2.top().val+q5.top().val)<res) res=q2.top().val+q5.top().val,p=q2.top().id,q=q5.top().id,op=4; ans+=res; if(op==1) push1(p); if(op==2) push2(p); if(op==3) push2(p),push0(q); if(op==4) push2(p),push1(q); } cout<<ans<<"\n"; for(int i=1;i<=n;++i) cout<<vis[i]; }
CF1474E What Is It?
简单构造题,首先画出 ,不难发现每次是增加 ,然后删除 ,所以不难构造出 。
这样答案是上界,注意特判 。
CF1540D Inverse Inversions
首先不难知道如果我们设 ,则表示 在前 个数中的相对大小,所以我们初始设 ,然后扫描 ,如果 就让 。
我们不难知道这个东西可以合并,然后一个长度为 len 的区间合并起来是一个大小为 的分段函数,然后我们就可以直接分块维护即可。
我用了种奇怪的写法,复杂度 。
CF1827C Palindrome Partition
首先我们维护 表示以 为结尾的好的串的个数,然后考虑如果满足 是一个偶回文子串且 最大,不难知道 。
然后我们只需要对于每一个 找出一个 即可,直接找到 可能不好找,我们可以先找到回文中心,然后再给他维护到的区间取 即可。
CF2025G Variable Damage
首先不难知道答案是:
所以一定是从大到小排序后依次配对,先算上前面的 ,我们只计算后面的 部分。
如果把人看成 ,装备看成 。
我们考察一个人的贡献什么时候能被用上,不难知道此时前缀和一定 。
我们考察一个装备的贡献什么时候能被用上,不难知道此时前缀和一定 。
然后我们考虑这个区间怎么合并,不难发现一个区间的取值还是 段的,然后分块维护即可。
。
P8497 [NOI2022] 移除石子
首先我们考虑对于一个确定的 怎么做,我们首先找点性质:
- 不会存在长度超过 的区间,否则我们可以拆成若干个 +一个长度不超过 的区间。
- 每种相同的区间不会超过一个,否则我们可以直接换成 操作。
然后恰好非常难受,我们考虑转成至多,我们考虑中间有哪些不同之处。
的时候,讨论不加入时便有解的情况:
- 出现 操作,把这个石子扔到一操作内即可。
- 有长度 的二操作,把边上的去掉,加入石子,加入一操作即可。
- 有二操作且 ,拓展此操作。
时分类讨论:
- 有解,用一次一操作删了剩下的。
- 无解,由于不可能加入 个石子后从有解变成无解,所以一定无解。
综上,只有不存在操作即全是 或者 时不行,特判即可。
现在我们只需要计算最少需要的石子个数即可,考虑 dp:
设 表示考虑 ,考虑了 个至少到 , 个至少到 ,转移时枚举 个从 开始,然后转移代价是好计算的,这样我们就可以得到 的暴力 dp:
Code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e3+5,H=1e9+7; int T,n,K,l[N],r[N],ans; inline void cmin(int &a,int b){a=min(a,b);} namespace Sub{ int a[N],f[N][7][4]; inline int solve(){ if(K==1){ if(accumulate(a+1,a+n+1,0)==0) return 0; if(n==3&&a[1]==1&&a[2]==1&&a[3]==1) return 0; } memset(f,0x3f,sizeof f),f[1][0][0]=0; for(int i=1;i<=n;++i) for(int j=0;j<=6;++j) for(int k=0;k<=3;++k) if(f[i][j][k]<=K){ for(int l=0;l<=3;++l){ int u=a[i]-j-k-l,v=u<0?-u:u==1?1:0; for(int p=k;p<=j+k&&p<=6;++p) cmin(f[i+1][p][l],f[i][j][k]+v); } } return f[n+1][0][0]<=K; } } inline void dfs(int x){ if(x>n) return ans+=Sub::solve(),void(); for(int i=l[x];i<=r[x];++i) Sub::a[x]=i,dfs(x+1); } inline void solve(){ cin>>n>>K,ans=0; for(int i=1;i<=n;++i) cin>>l[i]>>r[i]; dfs(1),cout<<ans<<"\n"; } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>T; while(T--) solve(); }
考虑怎么做到更好,考虑 dp 套 dp,然后暴搜出来状态只有 个,然后就做完了。
Code
#include<bits/stdc++.h> #define int long long #define Vec vector<int> using namespace std; const int N=1e3+5,M=1e4+5,H=1e9+7; int T,n,K,l[N],r[N],ans; int trans[M][9],f[2][M],tot; map<Vec,int>mp; inline void cmin(int &a,int b){a=min(a,b);} inline void add(int &a,int b){if((a+=b)>=H) a-=H;} inline int dfs(Vec a){ if(mp.find(a)!=mp.end()) return mp[a]; int id=mp[a]=++tot; for(int t=0;t<=8;++t){ vector<int>cur(9,101); for(int j=0;j<3;++j) for(int k=0;k<3;++k) if(a[j*3+k]<=100) for(int l=0;l<3;++l){ int u=t-j-k-l,v=u<0?-u:u==1?1:0; for(int p=k;p<=j+k&&p<3;++p) cmin(cur[p*3+l],a[j*3+k]+v); } trans[id][t]=dfs(cur); } return id; } inline int delta(){ if(K==1) return (count(l+1,l+n+1,0)==n)+(n==3&&l[1]<=1&&l[2]<=1&&l[3]<=1&&r[1]>=1&&r[2]>=1&&r[3]>=1); return 0; } inline void solve(){ cin>>n>>K; for(int i=1;i<=n;++i) cin>>l[i]>>r[i]; ans=(H-delta())%H,memset(f[1],0,sizeof f[1]),f[1][1]=1; for(int i=1;i<=n;++i){ memcpy(f[0],f[1],sizeof f[0]),memset(f[1],0,sizeof f[1]); for(int t=0;t<=8;++t){ int val=0; if(t<8) val=(t>=l[i]&&t<=r[i]); else if(r[i]>=8) val=r[i]-max(8ll,l[i])+1; if(val) for(int j=1;j<=tot;++j) if(f[0][j]) add(f[1][trans[j][t]],f[0][j]*val%H); } } for(auto i:mp) if(i.first[0]<=K) add(ans,f[1][i.second]); cout<<ans<<"\n"; } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); dfs((Vec){0,101,101,101,101,101,101,101,101}); cin>>T; while(T--) solve(); }
P4484 [BJWC2018] 最长上升子序列
考虑双杨表和排列的双射,然后考虑最长上升子序列的长度就是杨表第一行的长度,我们暴搜杨表形态,这个是拆分数的,然后用钩长公式算出杨表的方案数,最后求和即可。
复杂度 。
P3774 [CTSC2017] 最长上升子序列
由于 Dilworth 定理,我们考虑如果不存在长度为 的上升子序列,则我们的小大就是前 列的大小和。
然后我们可以暴力维护杨表了,复杂度 ,可以通过 。
我们考虑如果一个 存在,则必须满足 ,所以 里必须有一个 ,所以实际上我们维护一个普通杨表和一个转置杨表的前 行,即可得到所有的信息。
考虑如何计算答案。
如果 不超过 ,直接统计转置杨表的前 行。
否则我们先统计杨表前 行,然后剩下的都在转移杨表的前 行内,直接统计即可。
AT_agc018_c [AGC018C] Coins
同样反悔贪心,找到 种反悔决策即可。
P9870 [NOIP2023] 双序列拓展
暴力 dp 是简单的,然后我们可以把这个过程变成在二维平面上走,然后我们直接考虑一下最值的位置,然后分治求解即可。
P7116 [NOIP2020] 微信步数
把计算的东西变成每一个时刻还有多少点存在。
首先暴力算出第一组的答案,然后每一组被删除多少点可以算出来,发现每一个维度相乘是一个 次多项式,线性拉插即可。
牛顿插值忘完了怎么办,爆,重新推了一遍:
P10304 [THUWC 2020] 道路修建
对于一个点 ,记录一个 表示如果删除的祖先深度 则存在 条 的路径,知道 后求解答案是简单的,考虑如何求解 。
首先找到 dfs 生成树后,按逆拓扑序维护每条非树边,然后对于一条 ,拿出 上的最小值来更新 。
P8819 [CSP-S 2022] 星战
经典哈希题。
CF1824D LuoTianyi and the Function
简单扫描线+历史版本和。
首先我们把一条路径的代价变成 ,这样的话,对于两种情况,收益都是 。
然后我们枚举 ,则收益变成了:
然后这个就是简单的树上直径维护了。
所以我们对于每个 路径,在 上添加,在 路径的倒数第二个点删除,然后线段树合并维护即可。
CF1137F Matches Are Not a Child's Play
胡了一个巨大复杂的做法,看起来代码不会少于 ,然后观察题解有简单做法。
首先我们以最大值为根,因为他一定是最后一个被删除的,维护每个点子树内的最大值 ,考虑询问 的时候答案是怎样的。
我们首先考虑如果存在 满足 ,那么 一定会在 之前删除,,则 一定会在 后删除,剩下是 的情况,不难发现此时比 先删除的就是 这一条路径上的点,然后就做完了。
然后链推平可以 ODT 或者 LCT 即可 + BIT 维护即可。
我胡的做法是考虑以询问的 为根,然后考察最大值的位置,找到删除了最大值所在子树的最大值,然后首先除了最大值所在的子树都会被选,然后最大值的子树内相当于如果子树最大值小于某个数就会被选,如果最大值所在子树在本来以 为根时 的子树内,这个是好计算的。
否则我们考虑除了 的链,剩下的都是本来就是子树的可以直接维护, 的链有单调性,可以二分维护。
然后巨大难写,无法可想。
实现了题解做法:
Code
#include<bits/stdc++.h> #define pb push_back using namespace std; const int N=4e5+5; int n,q,cnt,pos[N],a[N],mx[N]; int dfn[N],dep[N],f[N],sz[N],son[N],top[N],dn; vector<int>G[N]; struct node{ int l,r,v; inline node(int _l=0,int _r=0,int _v=0){l=_l,r=_r,v=_v;} inline bool operator<(const node a)const{ return l<a.l; } }; set<node>S; inline void dfs1(int x,int fa){ sz[x]=1,dep[x]=dep[f[x]=fa]+1,mx[x]=x; for(auto v:G[x]) if(v!=fa) dfs1(v,x),sz[x]+=sz[v],mx[x]=max(mx[x],mx[v]), son[x]=sz[v]>sz[son[x]]?v:son[x]; } inline void dfs2(int x,int tp){ top[x]=tp,dfn[x]=++dn; if(son[x]) dfs2(son[x],tp); for(auto v:G[x]) if(v!=f[x]&&v!=son[x]) dfs2(v,v); } inline int lca(int u,int v){ while(top[u]!=top[v]) if(dep[top[u]]>=dep[top[v]]) u=f[top[u]]; else v=f[top[v]]; return dep[u]<dep[v]?u:v; } inline int dist(int a,int b){return dep[a]+dep[b]-2*dep[lca(a,b)];} namespace BIT{ int c[N]; inline void add(int x,int v){ for(;x<=n+q;x+=x&-x) c[x]+=v; } inline int ask(int x){ int ans=0; for(;x;x-=x&-x) ans+=c[x]; return ans; } } #define iter set<node>::iterator inline iter split(int x){ auto it=S.lower_bound(node(x)); if(it->l==x) return it; node now=*(--it); S.erase(it),S.insert(node(now.l,x-1,now.v)); return S.insert(node(x,now.r,now.v)).first; } inline void assign(int l,int r,int v){ auto ed=split(r+1),st=split(l); for(auto it=st;it!=ed;++it) BIT::add(it->v,-(it->r-it->l+1)); S.erase(st,ed),S.insert(node(l,r,v)),BIT::add(v,r-l+1); } inline void upd(int x,int y,int val){ while(top[x]!=top[y]) if(dep[top[x]]>=dep[top[y]]) assign(dfn[top[x]],dfn[x],val),x=f[top[x]]; else assign(dfn[top[y]],dfn[y],val),y=f[top[y]]; if(dep[x]<=dep[y]) assign(dfn[x],dfn[y],val); else assign(dfn[y],dfn[x],val); } inline void change(int x){ pos[a[x]=++cnt]=x,upd(pos[cnt-1],x,cnt-1),assign(dfn[x],dfn[x],cnt); } inline int ask(int x){ auto it=--S.upper_bound(node(dfn[x])); return BIT::ask((it->v)-1)+dist(pos[it->v],x)+1; } char opt[20]; signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>q; for(int i=1;i<=n;++i) a[i]=pos[i]=i; for(int u,v,i=1;i<n;++i) cin>>u>>v,G[u].pb(v),G[v].pb(u); dfs1(n,0),dfs2(n,n),S.insert(node(n+1,n+1,0)),cnt=n; for(int i=1;i<=n;++i) BIT::add(mx[i],1),S.insert(node(dfn[i],dfn[i],mx[i])); for(int i=1,u,v;i<=q;++i){ cin>>opt>>u; if(opt[0]=='u') change(u); else if(opt[0]=='w') cout<<ask(u)<<"\n"; else cin>>v,cout<<(ask(u)<ask(v)?u:v)<<"\n"; } }
CF799F Beautiful fountains rows
糊了个跟 std 相同的扫描线做法,然后一看题解全是哈希,震撼人心。
你对于每一行,把这一行的 标成 ,然后我们得到:
- 满足条件等价于 里面的 为偶数。
然后我们直接哈希即可。
CF1286D LCC
感觉,水平不如 7 月份了。
首先只有 种可能成为答案,然后从小到大扫描答案,然后我们考虑 dp 出概率,不难发现我们每次就是 ban 掉一个形如 的情况,这个我们可以 ban 完直接 ddp 即可。
11.18-11.24
AT_arc187_d [ARC187D] Many Easy Optimizations
考察枚举最小值之后怎么维护?贪心选后继即可。
我们用线段树同时维护 个最小值即可。
AT_agc016_d [AGC016D] XOR Replace
不难发现就是拿异或和与一个点交换,然后贪心换换即可。
AT_pakencamp_2018_day2_g グランド・グラフ (Grand Graph)
首先我们知道 ,所以我们可以考虑广义串并联图,给图缩完之后我们发现此时点数很小,然后我们可以暴力 Bell 数划分或者直接状压 dp 即可。
P5295 [北京省选集训2019] 图的难题
Nash-William 定理:一个图可以划分成 个森林的条件是,对于所有导出子图,满足 ,即 。
所以我们只需要求 的最大值即可,可以使用最大权闭合子图完成。
但是不难发现,我们 都是空集的时候,此时的权值已经不满足条件,所以我们每次需要强行钦定一个点必须选,然后退流即可。
P11197 [COTS 2021] 赛狗游戏 Tiket
首先设 在三个序列中的下标分别是 ,那么实际上我们就计数有多少个 满足:
首先可以三维偏序,但是有单 做法。
考虑计算出三种情况的二维偏序个数。
不难发现此时每个三维偏序被算了 次,二位偏序被算了 次。
而三维偏序数量等于零维偏序数量,二维数量等于一维数量,而总数是 。
所以我们可以知道三维偏序和二维偏序的数量和为 ,用三种情况的和减去这个再除以 就得到答案。
P5100 [JOI 2017 Final] 足球
不难知道每次球转走之后都是本来最近的球员来接球。
然后简单最短路题。
P7407 [JOI 2021 Final] ロボット (Robot)
比较困难的最短路题。
首先我们不难知道,因为颜色有 种,所以我们每次改变颜色可以变成一个从来没有出现过的,这样不会对后面造成影响。
我们考虑在某个点有什么决策,改变这个点同一颜色的其他出边,或者改变这一条边。
但是一个问题是如果我们有 ,但是这两条边都是颜色 且都是改变这条边的情况下,不难发现我们只需要一条边的代价即可,而此时就寄了。
对于这种情况,我们建立虚点 ,然后让 连边,边权是 ,然后 权值分别是改变其他的边的代价。
不难发现此时我们就省略掉了一条边的代价,完全胜利。
P7669 [JOI2018] Commuter Pass
首先建出最短路树,然后在上面 dp 一下即可。
P3204 [HNOI2010] 公交线路
首先不难想到一个状压 dp,然后我们发现这个状态数只有 ,然后我们矩阵快速幂优化 dp 即可。
P3502 [POI2010] CHO-Hamsters
首先我们可以先写一个 dp, 表示此时出现了 次以第 个字符串结尾的最短长度,这个转移系数可以 hash 预处理出来, 。
然后发现这个是 卷积,矩阵快速幂即可。
P11291 【MX-S6-T3】「KDOI-11」简单的字符串问题 2
困难题目,不想写了。
P11292 【MX-S6-T4】「KDOI-11」彩灯晚会
感觉比 T3 简单,首先我们不难知道 可以换成选在所有里面选两个的方案数。
然后我们考虑每两条链被计算多少次,考虑两个交集大小为 ,则方案数为 ,所以我们只需要对于每个 ,计算交集大小为 的时候的选的方案数。
刚好 不好做,然后我们考虑钦定,然后做二项式反演。
然后我们就可以 dp 了,设 表示在 DAG 上 dp 到了 ,两条链长度为 ,交集大小为 的方案数。
然后我们枚举下一个点 ,长度 ,转移到 。
需要预处理一个 表示从 ,长度为 的路径数量,这个是 的,然后 dp 是 的。
有点小寄,不难发现 由于和贡献系数无关,所以可以分开枚举,这样我们就做到了 。
无法通过,这个时候这个题最神秘的一点出现了:
考虑最后如何统计答案:
所以每次加入一个点相当于系数乘上一个 ,这样我们 dp 时就可不用维护 的数量,复杂度变成 ,可以通过。
CF1672G Cross Xor
首先我们考虑如何 check 一个矩阵合法。
首先我们可以操作 ,这样可以不改变其他点的时候翻转这四个点。
如果我们只通过这种方法,我们发现,我们可以不改变每行每列的奇偶性,如果都是偶数的话我们就可以用这个变法清空。
然后我们考虑单次操作带来的影响。
我们设每行的奇偶性是 ,每列的是 。
首先操作一个点 对于 的影响。
- 如果 是偶数,则 全体改变。
- 否则 除了 全体改变。
然后不难猜测如果 是偶数,则 必须全部相同,对于 同理。
然后我们考虑计算答案,如果都是偶数,则是
如果 是偶数,则枚举 都是啥,然后计算是简单的。
否则都是奇数的情况。
枚举 的权值,我们把 ? 的 连边,则我们一条边等价于可以把 取反,然后我们需要每个点权值都是 .
不难知道这个方案数是 。
然后就做完了。
CF2029G Balanced Problem
首先可以把序列长度减少到 ,然后 dp,设 表示考虑前 个点,前缀被操作 次,后缀操作 次的最大权值,然后转移简单。
我们把 定义改成离 还有多少,然后转移就很好了。
CF1801E Gasoline prices
一个唐氏做法就是线段树维护哈希。
实际上可以倍增并查集,然后合并次数只有 次,代码还比较短。
CF1976F Remove Bridges
似乎找一找形态,然后 dfs 一遍找到若干条最长链即可。
CF1320E Treeland and Viruses
建虚树,跑 Dijkstra
CF1942G Bessie and Cards
首先我们考虑计数不合法概率,然后我们把手中的牌数看成一个折线图,然后不难发现由于计算的是概率而不是方案数,所以 有多少张是无用的。
所以此时我们只会 ,然后我们枚举什么时候似的,方案数是一个折线容斥,然后在乘上后面部分选择的组合数和什么时候放特殊点的方案数。
最后除以总方案数就是不合法概率,然后就做完了。
CF930E Coins Exhibition
离散化后双指针优化 dp 即可。
CF568E Longest Increasing Subsequence
不是很难的 dp 题,你随便做做就好了。
CF461E Appleman and a Game
首先我们考虑二分答案,然后变成了如何 check mid 次操作得到的最小长度。
这个东西,不难发现我们每次新出现的子串确定是那么这个串的决策是确定的,然后我们直接 dp 即可。
CF1758F Decent Division
神秘构造,每次对于每个区间线段树上二分找到中间权值全都更大的下一个点即可,据称这种方法操作次数不会超过 。
CF283E Cow Tennis Tournament
首先由于完全图三元环数量为:
所以我们线段树维护每个数 的 即可。
CF875E Delivery Club
二分完倒序考虑合法区间在哪里。
CF616F Expensive Strings
建出广义 SAM,然后是板子题。
CF446C DZY Loves Fibonacci Numbers
,然后线段树维护。
CF1389F Bicolored Segments
首先有一个线段树优化 dp 做法。
另一个神秘做法是,考虑原图矛盾之间连边,然后二分图。
最大独立集=n-最大匹配,然后我们考虑求解这个。
然后可以经典按 排序后贪心。
CF772D Varying Kibibits
拆一拆式子,先高维后缀和一下,再差分一下即可。
CF763D Timofey and a flat tree
树哈希,不难发现从 只会改变 两个点哈希值,然后直接维护即可。
CF842E Nikita and game
首先树直径有两端,然后新增的时候考虑放到哪一端即可。
CF1181E2 A Story of One Country (Hard)
暴力是直接枚举行/列分裂。
正解是类似于启发式分裂的同时考虑四个方向从第几个开始分裂,复杂度 。
CF1400F xrime Substrings
首先 shaber 状压是 的,然后我们可以暴搜出有哪些串不能出现,然后拿去建 ACAM,一跑发现节点数只有不超过 个,然后直接 dp 就做完了。
CF521E Cycling City
不难发现如果有两个环边有交就赢了。
然后直接上 dfs 树,然后直接树上覆盖一下即可。
CF1215F Radio Stations
首先不难知道设 个点表示第 个点选/不选。
然后对于交的问题,我们设 表示是否 ,然后我们也可以刻画出一个限制。
然后跑 2-sat 即可。
本文作者:Nityacke
本文链接:https://www.cnblogs.com/Nityacke/p/18535974
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步