2021 简思短解

abc238_e

一道简单的题被我做成不简单的。

考场上由于这是 E 题,所以高估了这道题的难度,直接往随机大整数和线性基的方向想了。

作未知序列 {a} 的前缀和 {s}。对于给定的 [l,r],即固定了 sl1sr 的差值。而整个序列的和就是 sns0,所以利用并查集判断 s0sn 的差是否固定即可(是否在同一集合内)。

P7518 [省选联考 2021 A/B 卷] 宝石

关键条件:P1,P2,,Pc 互不相等。

Part1:Up

DFS 同时存祖先中每种宝石出现的位置(按 deep 排序),对于每一个树上的节点 x,记录 向上第一个碰到的颜色为 {P}下一个的宝石的节点编号 和 向上第一个碰到的颜色为 {P}上一个的宝石的节点编号 为 Ux,Dx(对于该节点的宝石在 {P} 中的情况),和向上第一个在 {P} 中的宝石位置 Tx(对于该节点的宝石不在 {P} 中的情况)。

U,D 倍增处理。

离线询问,对于每一个询问,先 O(logn) 求出 LCA,再 O(logc) 贪心+倍增求出上行路径的宝石收集数。

Part2:Down

对于每个询问 xylca(x,y)=z),在 y 节点上挂一个 struct (lst,id,top) 分别表示上行已经收集的宝石数,询问的编号,z 的位置。

仍然 DFS 同时存祖先中每种宝石出现的位置,当到一个节点时,遍历挂着的 structs,分别用二分+倍增 O(log2c) 求出答案。

具体地,二分收集宝石数,找到祖先中结尾的宝石出现的最后位置,向上用 D 倍增至 z,判断是否能与上行的接上即可。

时间 O(n(logn+logc)+qlog2c),不过有单 log 做法。

这必须放一下代码

P3216 [HNOI2011]数学作业

不让下载数据且超级难调的题目是屑。

由于不能 O(n),我们想到 O(logn)

那肯定是快速幂啊,设 fn=Concatenate(n)%m 考虑:

[10k11011001][fn1n11]=[fnn1]

k 为新加入的 n 的十进制位数。

这里矩阵乘法指带模乘法。

对于每一种 k(最多 logn 种),分别做快速幂即可。

时间 O(33log2n)

评测

382. K取方格数/275. 传纸条/P1006 [NOIP2008 提高组] 传纸条/P3381 【模板】最小费用最大流

最小费用最大流 MCMF 集合!

专题讲解

提交记录及本人精美代码+注释:

K取方格数

传纸条

【模板】最小费用最大流

AT3955 [AGC023D] Go Home/agc023_d

小粉兔的 Sol

P2150 [NOI2015] 寿司晚宴

给定 ={2,3,,n}(n500),求有几种方案取两个子集 S,T,使得 xS,yT,gcd(x,y)=1

发现 n30 时可以用状压 DP,其实 n500 也行。

由于 n 最多一个 >n 的素因数,我们存一下这个因数,没有设为 1,将这些数按照这个因数升序排序,这个因数相同的每一段或者这个因数为 1 的单个元素用状压 DP,再将这些结果汇入总 DP 中,具体见代码

P3224 [HNOI2012]永无乡/1063. 永无乡

并查集 + 权值线段树合并。

P4178 Tree/252. 树

给定一棵带边权的树(n104),求长度不超过 k路径有多少条。

点分治模板题

定根 rt,树上的路径只有两种情况:

  • 经过或端点是 rt

  • 只在 rt 的子树内。

对于第一类,DFS 整棵树,求出每一个点到根的距离,容斥即可,O(nlogn)瓶颈在排序

处理完第一类路径后可以在子树里递归求解

那不铁定超时吗?不然。

我们每次选择重心定根即可,由于这样子问题规模都减半,总时间 O(nlog2n)

//Said no more counting dollars. We'll be counting stars.
#include<bits/stdc++.h>
using namespace std;
#define For(i,j,k) for(register int i=j;i<=k;i++)
#define cmx(a,b) a=max(a,b)
#define gc getchar
#define pc putchar
inline int read(){
int x=0;char c=gc();bool f=0;
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<3)+(x<<1)+c-48;c=gc();}
if(f)x=-x;
return x;
}
inline void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
#define N 40010
#define All(i,s) for(int i=head[(s)];i;i=e[i].nxt)
struct edge{int to,val,nxt;}e[2*N];//前向星
int tot,head[N];
bool vis[N];//是否当过根(重心)
int n,k,ans;
inline void adde(int x,int y,int z){e[++tot]={y,z,head[x]};head[x]=tot;}
int sum,root,sz[N],wn[N];//gtrt 时树的大小,当前最优重心,子树大小,最大儿子大小
inline void gtrt(int rt,int fa){//求重心
sz[rt]=1;wn[rt]=0;
All(i,rt){
int to=e[i].to;
if(to==fa || vis[to]) continue;
gtrt(to,rt);
sz[rt]+=sz[to];
cmx(wn[rt],sz[to]);
}
cmx(wn[rt],sum-sz[rt]);
if(wn[rt]<wn[root]) root=rt;
}
int dt[N],dtt;
int dis[N];//到根的距离
inline void gtds(int rt,int fa){
dt[++dtt]=dis[rt];
All(i,rt){
int to=e[i].to;
if(to==fa || vis[to]) continue;
dis[to]=dis[rt]+e[i].val;
gtds(to,rt);
}
}
int calc(int rt,int len){//计算
dtt=0;
dis[rt]=len;
gtds(rt,0);
sort(dt+1,dt+1+dtt);
int r=dtt,res=0;
For(l,1,dtt){
while(l<r && dt[l]+dt[r]>k) r--;
if(l>=r) break;
res+=r-l;
}
return res;
}
void solve(int rt){
vis[rt]=1;
ans+=calc(rt,0);
All(i,rt){
int to=e[i].to;
if(vis[to]) continue;
ans-=calc(to,e[i].val);//减掉多算的部分
sum=sz[to];wn[root=0]=n;
gtrt(to,rt);
solve(root);//递归
}
}
signed main(){
n=read();
tot=1;
ans=0;
For(i,1,n) vis[i]=head[i]=0;
int x,y,z;
For(i,1,n-1){
x=read(),y=read(),z=read();
adde(x,y,z);
adde(y,x,z);
}
k=read();
wn[root=0]=sum=n;
gtrt(1,0);
solve(root);
write(ans);pc('\n');
return 0;
}

P4381 [IOI2008] Island/358. 岛屿

题意转化:给定带边权的基环树森林n106),求每一棵基环树的最长链的长度之和。

考虑单个基环树,最长链只有如下两种情况:

  • 在去掉基环的边后的森林中,O(n) 树形 DP 求直径取最大值即可。

  • 经过基环。

对于第二种情况,记录每一个基环 C 上的点 x,存所在子树中离她最远的点的距离 fx,则答案为(设基环上的点顺时针编号依次为 1,2,,|C|):

ans=maxi<j(fi+fj+disidisj+len,fi+fj+disjdisi)

其中 disx 表示环上 12x 的距离,len 表示环长。

gx=fx+disx,hx=fxdisx,则:

ans=maxi<j(gi+hj+len,hi+gj)

O(V(C)) 即可,最终 O(n)

377. 泥泞的区域

将每一个行极大泥泞块和列泥泞块看作点,单个泥泞 block 看作所在两个极大泥泞块之间的边,题目要求的最小木板数就是这个二分图的最小覆盖,即最大匹配O(N4)匈牙利算法 O(V×E)),以下为二分图最大匹配模板代码

//...head
bool vis[MAXN];//visited
int ma[MAXN];//女生匹配的男生,无对象则为 0
vector<int> e[MAXN];//graph
bool dfs(int rt){//帅哥 rt 找对象
for(int i:e[rt]){
if(vis[i]) continue;//试过了
vis[i]=1;//约会
if(ma[i]==0 || dfs(ma[i])){//女生小 i 本来就没有对象或者把对象绿了
ma[i]=rt; return true;//match
}
}
return false;//男生 rt 为单身狗
}
signed main(){
//...input
//...init
int ans=0;
memset(ma,0,sizeof ma);
For(i,1,men){//遍历男生
memset(vis,0,sizeof vis);//清除约会记忆
if(dfs(i)) ans++;//世上又多了一对
}
cout<<ans<<endl;
return 0;
}

348. 沙漠之王

最优比率生成树模板。

用 0/1 规划,假设答案为 x,设所有边 (i,j) 的边权为 cost(i,j)x×dis(i,j),用 Prim 判断最小生成树总长是否小于 0 二分即可。

注意这里为实数二分,我用的固定二分次数

arc132_d

转化好题。

给定两个 01s,t,两串 0 的个数都为 n1 的个数都为 m。两个等长的 01 串之间的距离 d(a,b) 定义为最小的邻项交换次数使得 s,t 可以互相变换。一个 01 串的价值 v(a) 为相邻两项值相同的个数(e.g. s=00111,v(s)=3,t=01010,v(t)=0)。求:

mind(s,x)+d(x,t)=d(s,t)v(x)

发现 0,1 分别的个数不随操作而改变,联想到(我考场上没想到)(n+mn) 和平面中从 (0,0) 走到 (n,m) 的方案。

是的,我们01 串转化成一条 (0,0)(n,m) 的折线

结论:d(s,x)+d(x,t)=d(s,t) 表示成路径后 x (被夹)在 s,t 的路径之间。

证明看官方题解,不再赘述。

于是有了贪心策略:

  1. 枚举 x 的起始方向(上或右)(即字符串的第一位 0/1)。

  2. 贪心地直走,“碰壁”转弯。

  3. 更新答案。

我们可以通过证明有不“碰壁”转弯的 x 路径,一定有转弯次数不比她少的 x 不“碰壁”转弯出现在其之后,来证明贪心是对的(证明看官方题解 qwq)。

官方题解

P4180 [BJWC2010]严格次小生成树

结论:必有一个严格次小生成树与最小生成树的对称差的边数为 2

先用 Kruscal 求出最小生成树,再枚举每一条非树边,加入树边,再在树边中删掉一条使得仍然是树,计算答案

然而这样是 TLE 的,我们考虑维护枚举的非树边两端在树上的路径中边权的最大值、严格次大值(因为题目要求严格次小生成树,所以要存严格次大),在之中断掉一条边使得新的树严格次于最小生成树,再在这些答案中取 min 即可

路径中边权的最大值、严格次大值可以用树上倍增 O(logn) 实现,总时间 O(nlogn)

202. 最幸运的数字

题意:求最小的正整数 N 使得 L|89(10N1),不存在则输出 0

变形:

9Lgcd(8,L)|10N1

M=9Lgcd(8,L),则:

10N1(modM)

如果 gcd(10,M)>1,则必然无解,输出 N=0

否则,由欧拉定理得到:

10k1(modM)k|φ(N)

我们用 O(n) 计算 φ(N)O(φ(N)) 枚举其因数O(logφ(N)) 快速幂判断是否合法,总时间 O(n+φ(N)logφ(N))

由于快速幂中模数最大 1010 级别,所以不要忘了龟速乘!不要忘了龟速乘!不要忘了龟速乘!

CF1606F Tree Queries

根号算法万岁!

答案一定非负,那么对于一个 km 上界是 nk 的,于是我们考虑根号分治。

考虑当 k<n 时,我们直接进行树上 DP,设 fi,j 表示 i 的子树里当 k=j 时的答案,那么转移的时候我们直接考虑当前子节点删不删,这样这部分复杂度就是 O(nn) 的。

然后考虑当 kn 的时候,一定有 mn,这样的话考虑树形背包,记 gi,j 表示 i 子树里删 j 个点的最大儿子数,这样进行树形背包,考虑每个儿子删不删,由于树形背包的复杂度是 O(nm) 的,所以这部分复杂度就是 O(nn)

不过如果直接这么做空间会萎,于是你把询问离线下来,分成两部分做就不会 MLE 了。

219. 剪纸游戏

模板博弈 SG 函数

对于一个状态:

  • 若分情况(即一场游戏走一条路),则为 mex

  • 若分子游戏(即所有路一起走),则为 xor

int f[N][N];//记忆化
int sg(int x,int y){
if(f[x][y]!=-1) return f[x][y];
unordered_set<int> S;
For(i,2,x-2) S.insert(sg(i,y)^sg(x-i,y));
For(i,2,y-2) S.insert(sg(x,i)^sg(x,y-i));
int pos=0;
while(S.find(pos)!=S.end()) pos++;
return f[x][y]=f[y][x]=pos;
}

CF1280D Miss Punyverse

显然先将 wb 作为点权,题意即为将这棵树划分为 m 个连通块,最大化点权和为正数的连通块个数

想到树形背包,状态 fi,j 表示以 i 为根的子树分成了 j+1 块,点权和为正数的连通块个数的最大值

但是这样转移不了,再定义一个 gi,j 表示 i 为根的子树中分成了 j+1 块,点 i 所在的连通块的点权和的最大值

特殊地,gi,j=0 可以表示不将根节点 i 的连通块与祖先连通,即闭关锁国,此时一共分成 j 块而不是 j+1

转移见代码

转移后再更新一下:是否从开放至闭关能获得更大结果,即可。

P1600 [NOIP2016 提高组] 天天爱跑步

小小紫题,耗我两天 qwq。

“LCA+桶+树上差分”

发现我们要处理树上的路径,必定要求 LCA,用倍增即可

如果一个观察员 P 能够准时看到 st 路线上的人在跑步,则满足任一

  1. W(P)+deep(P)=deep(s)Panc(s)Panc(lca(s,t))

  2. W(P)deep(P)=dis(s,t)deep(t)Panc(t)Panc(lca(s,t))

发现两部分的处理方式类似,我们着重讲第 1 种。

可以做树上差分。

将树建 DFS 序(每个点只有第一次才到 DFS 序里),将路径拆成底端加,顶端的 fa 减的差分形式,这样可以用桶扫一遍 DFS 序,每到一个点就相应的修改,用前缀和相减的方式即可求出 P 为根的子树内的操作的集合,P 的答案即为桶中下标为 W(P)+deep(P) 的值。

Code

P1054 [NOIP2005 提高组] 等价表达式

我写了两天 qwq。

题目就是求与给出的代数式恒等的有哪些(变量只有 a)。

想到评测机对提交者代码的做法,想到:

我们可以多代入一些 a 的值来 check 两个代数式的值是否相同。

接下来就是计算一个无变量的式子了,但在实操中发现对于负号 "-" 的特判极其难弄:

  • 不能以负号分裂求解 e.g.12+31(2+3)

  • 要将负号提出幂次不至于正负颠倒 e.g.34(3)4

我突发奇想:是否将 string 中的所有 "-" 替换成 "+b*"(如果在最左端改成 "b*")即可算出同样的效果?(b=1

答案是肯定的。

原因是负号的优先级与乘号相同……

这样做即可,细节看代码。

Code

P5110 块速递推

先计算得到通项公式

fn=a(bncn)%mod

其中

a=233230706,b=94153035,c=905847205,mod=109+7

发现 O(TlogN)T=5×107,N=1.8×1019 的数据下根本过不了。

优化:

由于 xyxy%(mod1)(modmod),将 N 降到 109 级别。

于是瞟一眼题目“块速递推”,想到分块加速幂运算

n=xB+y(B=65536x,y<B),以 b 为例,预处理出 bkBbk(0<k<B) 即可 O(1)bn=bxB×by

时间 O(T)

P1471 方差

建线段树,每个节点存区间的和及平方和。

avg=sumlen,s2=len×sossum2len2

区间修改,区间询问 打懒标记即可。

arc131_d

我可能永远不会猜结论 qwq

结论零

标枪的位置只取整数即可。

结论一

相邻的两个标枪一定相距正好 D

结论二

x=n+12 个标枪一定在 [0,D] 位置范围内。

于是有了想法:

ai(0iD) 表示 x1 个标枪分别插在 i,i+D,i+2D,,i+(x2)D 的位置时的分数。

bi(0iD) 表示 nx+1 个标枪分别插在 i,i+D,i+2D,,i+(nx)D 的位置时的分数。

最终答案即为 maxi=0D(ai+bDi)

问题就在于如何求 {a},{b}

以下以 {a} 为例。

考虑每一段相同分值的区间,计算每一个 i,这段对于 ai 的贡献,发现这些贡献只有可能为 yy+1(即相差不过 1,这算结论三吧 qwq),而且贡献为 y+1 的只是连续的一段(特别地,当同余类里包含了 0,要分成前后两段),用差分数组维护即可。

时间复杂度:O(m+D)

P2375 [NOI2014] 动物园

对于字符串 S(长度为 n106)的前 i 个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作 numi,求 {num}

KMP 求 nxt 数组,倍增跳即可。

时间 O(Tnlogn),T5,极其卡常,过不了。

但是,将倍增数组的两维交换(即代表 2k 那一维放前面),这样数组指针只会单次增减 O(1),实测能基本快一倍,能过。

P2486 [SDOI2011]染色

树剖,线段树维护区间连续颜色块数量和两端颜色。

P7950 [✗✓OI R1] 后方之水

通过生成函数计算得答案为:

(S+1n+1)(n2)

O(n) 计算即可。

P1450 [HAOI2008]硬币购物

先作完全背包,再用容斥原理减掉多算的。

P7883 平面最近点对(加强加强版)

期望 O(n) 的算法:

先将点 rand_shuffle 一下,保证随机,维护前缀点集的答案。

记前 i 个点的答案为 di

我们将全平面划分为边长为 di 的正方形网格。将前 i 个点丢到网格里,可以发现每个正方形中最多只有 3 个点。

对于新加入的第 i+1 个点,只需检查它所属的正方形周围 9 个正方形中的所有备选点。

如果 di+1di,则需要 O(i) 重构整个网格。但由于第 i 个点只有 1i 的概率更新答案,故总复杂度仍为 O(n)

需要使用哈希表存储每个方格中的点。

AcWing326

将二进制的每一位分开考虑期望。

fx 表示 xn 随机路径边权异或和的当前二进制位的期望(即为 1 的概率)。

fx=1degxyN(x)(fy[val(x,y)=0]+(1fy)[val(x,y)=1])

其中 N(x)x 的开领域,val(x,y)(x,y) 这条边当前二进制位的权值。

由于该式子有后效性,用高斯消元即可。

总共 O(32n3)

P4054

注意到值域只有 100,可以作 100 个数据结构来二维单点修改,矩阵查询。

而且 n×m 很小(否则就要用树套树或 CDQ 分治了 qwq),直接二维树状数组(本蒟蒻初学)维护每一种值即可。

时间 O(100nmlog(nm))

P4396

经典莫队的变形。

sol 1:

莫队区间移动时实时维护一个数组表示某个值在区间内出现的次数和是否出现。

再用树状数组维护它。

最终复杂度 O(nmlogn+mlogn)

跑得慢,不知道能不能过。

sol 2:

在 sol 1 的基础上将树状数组换成分块维护,实现 O(1) 修改,O(n) 查询。

最终复杂度 O(nm+mn),能过。

P3863

将区间修改和单点查询离线下来。

扫描线按序列 1n 顺序扫描。

如果碰到修改的左端点,则扫描线上该时刻及以后的时刻加上对应的值。

如果碰到修改的右端点,则扫描线上该时刻及以后的时刻减去对应的值。

如果碰到一个询问,则求扫描线上该时刻以前的时刻中询问值的 rank。

扫描线上用分块即可。

P2163

二维静态数点模板题

先对 y 坐标进行离散化。

对于每一个矩形询问,拆分成四个二维前缀和询问。

从左往右扫,维护一个树状数组存目前扫过的各个 y 上的点的个数。

当扫到一个二维前缀和询问 (x,y)x 时求树状数组中 pre(y) 的值即为该询问的答案。

x,y 两维都离散化了的屑程序

CF1582G

维护一个序列 Li 表示第 i 位为右端点的子串的最大左端点下标,可对每个素数用 vector 存当前还有哪些位置上的数是该素数的倍数且没有被抵消,一边更新一边扫当前 ai 的素因数求出 Li

再对 Li 开线段树,每次将 query(i,i)(1in) 累加得到答案。

query(x,y) 表示 LxLn 中最长前缀使得所有该前缀中的 Li 均不小于 y,该前缀的长度。

P2633

比 P3302 少了 Link 操作,稍加修改 P3302 的代码即可。

P3302

对每个点存该点到根节点的所有点权的可持久化值域线段树(点权离散化后)。

Link 时启发式合并。

Query 时四个值域线段树相加减搜索第 k 小即可。

P1972

对每个位置 x 存前一个颜色相同的位置 prex

顺序遍历数组,当前为 ax,维护树状数组,每次 add(x,1) 并且 add(prex,1)

当遍历到了某一个询问 [li,ri] 的右端点 rianssum(ri)sum(li1)

CF1446D2

猜结论:子串长度取最大值时其中一个众数必为全局众数。

根号分治即可,其中之一用尺取法。

巨佬的题解

P4592

对于 1 操作,用 DFS 序+可持久化 01trie 维护。

对于 2 操作,可持久化 01trie 维护到根节点的所有值。

P3834

离散化后维护数组的前缀值域可持久化线段树,每次按照对应的子树大小之差二分查找。

O((n+q)logn)

P7447

值域倍增分块。

奆佬的题解

CF438D

考虑 x%p 的情况:

{x%p=x,x<px%px2,xp

所以一个数的有意义取模(x%px)最多进行 logx 次。

线段树暴力(无懒标记)解决即可。

P4198

斜率预处理+线段树 O(mlog2n) 维护(单点修改后每层 push_upO(logn) 递归求解)。

CF1583E

先按度的奇偶来判断,奇点个数 cnt>2 则 NO 并输出 cnt2

否则建原图的任意生成树,求 q 组两点间路径记录途径点即可。

P2709

莫队模板

CF1598E

二维数组斜着开 set 记录位置,每次 upd 计算差值

posted @   ShaoJia  阅读(201)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示