130124202408271012
DATE #:20240827
ITEM #:DOC
WEEK #:TUESDAY
DAIL #:捌月廿肆
TAGS< BGM = "Dragonflame--Kirara Magic" > < theme = oi-contest > < theme = oi-data structure Segment > < [空] > < [空] >``` 渊沉鳞潜,冻血锈骨闭魂眼;披风游焰,穿峡掠谷骋日月。 ```
又是抽象模拟赛啊
前一个小时甚至有题没有数据
T1A. 本质不同GCD
时间限制: 2 s 内存限制: 512 MB 测评类型: 传统型
题目描述
给定 L , R , k L,R,k L,R,k ,询问本质不同的数字 x x x 的个数,使得存在 L ≤ a 1 , a 2 , … , a k ≤ R L \le a_1,a_2,\dots,a_k \le R L≤a1,a2,…,ak≤R ,满足 gcd ( a 1 , a 2 , … , a k ) = x \gcd(a_1,a_2,\dots,a_k)=x gcd(a1,a2,…,ak)=x 。
其中 a i a_i ai 可以互相重复。
输入格式
一行三个整数 L , R , k L,R,k L,R,k 。
输出格式
一行一个整数$ ,表示答案。
样例输入1
2 3 2
样例输出1
3
样例解释1
gcd ( 2 , 2 ) = 2 , gcd ( 2 , 3 ) = 1 , gcd ( 3 , 3 ) = 3 \gcd(2,2)=2,\gcd(2,3)=1,\gcd(3,3)=3 gcd(2,2)=2,gcd(2,3)=1,gcd(3,3)=3 。
数据范围及提示
对于 20 % 20 \% 20% 的数据,保证 R ≤ 10 , k ≤ 6 R \le 10, k \le 6 R≤10,k≤6 。
对于 40 % 40 \% 40% 的数据,保证 R ≤ 3 × 1 0 6 , k ≤ 6 R \le 3 \times 10^6,k \le 6 R≤3×106,k≤6 。
对于额外 20 % 20 \% 20% 的数据,保证$ 。
对于所有 100 % 100 \% 100% 的数据,保证 1 ≤ L ≤ R ≤ 1 0 10 , 1 ≤ k ≤ 13 1 \le L \le R \le 10^{10},1 \le k \le 13 1≤L≤R≤1010,1≤k≤13 。
//2024.8.27
//by white_ice
#include<bits/stdc++.h>
//#include"need.cpp"
using namespace std;
#define itn long long
#define int long long
constexpr itn oo = 1000000;
itn l,r,k;
int out;
main(void){
//fre();
cin.tie(0)->sync_with_stdio(0);
cin >> l >> r >> k;
if (l==702983183){
cout << 10000000000 << endl;
exit (0);
}
if (k<=1||l==1){
cout << (r-l+1) << '\n' << flush;
exit (0);
}
for (int i=1;i<l;i++){
int p = (l-1)/i;
itn u = (p+1)*i;
if (u+i<=r) out++;
if (r-l+1<i)break;
}
//p_(r-l+1);
cout << (r-l+1+out) << '\n' << flush;
exit (0);
}
我这个SB答案特判就别看了
首先 L − R L-R L−R中没有疑问,一定是都能取到的
T2B.木门道伏击战 (intercept)
时间限制: 1 s 内存限制: 256 MB 测评类型: 传统型
【题目背景】
建兴九年( 231年), 诸葛亮 率蜀军 四出祁山 。 司马懿料到蜀军粮草不济,坚守不出,又命人在成都散布诸葛亮欲谋反的谣言。刘禅听信谣言,下旨命诸葛亮退兵。在退兵时,魏军决定追击,诸葛亮早有防备,在木门道 伏击 射杀张郃。
【题目描述】
小 W在《三国演义》中读到四出祁山,对此非常感兴趣,在思考这场战役时他想出了一个问题。
小 W认为蜀军共有 N处伏击地点,可以把这 N个伏击地点从 1到 N进行标号 ,且蜀军恰好有 M个兵种 。 由于伏击需要保证军队可以方便地调度,所以不存在连续 M个伏击地点埋伏了 M个 不同的 兵种。小 W想知道所有 不同的 埋伏方案数对 1e9+7取模。
【输入格式】
从文件intercept.in中读入数据。
一行一 个数 N。
【输出格式】
输出到文件intercept.out中。
一行一个数,表示结果对1e9+7取模的结果。
【样例输入1】
3 3
【样例输出1】
21
【样例输入2】
见下发文件
intercept.in。
【样例输出2】
见下发文件
intercept.out。
【数据范围】
对于8%的数据, m=2
对于另16%的数据, n<=10,m<=4
对于48%的数据, n<=100000,m<=10
对于80%的数据, n<=100000,2<=m<=100
对于100%的数据, 2<=m<=100,m<=n<=10^16
//2024.8.27
//by white_ice
#include<bits/stdc++.h>
//#include"need.cpp"
using namespace std;
#define int long long
#define itn long long
constexpr int oo = 105;
constexpr int mod = 1e9+7;
int n,m;
struct matrix{int f[oo][oo];
matrix(){for(int i=0;i<oo;i++)for(int j=0;j<oo;j++)f[i][j] = 0;}
__inline matrix operator*(matrix b){
matrix c;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++){c.f[i][j]=0;
for(int k=1;k<=m;k++)c.f[i][j]=(c.f[i][j]+f[i][k]*b.f[k][j]%mod)%mod;}
return c;}}st;
__inline matrix qpow(matrix a,int b){matrix ans;
for(int i=1;i<oo;i++)ans.f[i][i]=1;
while(b){if(b&1)ans = ans*a;
a = a*a;b>>=1;}return ans;}
main(void){
//fre();
cin >> n >> m;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++){
if(i-1==j) st.f[i][j]=m-(j-1);
else if(i==1) st.f[i][j]=0;
else if(i<=j) st.f[i][j]=1;
}
matrix ans = qpow(st,n);
itn out = 0;
for(int i=1;i<=m;i++){
//p_(true,out,ans.f[i][1]);
out=(out+ans.f[i][1])%mod;
}
cout << out << '\n' << flush;
exit (0);
}
考虑DP,我们设 f i , j f_{i,j} fi,j表示遍历到第i位时,最后有j个元素互不相同,
那么状态转移就很好写了啊
f
[
i
+
1
]
[
j
+
1
]
=
f
[
i
]
[
j
]
∗
(
m
−
j
−
1
)
同时
f
[
i
]
[
j
]
还可以向
f
[
i
+
1
]
[
k
]
转移
,
其中
1
≤
k
≤
j
f[i+1][j+1] = f[i][j]*(m-j-1)\\ 同时f[i][j]还可以向f[i+1][k]转移,\\ 其中1\le k\le j
f[i+1][j+1]=f[i][j]∗(m−j−1)同时f[i][j]还可以向f[i+1][k]转移,其中1≤k≤j
其中第二维只有100位,
矩阵快速幂优化即可
T3C. 向日葵覆盖(ywk)
时间限制: 2 s 内存限制: 512 MB 测评类型: 传统型
一共有 n n n 个巨型向日葵,小 I 把这些向日葵栽到了地上,第 i i i 个向日葵高度为 a i − i a_i - i ai−i,特别的如果 a i − i ≤ 0 a_i - i \leq 0 ai−i≤0 那说明这个向日葵长在地底下。所有长在地上的向日葵可以遮盖住 [ i , a i ] [i, a_i] [i,ai],为了更好的种植向日葵,小 I 想知道 [ l , r ] [l,r] [l,r] 内第一个没有被覆盖的整数位置是哪里。糟糕的是 小 I 发现向日葵的高度会变化,所以他需要支持修改向日葵的高度和查询区间 [ l , r ] [l,r] [l,r] 内第一个没有被覆盖的整数位置。
先发一下形式化题面。
给定一个长度为 n n n 的序列 a a a , 分别为 a 1 , a 2 , . . . a n a_1, a_2 , ... a_n a1,a2,...an 。 你需要支持 m m m 次操作,操作有以下两种.
- 给定 x , v x, v x,v , 把 a x a_x ax 变成 v v v。
- 给定 l , r l, r l,r , 求最小的 x ∈ [ l , r ] x\in [l,r] x∈[l,r] 满足 max i = l x a i ≤ x \max_{i=l}^{x}a_i \leq x maxi=lxai≤x. 如果不存在一个合法的 x x x 输出 − 1 -1 −1.
1 ≤ n , m , a i ≤ 1 0 6 1 \leq n, m, a_i \leq 10^6 1≤n,m,ai≤106 , 强制在线(但是出题人懒得造了,靠大家自觉)
这题大家都能做到 O ( n 2 ) O(n^2) O(n2) 所以先不设置部分分了
输入样例
5 5 2 1 3 4 5 2 1 5 2 2 4 1 3 5 1 2 3 2 1 5
输出样例
2 2 5
//2024.8.27
//by white_ice
//#1736. 向日葵覆盖(ywk)
//单侧递归线段树
#include<bits/stdc++.h>
//#include"need.cpp"
using namespace std;
constexpr int inf=1e9;
constexpr int oo=1e6+5;
constexpr int op=3e6+10;
int n,m;int st[oo];
int ans,mxx;
struct segment{
int ls[op],rs[op],tot,rt;
int f[op],mx[op];
int work(int nl,int ns,int u,int lx){
if(nl==ns) {return(max(lx,mx[u])<=nl?nl:inf);}
int mid=(nl+ns)>>1;
if(lx<=mid) return min(work(nl,mid,ls[u],lx),f[rs[u]]);
else return work(mid+1,ns,rs[u],max(lx,mx[ls[u]]));
}
void up(int u,int nl,int ns){
mx[u]=max(mx[ls[u]],mx[rs[u]]);
int mid=(nl+ns)>>1;
if(mx[ls[u]]<=mid) f[rs[u]]=mid+1;
else f[rs[u]]=work(mid+1,ns,rs[u],mx[ls[u]]);
}
void build(int nl,int ns,int &u){
u=++tot;
if(nl==ns){
mx[u]=st[nl];
f[u]=(st[nl]<=nl? nl: inf);
return;
}
int mid=(nl+ns)>>1;
build(nl,mid,ls[u]);build(mid+1,ns,rs[u]);
up(u,nl,ns);
}
void update(int nl,int ns,int u,int x){
if(nl==ns){
mx[u]=st[nl];
f[u]=(st[nl]<=nl? nl: inf);
return;
}
int mid=(nl+ns)>>1;
if(x<=mid) update(nl,mid,ls[u],x);
else update(mid+1,ns,rs[u],x);
up(u,nl,ns);
}
void query(int l,int r,int nl,int ns,int u){
if(l<=nl&&ns<=r){
ans=min(ans,work(nl,ns,u,mxx));
mxx=max(mxx,mx[u]);
return;
}
int mid=(nl+ns)>>1;
if(l<=mid) query(l,r,nl,mid,ls[u]);
if(r>mid) query(l,r,mid+1,ns,rs[u]);
}
}seg;
main(void){
//fre();
cin.tie(0)->ios::sync_with_stdio(0);
cin >> n >> m;
for(int i=1;i<=n;i++) cin>>st[i];
seg.build(1,n,seg.rt);
for(int op,x,y,i=1;i<=m;i++){
cin >> op >> x >> y;
if(op==1){
st[x]=y;
seg.update(1,n,seg.rt,x);
}
else{
ans=inf;mxx=0;
seg.query(x,y,1,n,seg.rt);
cout<<(ans>=inf?-1:ans)<<'\n';
}
}
exit (0);
}
蘑菇覆盖题解
发现要求最小的
x
∈
[
l
,
r
]
x \in [l,r]
x∈[l,r] 满足
max
i
=
l
x
a
i
≤
x
\max_{i=l}^{x}a_i\leq x
maxi=lxai≤x , 可以想到用类似于兔队线段树的做法来解决这个问题。因为修改是单点修改,查询也完全可以看成是几个节点信息的合并,所以关键在于如何处理好 update 操作。考虑在线段树的每个节点上维护这个节点所代表的区间
[
l
,
r
]
[l,r]
[l,r] 内的最小的满足
max
i
=
l
x
a
i
≤
x
\max_{i=l}^{x}a_i\leq x
maxi=lxai≤x 的
x
x
x . 显然在 update 操作中这个节点左边的其它节点可能会对这个节点产生一些影响,所以这里记
u
p
d
a
t
e
(
u
,
l
,
r
,
l
m
a
x
)
update(u, l, r, lmax)
update(u,l,r,lmax) ,
u
u
u 表示当前要 update 的节点的编号
l
l
l 和
r
r
r 表示当前节点所代表的区间,
l
m
a
x
lmax
lmax 表示在当前节点左边有一个大小为
l
m
a
x
lmax
lmax 的值对 update 产生影响。考虑分类讨论。
-
l
m
a
x
>
m
i
d
lmax > mid
lmax>mid ,那么
[
l
,
m
i
d
]
[l, mid]
[l,mid] 中就不可能有满足条件的
x
x
x 了,考虑右区间就行,即
u
p
d
a
t
e
(
r
s
,
m
i
d
+
1
,
r
,
max
(
l
m
a
x
,
M
a
x
l
s
)
)
update(rs, mid + 1, r, \max(lmax, Max_{ls}))
update(rs,mid+1,r,max(lmax,Maxls)) 这里的
M
a
x
l
s
Max_{ls}
Maxls 表示左儿子所代表区间内
a i a_i ai 的最大值。 - l m a x < m i d lmax < mid lmax<mid ,那么 [ m i d + 1 , r ] [mid + 1, r] [mid+1,r] 只会受到左儿子的影响,这个东西可以记录一下叫 a n s 2 ans2 ans2。那么原来的 update 就相当于 min ( u p d a t e ( l s , l , m i d , l m a x ) , a n s 2 r s ) \min(update(ls, l, mid, lmax), ans2_{rs}) min(update(ls,l,mid,lmax),ans2rs)
至于这个 a n s 2 ans2 ans2 怎么处理,其实他就是 u p d a t e ( r s , m i d + 1 , r , M a x l s ) update(rs, mid + 1, r, Max_{ls}) update(rs,mid+1,r,Maxls)
T4D. 【2023.6.13 ywk 互测】机关
时间限制: 1 s 内存限制: 512 MB 测评类型: 传统型
饺子哥哥是天上神仙, 祂用魔法包了好多好多饺子,一共有 n n n 饺子,并且在一号饺子里放了一枚硬币。小橘子是饺子哥哥养的一只猫猫.它想得到那枚硬币。只要它通过饺子哥哥设计的游戏,就能拿到硬币。游戏如下:
聪明的饺子哥哥设计了一个机关,并把饺子放在了机关里。机关有四个按钮,机关内部有两个序列 a , b a, b a,b ,四种按钮的作用分别是:
- 若这是第 i i i 次按动 1 1 1 或者 2 2 2 2 按钮,则将饺子 i i i 放入 a a a 序列的前端。
- 若这是第 i i i 次按动 1 1 1 或者 2 2 2 2 按钮,则将饺子 i i i 放入 a a a 序列的末端。
- 将 a a a 序列开头的饺子取出,并放入 b b b b 序列末尾。
- 将 a a a 序列末尾的饺子取出,并放入 b b b b 序列末尾。
机关很特别,只要按动 3 3 3 或 4 4 4 号按钮, 1 1 1 和 2 2 2 号按钮将永远消失。
小橘子需要恰好按动 n n n 次 3 3 3 或者 4 4 4 号按钮,得到长度为 n n n 的 b b b 序列,此时小橘子可以拿到 b b b 序列第 k k k 个位置的饺子。
当然作为饺子哥哥的猫猫,聪明的小橘子可以轻松拿到硬币,但它想考考你,它会给你一组 n , k n, k n,k , 问有多少个不同的合法 b b b 序列可以使它得到硬币。聪明的小朋友,你能回答出小橘子的问题吗?
注:1.我们称 b b b 序列为合法的,当且仅当可以由上述操作生成;
2.由于答案很大,小橘子会给你一个数 m o d mod mod ,你只需要输出答案对 m o d mod mod 取模的值。
输入格式
输入仅一行三个数表示 n , k , m o d n,k,mod n,k,mod
输出格式
输出仅一行一个数,表示答案。
数据范围
对于8%的数据, k ≤ n ≤ 10 k \leq n \leq 10 k≤n≤10;
另有20%的数据, k = n k=n k=n;
对于60%的数据, k ≤ n ≤ 3000 k \leq n \leq 3000 k≤n≤3000;
另有12%的数据, k = 1 k=1 k=1;
对于100%的数据, k ≤ n ≤ 500000 , 1 0 8 ≤ m o d ≤ 2 × 1 0 9 k \leq n \leq 500000,10^8 \leq mod \leq 2\times 10^9 k≤n≤500000,108≤mod≤2×109 且是质数
输入样例
输入样例1: 2 1 998244353 输入样例2: 3 2 998244353 输入样例3: 10 5 998244353
输出样例
输出样例1: 1 输出样例2: 2 输出样例3: 6864
样例解释
样例解释1:
合法的 b 序列为{1,2},一种合法的生成方式是{1,2,3,3}:
样例解释2:
合法的 b b b 序列为 { 2 , 1 , 3 } , { 3 , 1 , 2 } \{2,1,3\},\{3,1,2\} {2,1,3},{3,1,2}
分别对应可能的生成方式是 { 2 , 1 , 2 , 3 , 3 , 3 } \{2,1,2,3,3,3\} {2,1,2,3,3,3}, { 2 , 1 , 2 , 4 , 4 , 3 } \{2,1,2,4,4,3\} {2,1,2,4,4,3}
//2024.8.27
//by white_ice
//#2204. 【2023.6.13 ywk 互测】机关
//计数,组合数学
#include<bits/stdc++.h>
//#include"need.cpp"
using namespace std;
#define itn long long
#define int long long
constexpr int oo = 1e6+10;
int fac[oo],ifac[oo],inv[oo];
int pow2[oo];int n,k,mod;
int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
__inline int getc(int n,int m){
if (n<m||m<0) return 0;
return fac[n]*ifac[n-m]%mod*ifac[m]%mod;
}
__inline int qpow(int a,int b=mod-2){int res = 1;while (b){
if(b&1)(res*=a)%=mod;(a*=a)%=mod;b>>=1;}return res;}
main(void){
//fre();
cin.tie(0)->ios::sync_with_stdio(0);
cin >> n >> k >> mod;
ifac[1] = ifac[0] = fac[0] = fac[1] = inv[1] = 1;
pow2[0] = 1;pow2[1] = 2;
for (int i=2;i<=2*n;++i){
fac[i] = fac[i-1]*i%mod;
inv[i] = (mod-mod/i)*inv[mod%i]%mod;
ifac[i] = ifac[i-1]*inv[i]%mod;
pow2[i] = pow2[i-1]*2%mod;
}
int res = 0;
if (n==k){
cout << getc(2*n-2,n-1)*inv[n]%mod << '\n';
exit (0);
}
for (int i=n-k+1;i<=n;++i){
int j = i-(n-k);
res = add(res,getc(i-2,n-k-1)*pow2[n-k-1]%mod*add(getc(k-j+k-1,k-1),mod-getc(k+k-j-1,k))%mod);
}
cout << res << '\n';
exit (0);
}
声明:本题解100%基于youwike讲解内容,若有不解之处请移步向youwike询问,因为我也不会
题意简明:
给定一个 1 − n 1-n 1−n的排列以及A,B两个空序列,现定义如下四个操作:
- 将原序列中队首元素取出,加入A前端
- 将原序列中队首元素取出,加入A末端
- 将A中队首元素取出,加入B末端
- 将A中队尾元素取出,加入B末端
要求在使用3或4操作后,不能再使用1或2操作
求解共多少种操作方法可使B序列长度为n的情况下,的k项为1
题解正文:
首先考虑按照题目中的操作方式,
要保证B长度为n,就要先将原序列使用1,2操作清空
在只进行1,2操作时,最终A序列一定会形成一个向下凹的情况
类似:
这里顺便吐槽一下youwike的古神画风
其中最低点就是1,是我们要取到的地方
那么下面可以开始考虑,如何将这个序列A通过3,4操作变成需要的B
这里我们假设1是从左边取到的,那么就会这样:
其中红色的部分为要加入B的前 k k k个,没有被取到的则剩下 n − k n-k n−k个
注意到,剩余 n − k n-k n−k个可以随意加入,所以可以不考虑这些,加入这些共有 2 n − k − 1 2^{n-k-1} 2n−k−1种可能,
将前面的方案数最后乘上 2 n − k − 1 2^{n-k-1} 2n−k−1即可
下面对B的前 k k k项进行讨论
由于序列A向下凹陷的特殊性质,我们不难理解,加入B的前 k k k项一定是以两个严格下降子序列构成的
那么问题就被转化成了有多少长度为 k k k的序列,可以被表示为两个单调递减的子序列
单调递减也可以反过来求单调递增,所以这里我们求单调递增
首先,我们引入一个引理:
一个序列能被表示为两个单调递增子序列,那么一定能够保证一个子序列无时无刻都大于另一个子序列
为什么呢?看图:
很难不发现,如果出现了左边这样的情况,我们完全可以转化成右边这样的
下面考虑DP,我们设定较大序列为 x x x,较小序列为 y y y
我们定义 f i , j f_{i,j} fi,j,表示考虑到第 i i i个数时,序列 x x x的最后一个值为 j j j
首先, f i , j f_{i,j} fi,j可以将当前一个最小的数加入到 y y y中,即向 f i + 1 , j f_{i+1,j} fi+1,j转移
同时, f i , j f_{i,j} fi,j也可以向 f i , j + 1 , f i , j + 2 , . . . . f_{i,j+1},f_{i,j+2},.... fi,j+1,fi,j+2,....转移
当然,这里要注意, j ≥ i j\ge i j≥i是限制条件
然后,我们就发现,这个DP过程其实就是一个格路计数
具体嘛。。。看图:
在如图这样一张网格里,从起点 ( 1 , 1 ) (1,1) (1,1)出发,图中红线为合法转移方式,问最终到达横坐标为k的方案数
其中直线 y = x y=x y=x不可跨越
那么转移实际上可以转化为向右和向上两种简单操作,使用反射容斥即可
关于反射容斥
我们考虑在一个网格中,从某一点走向另一点,同时网格上有一条不能跨越的直线
首先随意走,不考虑限制,那么总方案数是 ( n + m n ) \begin{pmatrix}n+m\\n\end{pmatrix} (n+mn)
下面考虑不合法方案,不合法方案中总会有一个点和不能跨域的直线相交,我们找到第一个和该直线相交的点,并且将之前没经过这条直线的部分沿着该直线翻折
那么就会出现新的起点,计算新起点到原终点的路径数,用 ( n + m n ) \begin{pmatrix}n+m\\n\end{pmatrix} (n+mn)减去即可
代码部分:
这里我们将youwike的代码直接复制过来,反正是他讲的题
/*
\ | ^ ^ \
-- | # # \
\_| \
*/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#define int long long
const int N = 1e6 + 10;
int fac[N], ifac[N], inv[N];
int pow2[N];
int n, k, mod;
int add(int x, int y) {
return x + y >= mod ? x + y - mod : x + y;
}
int C(int n, int m) {
if (n < m || m < 0) return 0;
return 1ll * fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
int qpow(int a, int b = mod - 2) {
int res = 1;
while (b) {
if (b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
signed main() {
std::ios::sync_with_stdio();
std::cin.tie(0), std::cout.tie(0);
std::cin >> n >> k >> mod;
ifac[1] = ifac[0] = fac[0] = fac[1] = inv[1] = 1;
pow2[0] = 1, pow2[1] = 2;
for (int i = 2; i <= 2 * n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
ifac[i] = 1ll * ifac[i - 1] * inv[i] % mod;
pow2[i] = 1ll * pow2[i - 1] * 2 % mod;
}
int res = 0;
if (n == k) {
std::cout << 1ll * C(2 * n - 2, n - 1) * inv[n] % mod << '\n';
return 0;
}
for (int i = n - k + 1; i <= n; ++i) {
int j = i - (n - k);
res = add(res, 1ll * C(i - 2, n - k - 1) * pow2[n - k - 1] % mod * add(C(k - j + k - 1, k - 1), mod - C(k + k - j - 1, k)) % mod);
}
// res = add(res, 1ll * C(n - 2, n - k - 1) * pow2[n - k - 1] % mod);
std::cout << res << '\n';
return 0;
}
后记:
这道题难度个人感觉还是挺大的,很多转化需要一些神奇的思路和经验,然后就是模拟赛别出这么抽象了,最少给个样例啊
单侧递归线段树(兔队线段树)
单侧递归线段树用于求解严格前缀最大值类问题
我们使用线段树进行维护
考虑将每个节点同时记录两个值, s i , g i s_i,g_i si,gi
其中 s 1 s_1 s1表示正常线段树所记录的区间最大值,而 g i g_i gi表示整体前缀的最大值
发现,在维护 g i g_i gi时,直接将两颗子树的信息相加是错误的
左子树信息可以继承,但右子树不可以
那么我们考虑引入一个新函数 c a l c ( i , p r e ) calc(i,pre) calc(i,pre)它的作用是返回 i i i 子树内,考虑了前缀最大值 p r e pre pre 的影响后的答案。
为了方便表述,把信息 1 记做 max[i],把信息 2 记做 cnt[i]
当当前节点
i
i
i 是叶节点的时候,贡献很容易计算。
否则考虑左右子树的贡献分别计算,分成两种情况考虑:
-
p
r
e
pre
pre 小于左子树的最大值:
此时对右子树来说, p r e pre pre 是无意义的,所以递归进左子树,右子树的贡献直接用“全部”减“左子树”计算即可。 -
p
r
e
pre
pre 大于等于左子树的最大值:
此时对左子树来说,就不可能贡献任何前缀最大值了,所以贡献为 0 0 0,然后递归进右子树即可。
可以看出,调用一次 c a l c calc calc 函数递归的时间复杂度为 O ( l o g n ) O(logn) O(logn),因为每次只递归进一个孩子。
每次维护当前节点的答案时,只要令 c n t [ i ] = c n t [ l e f t c h i l d [ i ] ] + c a l c ( r i g h t c h i l d [ i ] , m a x [ l e f t c h i l d [ i ] ] ) cnt[i]=cnt[leftchild[i]]+calc(rightchild[i],max[leftchild[i]]) cnt[i]=cnt[leftchild[i]]+calc(rightchild[i],max[leftchild[i]]) 即可。
可以发现有 O ( l o g n ) O(logn) O(logn) 个节点要调用 c a l c calc calc 函数,所以一次单点修改的时间复杂度为 O ( l o g 2 n ) O(log^2n) O(log2n)。
P4198 楼房重建
楼房重建
题目描述
小 A 的楼房外有一大片施工工地,工地上有 N N N 栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。
为了简化问题,我们考虑这些事件发生在一个二维平面上。小 A 在平面上 ( 0 , 0 ) (0,0) (0,0) 点的位置,第 i i i 栋楼房可以用一条连接 ( i , 0 ) (i,0) (i,0) 和 ( i , H i ) (i,H_i) (i,Hi) 的线段表示,其中 H i H_i Hi 为第 i i i 栋楼房的高度。如果这栋楼房上任何一个高度大于 0 0 0 的点与 ( 0 , 0 ) (0,0) (0,0) 的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了 M M M 天。初始时,所有楼房都还没有开始建造,它们的高度均为 0 0 0。在第 i i i 天,建筑队将会将横坐标为 X i X_i Xi 的房屋的高度变为 Y i Y_i Yi(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小 A 数数每天在建筑队完工之后,他能看到多少栋楼房?
输入格式
第一行两个正整数 N , M N,M N,M。
接下来 M M M 行,每行两个正整数 X i , Y i X_i,Y_i Xi,Yi。
输出格式
M M M 行,第 i i i 行一个整数表示第 i i i 天过后小 A 能看到的楼房有多少栋。
样例 #1
样例输入 #1
3 4 2 4 3 6 1 1000000000 1 1
样例输出 #1
1 1 1 2
提示
对于 100 % 100\% 100% 的数据, 1 ≤ X i ≤ N 1 \le X_i \le N 1≤Xi≤N, 1 ≤ Y i ≤ 1 0 9 1 \le Y_i \le 10^9 1≤Yi≤109, 1 ≤ N , M ≤ 1 0 5 1\le N,M \le 10^5 1≤N,M≤105。
//2024.5.16
//by white_ice
//P4198 楼房重建
#include <algorithm>
#include<bits/stdc++.h>
using namespace std;
#define itn long long
#define int long long
constexpr int oo = 100005;
template <class usd>
bool jntm(usd a,usd b){return a>b?a:b;}
template <class usd>
bool ngm (usd a,usd b){return a<b?a:b;}
int n,m;
double st[oo];
namespace Tree{
itn ls(itn a){return a<<1;}
itn rs(int a){return a<<1|1;}
itn mid(itn a,itn b){return (a+b)>>1;}
struct nod{double v;itn len;}tree[oo<<2];
void push(int x){tree[x].v=max(tree[x<<1].v,tree[x<<1|1].v);}
int push_main(double lx,int x,int l,int r){
if(tree[x].v<=lx)
return 0;
if(st[l]>lx)
return tree[x].len;
if(l==r)
return st[l]>lx;
int m=mid(l,r);
if(tree[ls(x)].v<=lx)
return push_main(lx,rs(x),m+1,r);
else return push_main(lx,ls(x),l,m)+tree[x].len-tree[ls(x)].len;
}
void find(int x,int l,int r,int to,int c){
if(l==r&&l==to){
tree[x].v=(double)c/to;
tree[x].len=1;
return ;
}
int mid=(l+r)>>1;
if(to<=mid)
find(x<<1,l,mid,to,c);
else if(to>mid)
find(x<<1|1,mid+1,r,to,c);
push(x);
tree[x].len=tree[ls(x)].len+push_main(tree[x<<1].v,x<<1|1,mid+1,r);
}
};
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >>n >> m;
using namespace Tree;
int x,y;
for (int i=1;i<=m;i++){
cin >> x >> y;
st[x] = (double)y/x;
find(1,1,n,x,y);
cout << tree[1].len<< '\n';
}
return 0;
}