Logo handezheng 的博客

博客

标签
暂无

整除分块

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-22 15:03:49

整除分块

引入:看题

明显,对于这道题,单纯的 $O(n)$ 枚举会爆时间,这时候我们就要引入 整除分块 的芝士了。

举例及分块有关知识

以 $16$ 为例,$n=16$ 时,枚举 $\lfloor \frac{n}{i} \rfloor$ 的情况。

$i$ $\lfloor \frac{n}{i} \rfloor$
${\color{red}1}$ ${\color{red}16}$
${2}$ ${8}$
${\color{orange}3}$ ${\color{orange}5}$
$\color{purple}4$ $\color{purple}4$
${\color{yellow}5}$ ${\color{yellow}3}$
${\color{green}6}$ ${\color{green}2}$
${\color{green}7}$ ${\color{green}2}$
${\color{green}8}$ ${\color{green}2}$
$\textcolor{blue}{9-16}$ ${\color{blue}1}$

这时候我们会发现,$\lfloor \frac{n}{i} \rfloor$ 被分成了一个个块,在同一个块中 $\lfloor \frac{n}{i} \rfloor$ 的值不变,如 $data (块的值) = 2$ 时,$l = 6,r = 8$;$data=1$ 时,$l = 9,r = 16$。

块的个数

当 $i \le \sqrt{n}$ 时,

此时最多分块 $\sqrt{n}$ 个(一个块最少要对应一个 $i$,明显块最小,即块的长度为 $1$ 时,块最多)。

当 $i > \sqrt{n}$ 时,

此时最多的分块也是 $\sqrt n$ 个($i \times \lfloor \frac n i \rfloor$ 近似等于 $n$,所以 $\lfloor \frac n i \rfloor$ 最多会有 $\sqrt n$ 个)。

综上,$size(\text{分块}) \le 2\sqrt n$。

块的范围

(设第 $i$ 个块的左端点为 $l_i$,右端点为 $r_i$)

易得 $l_1 = 1,l_i = r_{i-1} + 1$,即当前分块的左端点是上一个分块的右端点的下一个。

那怎么求 $r_i$ 呢?
我们可以设 $\lfloor \frac n i \rfloor$ 为 $data$,则在同一个分块中 $data$ 的值不变(由定义得)。
$r_i$ 代表的是满足 $data$ 不变的最大值,我们可以倒过来推,$r_i \times data \le n$(当且仅当 $data\mid n$ 时等号成立),而我们要求最大,所以就有 $$r_i = \lfloor\frac n{data}\rfloor = \lfloor \frac{n}{\lfloor \frac{n}{i}\rfloor}\rfloor$$ 如此我们便可以知道块的右端点位置了。

回到题目

朴素做法:

这个应该很容易想到,时间复杂度为 $O(n)$ 的做法:直接照着题目需求枚举 $k \bmod i$,累加即可。但很明显这样会爆,所以看下面的做法:

转化、分解一下需要求的等式:

下面的非常非常重要!!! $$ \sum_{i=1}^n k \bmod i $$ $$ = \sum_{i=1}^n k - \lfloor \frac k i \rfloor \times i $$ $$ = n\cdot k - \sum_{i=1}^n \lfloor \frac k i \rfloor \times i $$ 我们知道,当 $i > k$ 时,$\lfloor \frac k i \rfloor$ 一定为零,所以: $$ \text{原式} = n\cdot k - \sum_{i=1}^{\min (k,n)} \lfloor \frac k i \rfloor \times i $$

做法(应用分块)

学过整除分块后(当然刚刚学了),我们会知道在同一个块中,$\lfloor \frac k i \rfloor$ 的值不变,而一个除法分块的左右端点可求(在此我用 l,r 表示),所以在同一个分块中,$\sum_{i=1}^n \lfloor \frac k i \rfloor \times i$ 就变成了: $$ \sum_{i= \text{当前分块的l}}^{\text{当前分块的r}} \lfloor \frac k i \rfloor \times i $$ $$ = \lfloor \frac k i \rfloor \times l +\lfloor \frac k i \rfloor \times (l+1) +...+\lfloor \frac k i \rfloor \times r $$ 乘法分配律得 $$ \lfloor \frac k i \rfloor \times [l+(l+1)+(l+2)+...+r] $$ 等差数列求和公式:(首项加末项)乘项数除以二,得到: $$ \lfloor\frac k i\rfloor \times \frac{(l+r) \times (r-l+1)}2 $$ 很明显这是一个时间复杂度为 $O(1)$ 的公式,对于每一个分块我们都可以进行一次这样的操作,而分块的个数是 $\sqrt k$ 量级的,所以时间复杂度就由开始的 $O(n)$ 变成了 $O(\sqrt n)$ 了。

下面给出代码:

#include<bits\/stdc++.h>
using namespace std;
#define int long long
#define F(i,j,n) for(int i = j;i <= n;i ++)
const int N = 1e6 + 50,M = 1e3 + 50;

int n,k;
int l,r,ans;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin >> n >> k;
	ans = n * k;\/\/初值 
	for(int l = 1;l <= n;l = r + 1){
		if(l > k) r = n;
			\/\/l > k 时,k\/l为0,此时k\/0,会RE; 
		else r = min(k \/ (k\/l),n);
			\/\/r最大到n 
		ans -= (k\/l) * (l + r) * (r-l+1)\/2;
			\/\/等差数列求和 
	}
	cout << ans;
	return 0;
}

一道蓝题就这样被水过了......

题解:P10730 [NOISG 2023 Qualification] Burgers

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-09-10 08:16:04

题解:P10730

前言

题目传送门

博客食用更佳

本题解借鉴了部分楼上大佬的内容,但是我想详细讲一下赚点估值,以便理解(楼上的题解我看了两天才看懂)。

题意

两种汉堡,一个汉堡对于原料 $x_i$ 需要使用 $a_i$ 或 $b_i$ 份(根据汉堡是哪种决定使用份数),求最大能做的汉堡个数。

思路

首先想到贪心,很明显,贪心是错的。因为你在选择原料时并不知道是优先选哪个汉堡,也不知道每种汉堡要选多少个。
其次想到 DP。如此你会发现,转移方程不知道怎么写,而且时间会超。

我们发现,对于一个整数 $k$,如果可以做成 $k$ 个汉堡,那么也一定能做出数量小于 $k$ 的汉堡;反之,如果 $k$ 个汉堡不可做,那么数量大于 $k$ 的汉堡也一定不可做——这不就是单调性嘛!
有了单调性,下意识想到二分。可是难题又来了:我们的 $check$ 函数怎么写呢?

我们发现,如果 $a_i$ 比 $b_i$ 小并且在 $x_i$ 范围内能够取到 $k$ 个汉堡的话,会对 $a_i$ 有一个下界要求:总量为 $k$ 个汉堡,$a_i$ 取到的越少 $b_i$ 取到的就越多(废话),这样用到的原料总数就会更多,就有可能超出 $x_i$ 的范围。所以要求 $a_i$ 至少要取到多少个才能使汉堡总数达到 $k$(在 $x_i$ 范围内),这便是 $a_i$ 的下界要求。
同理,当 $b_i<a_i$ 时,对 $b_i$ 有也一个下界要求,我们可以转换为对 $a_i$ 的上界要求

在 $a$ 的上下界中去选,如图,红色为上界,蓝色为下界,如果上下界不相交(最大下界小于等于最小上界),即存在 $a$ 的一个取值,满足它大于等于所有下界且小于等于所有上界,那么便可以取到 $k$ 个汉堡,$check$ 函数返回值为 $true$。

袋马

与楼上大佬大差不差(感觉我的更简洁),注释请看楼上

#include<bits\/stdc++.h>
using namespace std;
#define int long long
#define F(i,l,r) for(int i = l;i <= r;i++)
const int N = 1e6 + 50,M = 1e3 + 50;
const int INF = 0x3f3f3f3f3f3f3f3f,INT = 0x7fffffff;
const int mod = 1e9 + 7;

int n,x[N],a[N],b[N];

inline bool check(int k){
	int mi=0,ma=1e9;
	\/\/上界和下界
	F(i,1,n){
		if(a[i]==b[i]){
			if(a[i]*k>x[i]) return 0;
		}else if(a[i]<b[i]){
			if(a[i]*k>x[i]) return 0;
			if(b[i]*k<=x[i]) continue;
			mi=max(mi,k-(x[i]-a[i]*k)\/(b[i]-a[i]));
\/\/			a[i]的下界
		}else{
			if(b[i]*k>x[i]) return 0;
			if(a[i]*k<=x[i]) continue;
			ma=min(ma,(x[i]-b[i]*k)\/(a[i]-b[i]));
		}
	}
	return (mi <= ma);
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	F(i,1,n) cin>>x[i];
	F(i,1,n) cin>>a[i];
	F(i,1,n) cin>>b[i];
	int l=0,r=1e9;
	while(l<=r){
		int mid = (l+r) >>1;
		if(check(mid)) l=mid+1;
		else r=mid-1;
	}
	cout<<r;

	return 0;
}

AC记录

——$2024.9.10,9:10$ 完笔

题解:[ABC200D] Happy Birthday! 2

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-09-18 11:18:38

题解:[ABC200D] Happy Birthday! 2

最朴素、最暴力的做法是枚举 $N$ 的全排列,但是因为有 $ N \le 200$,所以这种方法无疑会超时。

这时候我们可以用容斥的思路进行优化。引入抽屉原理

将 $n+1$ 个物体,划分为 $n$ 组,那么有至少一组有两个(或以上)的物体。

因为模数是 200,所以我们只要有 201 个互异的序列,就一定会有两个序列的和同余。而对于长度为 $N$ 的序列,我们可以构造出 $2^N$ 种不同序列。对于 $N\le8$,我们可以暴力取得答案;而对于 $N>8$,因为 $2^8=256>200$,所以一定有答案,我们只需要用前八个元素构造序列,然后用桶存储和判断即可。

代码如下:

#include<bits\/stdc++.h>
#include<vector>
#include<map>
using namespace std;
#define int long long
#define F(i,l,r) for(int i = l;i <= r;i++)

int n,a[205];
map<int,vector<int> > mp;

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	F(i,1,n){
		cin>>a[i];
		a[i]%=200;
	}
	n=min(n,8ll);
	for(int i=1;i<1<<n;i++){
		vector <int> t;
		int sum=0;
		for(int j=1;j<=n;j++){
			if(i>>(j-1) & 1){
				t.push_back(j);
				sum=(sum+a[j])%200;
			}
		}
		sum%=200;
		if(mp.find(sum)!=mp.end()){
			cout<<"Yes\n"<<mp[sum].size()<<' ';
			for(auto e:mp[sum])	cout<<e<<' ';
			cout<<'\n'<<t.size()<<' ';
			for(auto e:t) cout<<e<<' ';
			return 0;
		}
		mp[sum]=t;
	}
	cout<<"No\n";

	return 0;
}

$2024.9.18$ 完稿

题解:P7311 [COCI2018-2019#2] Maja

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-10-12 15:33:11

题解:P7311 [COCI2018-2019#2] Maja

题目传送门

更垃圾的阅读体验

题意

给定 $n\times m$ 的网格和起点,每个格子的权值为 $c_{i,j}$,共可以走 $k$ 步,求最大的 $\sum_{i,j}^{走过网格i,j}c_{i,j}$。

其中,有 $n,m\le100,k\le10^9,2\mid k$。

思路

应为 $k$ 很大,所以一定会在某一些位置循环跑动,而循环节的长度为二时一定最优,下面给出证明:

设循环节长度为三且三个点的权值分别为 $a,b,c$,而此时的平均权值为 $\frac{a+2b+c}4$。
若循环节长度为二,会有两种方案,平均权值分别为 $\frac{a+b}2$ 和 $\frac{b+c}2$。
能够发现,$\frac{a+b}2+\frac{b+c}2=2\times \frac{a+2b+c}4$,所以前两者必有一个大于等于后者(当且仅当 $a=b=c$ 时等号成立)。

循环节大于三时证明与其类似,这里不再赘述。

那我们便可以把 $k$ 步转化为 $n\times m$ 步,在其范围内进行 DP,对于每一个网格取其相邻的权值最大网格为循环节,取得它们和的最大值即为答案。

时间复杂度 $O(n^2m^2)$,空间复杂度 $O(nm) $。

DP过程

设 $f_{k,i,j}$ 表示第 $k$ 步,网格坐标为 $(i,j)$ 时取得的最大值。

初始化 $f_{k,i,j}$ 为极大值,$f_{0,a,b}=0$。

转移:$f_{k,i,j}=\max{f_{k-1,i-1,j},f_{k-1,i+1,j},f_{k-1,i,j-1},f_{k-1,i,j+1}}+c_{i,j}$。

代码

#include<bits\/stdc++.h>
using namespace std;
#define int long long
#define F(i,l,r) for(int i = l;i <= r;i++)
const int N = 1e6 + 50,M = 1e3 + 50;
const int INF = 0x3f3f3f3f3f3f3f3f,INT = 0x7fffffff;
const int mod = 1e9 + 7;

int n,m,stx,sty,q,ans=-INF;
int c[105][105],mx[105][105];
int f[3][105][105];

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	memset(f,0xcf,sizeof f);
	cin>>n>>m>>stx>>sty>>q;
	F(i,1,n) F(j,1,m) cin>>c[i][j];
	F(i,1,n) F(j,1,m) mx[i][j]=max(max(c[i-1][j],c[i+1][j]),max(c[i][j-1],c[i][j+1]))+c[i][j];
	f[0][stx][sty]=0;
	for(int k=1;k<=min(q\/2,n*m);k++){
		int t=k&1,l=t^1;
		for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
			f[t][i][j]=max(max(f[l][i-1][j],f[l][i+1][j]),max(f[l][i][j-1],f[l][i][j+1]))+c[i][j];
			ans=max(ans,f[t][i][j]*2 + (q\/2-k)*mx[i][j] - c[i][j]);
		}
	}
	cout<<ans;

	return 0;
}

——$2024.10.14,11:28$ 完笔。

关于狄利克雷卷积与莫比乌斯反演

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-01-22 16:45:48

狄利克雷卷积

$$ 定义常值函数I(n) = 1; \quad 线性函数id(n) = n;\quad 单位函数\epsilon(n) = [n=1] $$

$$ 狄利克雷卷积:h(n) = \sum_{d\mid n}f(d) g(\frac d n) $$ $$ 记作h = f * g,有如下性质: $$

  1. 交换律 $fg = gf$
  2. 结合律 $(fg)h = f(gh)$
  3. 分配率 $f(g+h) = fg + f*h$
  4. $fh = gh 的充要条件是f=g(h(1)\ne0)$
  5. $若fg=h,h是积性函数的充要条件是f,g均为积性函数$ 证明如下: $$ 设两积性函数f(n),g(n),且h = fg;整数a,b互质 $$ h(a) = \sum_{d\mid a}f(d)\cdot g(\frac a d)\quad h(b) = \sum_{d\mid b}f(d)\cdot g(\frac b d) $$ $$ h(a)h(b) = \sum_{d_1\mid a}f(d_1)\cdot g(\frac a {d_1})\times \sum_{d_2\mid b}f(d_2)\cdot g(\frac{b}{d_2}) $$ $$ = \sum_{d\mid ab}f(d)\cdot g(\frac {ab} d) = h(ab) $$

狄利克雷逆元

关于狄利克雷卷积,其有逆运算,称之为狄利克雷逆元,如下:

若有 $fg=h和f=hg^{-1}$,则有 $gg^{-1}=\epsilon$,即 $g$ 的狄利克雷逆元,记作 $g^{-1}$。$g^{-1}$ 的表达式如下:
因为有 $(f
g)(n)=\epsilon(n)=\sum_{d\mid n}g(d)\cdot f(\frac n d)$,
首先 $f(1)\times g(1)=\epsilon(1)$,得 $g(1)=\frac{1}{f(1)}$
对于 $n>1$,可发现等式右边总有一个 $g(n)$,将其提出,得到 $g(n)f(1) + \sum_{d\mid n,d\ne n}g(d)f(\frac n d )=0$ ,变形得到: $$ g(n) = -\frac{1}{f(1)}\sum_{d\mid n,d\ne n}g(d)f(\frac n d) $$ $g$ 便是 $f$ 的狄利克雷逆元。

莫比乌斯函数

定义

莫比乌斯函数 $\mu$ 是一个积性函数,而且它对应任意质数的取值都为 $−1$,任意质数的高次幂的取值都为 $0$。

基于此,对于 $\mu(n)$,对 $n$ 进行质因数分解,得到 $$ n = \prod_{k=1}^s p_k^{a_k} $$ 对于 $n=1$,有 $\mu(1)=1$,
否则:$若 \exists a_k >1,则 \mu(n)=0;\quad 否则若\forall a_k=1,那么\mu(n)=(-1)^s$。

方程及性质

方程

思考上文所述狄利克雷逆元相关知识,求 $I^{-1}$(设 $I^{-1}=f$):

对于 $n=1$,有 $f(1)=1$;
否则,有 $$ f(n)=-\sum_{d\mid n,d\ne n}f(d) $$ 此时可以发现,对于任意质数 $p$,$f(p)=−f(1)=−1$,而 $f(p^2)=−[f(1)+f(p)]=0$。
容易发现,$p$ 的其他高次幂也都会等于 $0$,因为它们都等于 $−[f(1)+f(p)]$,因为其他 $f(p^n)$ 项都直接等于 $0$ 而被约去了。

这个函数其实就是莫比乌斯函数,我们可以得到它的表达式($n>1$):

$$ \mu(n) = -\sum_{d\mid n,d\ne n}\mu(d) $$

性质

由于 $I$ 是积性函数,且任意积性函数的狄利克雷逆元都是积性函数,所以莫比乌斯函数 $\mu$ 也是积性函数。

因此我们可以得到几点性质: $$ \mu * I = \epsilon $$ $$ \phi * I = id $$ $$ id * \mu = id * I^{-1} = \phi $$

关于莫比乌斯函数,它在狄利克雷生成函数中,等价于黎曼函数 $\zeta$ 的倒数

利用欧拉筛求莫比乌斯函数

  1. $\mu(1)=1$.
  2. 当 $n$ 是质数时,$\mu(n)=-1$。
  3. 当 $n$ 不是质数且因子 $i$ 中包含质数 $prime_j$ 时,说明 $n$ 有质数的高次幂作为因子,所以此时 $\mu(n)=0$。
  4. 当 $n$ 不是质数且因子 $i$ 中不包含质数 $prime_j$ 时,说明 $i$ 与 $prime_j$ 互质;又因为莫比乌斯函数是积性函数,且第2条可以得到 $\mu(prime_j)=-1$,所以此时 $\mu(n)=-\mu(i)$。

由此 $O(n)$ 可以得到莫比乌斯函数的任意值。

莫比乌斯反演

反演结论 $$ \sum_{d\mid \gcd(i,j)}\mu(d) = [\gcd(i,j)=1] $$

  1. P2522 莫比乌斯反演+整除分块解法 AC代码
  2. SP5971 莫比乌斯反演+莫比乌斯函数关于狄利克雷卷积的性解法 AC代码

范德蒙德卷积

$$ \sum_{i=0}^{k} {n \choose i} {m \choose k-i}={n+m \choose k} $$

ABC397赛后总结

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-03-17 16:02:56

庆幸打了 unrated,惋惜没有做出E

A

B

C

D

发现直接枚举会爆时间和 long long,先优化精度问题。
发现所求可用立方差公式优化为 $(x-y)\cdot(x^2+xy+y^2)=n$。
能够得到 $(x-y)和(x^2+xy+y^2)$ 都是 $n$ 的因数,所以直接枚举因数 $c$,时间 $O(\sqrt n)$。
$x-y=c$,可得 $$ \begin{aligned} c\times((y+c)^2 + y(y+c) + y^2) &= n \ (y+c)^2 + y(y+c) + y^2 &= \frac n c \ 3y^2 + 3cy &= \frac n c - c^2 \ y^2 + cy &= \frac{\frac n c - c^2}{3} \end{aligned} $$ 得到 $y=\frac{-c + \sqrt{c^2+4t}}{2}$。
而又因为 $(x-y)$ 最多是 $\sqrt[3] n$,所以时间复杂度为 $O(\sqrt[3] n)$。

赛时没想到枚举到 $\sqrt[3] n$ 就可以,硬控半小时,最后还是试出来的。

E

树形DP可做。
发现要么是一条树链,要么是一条树上的路径,而且子树个数不大于2。
若子树个数为1,直接加;子树个数为2,两条链长度加1必为 $K$,转移很巧妙,见炜哥代码

赛时没做出来,惋惜痛恨之。

F

类似的题 CF833B
线段树优化DP $$ 令f_{i,j} 表示划分成i层,到第j个点的最大价值\ 易得转移方程f_{i,j}=\max_{k=1}^{j-1}{f_{i-1,k}+cnt_{k+1,j}} $$ 因为是max,所以可以考虑线段树维护这个东西,按 $i$ 枚举,每次到新的一层重新建树维护。
而F题就是 $K=3$ 时的结果。

赛时根本没看这道题,预估看了也写不出来。

题解:P6890 [CEOI 2006] Link

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-03-20 19:54:31

题解:P6890 [CEOI 2006] Link

题目传送门

题面

给定 $n$ 个点 $n$ 条有向边的内向基环树(不保证连通),要求添加最少的有向边使得点 $1$ 到达所有其它点的最短路长度不超过 $K$。

思路

首先考虑到内向基环树就是带着一个环的 DAG,所以如果有某个点入度为 $0$,肯定要从 $1$ 向它连边。

先把不是环上的点处理好,令 $f_i$ 表示从 $i$ 点还能走多少步满足性质,能够得到转移方程 $f_v = \max{f_v,f_u-1}$,并且发现,若 $f_u=0$,一定要从 $1$ 向它连边,可用拓扑处理。

对于环上的点,我们可以先扩散一遍,得到的 $f_u$ 实质上就是从 $u$ 点能跳到哪个点;对于所有的 $f_u=0$,必须连边并统计贡献。设环上有 $L$ 个点,能够得到单次查询的时间复杂度为 $O(\frac L k)$。

正常情况下我们要以环上的每一个满足 $f_u=0$ 点为起点进行跳跃,时间复杂度 $O(\frac {L^2} k)$。但是我们注意到,在这些满足条件的点中,每 $k$ 个就成一次循环,它们的决策实质上是一样的,所以我们可以只进行 $k$ 次查询,时间复杂度为 $O(\frac {L^2} k \times k)=O(L)$。这样我们就以 $O(n)$ 的复杂度做完了本题。

注意事项

  1. 题目中并未说明保证图连通,所以可能有多棵基环树,多个环,每个都要统计答案,注意处理。
  2. 初始状态 $f_1=k+1$,不要忘记。

代码

#include<bits\/stdc++.h>
#include<vector>
#include<queue>
#define int long long
#define F(i,l,r) for(int i=(l); i<=(r); i++)
using namespace std;
const int N = 1e6 + 50, INF=0x3f3f3f3f3f3f3f3f;

int n,K,cnt;
int ind[N], f[N];
int to[N];

inline void Topsort(){
	queue<int> q;
	F(i,1,n) if(!ind[i]){
		q.push(i);
		f[i] = K;
		cnt+=(i!=1);
	}
	f[1]=K+1;
	for(; q.size(); q.pop()){
		int u=q.front(), v=to[u];
		if(!f[u])cnt++, f[u]=K;
		f[v]=max(f[v],f[u]-1);
		ind[v]--;
		if(!ind[v]) q.push(v);
	}
}
inline int fun(int rt, int tot, int cir[]){
	int sum=0, res=0;
	for(int i=rt; sum<tot;){
		int u=cir[i];
		if(f[u]){
			sum+=f[u];
			i = (i+f[u]-1)%tot + 1;
		}else{
			res++;
			sum+=K;
			i = (i+K-1)%tot +1;
		}
	}
	return res;
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>K;
	F(i,1,n){
		int u,v; cin>>u>>v;
		to[u]=v;
		ind[v]++;
	}
	Topsort();
	F(i,1,n){
		if(ind[i]){
			vector<int> vec;
			
			for(int u=i,v; ind[u]; u=v){
				ind[u]=0;
				vec.push_back(u);
				v=to[u];
				f[v]=max(f[v],f[u]-1);
			}
			
			int cir[vec.size()+3]={0}, tot=0, Cnt=0, mi=INF;
			for(int e:vec) cir[++tot]=e;
			F(j,1,tot){
				if(f[cir[j]]) continue ;
				Cnt++;
				mi = min(mi, fun(j,tot,cir));
				if(Cnt==K) break;
			}
			cnt+=(mi==INF)?0:mi;
		}
	}
	cout<<cnt<<'\n';

	return 0;
}

另一种写法

#include<bits\/stdc++.h>
#include<vector>
#include<queue>
#define int long long
#define F(i,l,r) for(int i=(l); i<=(r); i++)
using namespace std;
const int N = 1e6 + 50, INF=0x3f3f3f3f3f3f3f3f;

int n,K,cnt;
int ind[N], f[N];
int cir[N],tot;
int to[N];

inline void Topsort(){
	queue<int> q;
	F(i,1,n) if(!ind[i]){
		q.push(i);
		f[i] = K;
		cnt+=(i!=1);
	}
	f[1]=K+1;
	for(; q.size(); q.pop()){
		int u=q.front(), v=to[u];
		if(!f[u])cnt++, f[u]=K;
		f[v]=max(f[v],f[u]-1);
		ind[v]--;
		if(!ind[v]) q.push(v);
	}
}
inline int fun(int rt){
	int sum=0, res=0;
	for(int i=rt; sum<tot;){
		int u=cir[i];
		if(f[u]){
			sum+=f[u];
			i = (i+f[u]-1)%tot + 1;
		}else{
			res++;
			sum+=K;
			i = (i+K-1)%tot +1;
		}
	}
	return res;
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>K;
	F(i,1,n){
		int u,v; cin>>u>>v;
		to[u]=v;
		ind[v]++;
	}
	Topsort();
	F(i,1,n){
		if(ind[i]){
			tot=0;
			for(int u=i,v; ind[u]; u=v){
				ind[u]=0;
				cir[++tot]=u;
				v=to[u];
				f[v]=max(f[v],f[u]-1);
			}
			
			int Cnt=0, mi=INF;
			F(j,1,tot){
				if(f[cir[j]]) continue ;
				Cnt++;
				mi = min(mi, fun(j));
				if(Cnt==K) break;
			}
			cnt+=(mi==INF)?0:mi;
		}
	}
	cout<<cnt<<'\n';

	return 0;
}

完结撒花!

题解:AT_abc404_f [ABC404F] Lost and Pound

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-05-08 15:53:38

题解:AT_abc404_f [ABC404F] Lost and Pound

题目传送门

题意

题面真的太粪了。

给定 $n$ 个按钮,包括 $1$ 个好的按钮和 $n-1$ 个坏的按钮。进行 $T$ 轮游戏,每一轮将按钮随机打乱,并按 $m$ 次按钮(同一按钮可以按多次)。$n$ 轮游戏结束后,若好的按钮被按的次数不小于 $K$ 次,获胜。求获胜的最大概率。

思路

每一轮游戏中,按钮都被随机打乱,我们不知道好的按钮在哪里,也不清楚每一个按钮会按几次,所以我们可以枚举(说了跟没说一样)。发现 $m\le 30$,所以枚举可能出现的局面,即数的拆分。搜索发现 $p(30)=5604$。

在枚举按钮过后,可用动态规划解决概率问题。
令 $f_{i,j}$ 表示到第 $i$ 轮,好的按钮至少被按了 $j$ 次的概率,初始状态为 $f_{i,0}=1$。
在枚举了局面后,令 $cnt_e$ 表示当前局面,同一按钮被按了 $e$ 次的次数,可以得到 $$ f_{i,j} = \max_{\text {局面}}{\sum_{e=0}^{m} f_{i-1,\max(j-e,0)} \cdot \frac{cnt_e}{n}} $$ 最终答案 $f_{T,K}$,时间复杂度 $O(T\times m \times K \times p(m))$。

代码

#include<bits\/stdc++.h>
#define int long long
#define db long double
#define fi first
#define se second
#define F(i,l,r) for(int i=(l); i<=(r); i++)
using namespace std;
const int N = 1e6 + 50, M = 1e3 + 50;
const int INF=0x3f3f3f3f3f3f3f3f, mod=1e9+7;

int n,T,m,K,I;
int tot,cnt[40];
db f[40][40];

inline void calc(){
	if(tot>n) return ;
	cnt[0]=n-tot;
	F(j,1,K){
		db sum=0;
		F(e,0,m)
			sum += f[I-1][max(j-e,0ll)] * cnt[e] \/ n;
		f[I][j] = max(f[I][j], sum);
	}
}
inline void DFS(int t,int lst){
	if(!t) calc();
	F(i,max(1ll,lst), t){
		tot++;
		cnt[i]++;
		DFS(t-i,i);
		cnt[i]--;
		tot--;
	}
}
inline void work(){
	f[I][0]=1;
	tot=0;
	DFS(m,0);
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>T>>m>>K;
	f[0][0]=1;
	F(i,1,T){
		I=i;
		work();
	}
	cout<<fixed<<setprecision(20)<<f[T][K]<<'\n';

	return 0;
}

完结撒花!

题解:P12665 [KOI 2023 Round 2] ZigZag

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-02 08:33:53

题解:P12665 [KOI 2023 Round 2] ZigZag

感谢

感谢@KSCD_大佬的讲解,他提供了本题的思路与优化方法。
感谢@Dtw_大佬,他提供了本题的部分代码。

题意

给定一个长度为 $n$ 的排列,定义函数 $f(x,y,z)$ 表示满足以下条件的最长子序列长度:

  1. 子序列中的元素均选自原序列中下标处于区间 $[y, z]$ 的部分。
  2. 子序列中所有的元素值都不超过 $x$。
  3. 子序列必须是一个之字形序列(Zigzag)

“之字形序列”$S$ 的定义为:

  • 如果 $S_i < S_{i+1}$,则有 $S_{i+1} > S_{i+2}$;
  • 如果 $S_i > S_{i+1}$,则有 $S_{i+1} < S_{i+2}$。

对于每个 $x$,你需要求出 $\sum_{y=1}^n \sum_{z=y}^n f(x,y,z)$。

思路

我们可以先考虑 $O(n)$ 计算单个 $f$ 的贡献。
定义有效点为区间内小于等于 $x$ 的点,那么我们选择的子序列中一定都是有效点(废话)。
贪心地想,我们在一个区间内,一定会选择最左端的有效点,最右端的有效点,和最顶端与最低端(结合图理解,这样一定不劣)的点。

图

但是由于要求 $f$ 函数的和,我们发现这样真的太慢了,于是考虑拆贡献
定义“有效点”表示区间内小于等于 $x$ 的点。
对于一个点 $p$,令它左右的有效点位置分别为 $l,r$,那么有以下几种情况:

  1. 当前区间内只有 $p$ 一个有效点,即左右端点满足大于 $l$ 且小于 $r$,即造成 $(i-l)\times (r-i)$ 的贡献;
  2. $p$ 为区间左端点,满足区间左端点 $l<L\le i$,右端点 $r\le R\le n$,造成 $(i-l)\times (n-r+1)$ 的贡献;
  3. $p$ 为区间右端点,与上条类似,造成 $l\times (r-i)$ 的贡献;
  4. $p$ 为顶端或低端的数,即满足 $[a_p > a_l]=[a_p>a_r]$ 时,造成 $l \times (n-r+1)$ 的贡献。

我们意外地发现,当新出现或减少一个有效点时,仅会对它左右的有效点 $l,r$ 的贡献造成影响,我们便可以通过改变 $x$ 每次新增或减少一个有效点,$O(n)$ 便可做出(具体咋做看实现)。

实现

我们可以枚举 $x$,用一个 set 维护当前有效点,那么我们新增一个有效点后,便可以通过 set 更改左右点的贡献,并加入其本身的贡献,时间复杂度 $O(n \log n)$。
代码(代码选自@Dtw_大佬):

#include <bits\/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5 + 10;

int n, a[N], pa[N];
ll ans;
set<int> q;

void add(auto p)
{
	int l = *prev(p), r = *next(p), i = *p;
	ans += 1ll * (i - l) * (r - i);
	ans += 1ll * l * (r - i);
	ans += 1ll * (n - r + 1) * (i - l);
  	bool o1 = (a[l] < a[i]), o2 = (a[i] > a[r]);
 	bool ok = o1 ^ o2;
 	if (!ok && l != 0 && r != n + 1) ans += 1ll * l * (n - r + 1);
}

void del(auto p)
{
	int l = *prev(p), r = *next(p), i = *p;
	ans -= 1ll * (i - l) * (r - i);
	ans -= 1ll * l * (r - i);
	ans -= 1ll * (n - r + 1) * (i - l);
  	bool o1 = (a[l] < a[i]), o2 = (a[i] > a[r]);
 	bool ok = o1 ^ o2;
 	if (!ok && l != 0 && r != n + 1) ans -= 1ll * l * (n - r + 1);
}

int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i], pa[i] = i;
	sort (pa + 1, pa + n + 1, [&](int x, int y) { return a[x] < a[y]; });
	set<int> q; q.insert(0), q.insert(n + 1);
	for (int i = 1; i <= n; i++) {
		auto p = q.upper_bound(pa[i]); p--;
		if (*p != n + 1) del(p);
		if (*next(p) != n + 1 && *p != n + 1) del(next(p));
		q.insert(pa[i]); p = q.find(pa[i]);
		if (*prev(p) != 0) add(prev(p));
		add(p);
		if (*next(p) != n + 1) add(next(p));
		cout << ans << "\n";
	}
	return 0;
}

别急,还有线性的做法。
我们可以先线性预处理出 $x=n$ 时的总贡献,然后用双向链表维护每个点的左右有效点,倒序枚举 $x$,然后删贡献(与上一做法类似),就可以省去 set 内二分的对数复杂度。
时间复杂度 $O(n)$。

代码(这份是我自己的,目前是最优解):

#include<bits\/stdc++.h>
#include<set>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
#define F(i,l,r) for(int i=(l); i<=(r); i++)
using namespace std;
const int N = 2e5 + 50, M = 1e3 + 50;
const int INF=0x3f3f3f3f3f3f3f3f, mod=1e9+7;

int n,ans;
int res[N];
int a[N],pos[N];
int pre[N],nxt[N];

inline void change(int p,int f){
	int l=pre[p],r=nxt[p];
	int res=0;
	if(l==-1 || r==-1) res=p*(n-p+1);
	else{
		res = (p-l)*(r-p) + l*(r-p) + (p-l)*(n-r+1);
		if((a[p]>a[l])==(a[p]>a[r])) res += l*(n-r+1);
	}
	ans += f*res;
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	F(i,1,n) cin>>a[i];
	F(i,1,n){
		pos[a[i]]=i;
		pre[i]=i-1,nxt[i]=i+1;
		ans+=n;
		if(i!=1&&i!=n && (a[i]>a[i-1])==(a[i]>a[i+1])) ans+=(i-1)*(n-i);
	}
	pre[1]=nxt[n]=-1;
	res[n]=ans;
	for(int x=n-1; x; x--){
		int p=pos[x+1];
		int l=pre[p], r=nxt[p];
		
		if(l!=-1) change(l,-1);
		if(r!=-1) change(r,-1);
		
		if(l!=-1) nxt[l]=r;
		if(r!=-1) pre[r]=l;
		change(p,-1);
		
		if(l!=-1) change(l,1);
		if(r!=-1) change(r,1);
		
		res[x]=ans;
	}
	F(i,1,n) cout<<res[i]<<'\n';

	return 0;
}

好久没写题解了,希望审核大大 一遍 两遍过,拜谢了。

题解:P10753 [COI 2023] Bliskost

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-13 19:21:58

建议难度评为 $\color{orange}橙$ 或 $\color{yellow}黄$。

题意

给定两长度为 $n$ 的字符串 $s,t$,判断它们是否相近(指可以通过若干次操作使得 $s,t$ 相等),并给定 $Q$ 次修改,每次修改 $s$ 的一个字符,然后再判断是否相近。

对于每次操作,选择一个字符串 $s$ 和位置 $p$,将 $s_p$ 和 $s_{p+1}$ 在字母表上前移一位。

思路

可以发现,将所有操作集中到一个字符串可以使 $s,t$ 相等时,那么 $s,t$ 相近。
注意到,对于 $s_1$,通过操作使其变成 $t_1$ 的操作次数为 $t_1-s_1$,次数对 $26$ 取模。令这个次数为 $a_1$,那么 $s_2$ 便已经进行了 $a_1$ 次操作,需再进行 $t_2-s_2-a_1$ 次操作。以此类推,可求出 $a_n$。
第 $n$ 位是字符串的最后一位,它不可能进行操作,所以当且仅当 $a_n=0$ 时,两字符串相近。对于每次修改,可根据其与 $n$ 的位置差直接修改 $a_n$,进行 $O(1)$ 判断是否相近。

时间复杂度 $O(n+Q)$。

代码

#include<bits\/stdc++.h>
#define int long long
#define F(i,l,r) for(register int i=(l); i<=(r); ++i)
using namespace std;
const int N= 1e6 +50;

int n,Q;
int a[N];
string s,t;

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>Q>>s>>t;
	F(i,1,n){
		a[i]=(t[i-1]+26-s[i-1])%26;
		a[i]-=a[i-1];
		if(a[i]<0) a[i]+=26;
	}
	a[n]%=26;
	cout<<(a[n]?"ne\n":"da\n");
	F(i,1,Q){
		int p;char c; cin>>p>>c;
		int t=c-s[p-1];
		s[p-1]=c;
		a[n]+=t*((n-p)&1?1:-1);
		a[n]%=26;
		cout<<(a[n]?"ne\n":"da\n");
	}
	
	return 0;
}
共 20 篇博客