随题记录20230915

\(\text{Luogu5338}\)

题目描述

现在一个字符串 \(S_1\),你需要构造一个长度为 \(n\) 的字符串 \(S_2\)。这个字符串 \(S_2\) 需要满足字符串 \(S_1\) 中相邻的字符不可以在 \(S_2\) 中相邻。所有字符只包含小写字母。

\(n \leqslant 10^{15} ,|s_1| \leqslant 10^5\)

思路点拨

比较简单的一题。

考虑一个 \(O(26^2n)\) 的暴力 \(dp\) 。我们定义状态 \(f_{i,j}\) 表示目前填到 \(S_2\) 的第 \(i\) 个字符,这个字符为 \(j\) 的方案数。转移可以从 \(f_{i-1,k}\) 转移,如果 \(j,k\) 可以相邻得话。

发现我们从 \(i-1\)\(i\) 这个位置转移得时候,这个操作是可以使用矩阵优化的。所以我们可以构造一个矩阵进行递推加速,矩阵就不细讲了,很套路。

时间复杂度 \(O(26^3\log n)\)

\(\text{Luogu5340}\)

题目描述

现在有一个 \(n\) 个点,\(m\) 条边的无向加权图,每一个节点有一个点权 \(v_i\) ,保证 \(v_i \in \{1,-1\}\) 。你需要寻找一条从 \(u\)\(v\) 的道路,满足这条道路每时每刻走过点的点权和在 \([-k,k]\) 之间,并且尽可能短。输出这个最短距离。

\(n \leqslant 10^4 ,m \leqslant 10^5 ,k \leqslant 10\)

思路点拨

还是比较简单一题。

我们定义状态 \(f_{i,j}\) 表示目前在节点 \(i\) ,经过节点的权值和为 \(j\) 的最短距离, \(j \in [-k,k]\)

每一次转移可以使用刷表,用节点 \(i\) 去更新直接相邻的节点 \(to\)\(f_{to,j+v_i}=\min\{f_{to,j+v_i} , f_{i,j}+w\}\) 。因为这样的松弛操作要进行 \(n\) 次,所以时间复杂度为 \(O(n^2k)\) ,不可以通过。

我们可以使用类似于 \(\text{Dijkstra}\) 的,用堆优化转移,时间复杂度 \(O(nk \log n)\)

\(\text{Luogu2804}\)

题目描述

给定 \(n,m\) 和一个序列 \(a\) ,问序列 \(a\) 中有多少个连续段平均数大于 \(m\)

\(n \leqslant 2\times10^5\)

思路点拨

比较套路。我们将序列 \(a\) 全部减去 \(m\) ,接下来我们想要知道有多少个区间和为正数。

我们考虑一个区间和的计算方式,就是可以使用前缀和 \(sum_r-sum_{l-1}\) 。每一次我们枚举 \(r\) ,使用某种数据结构求出有多少个 \(l\) 满足条件。对于一个 \(r\) 而言:

\[sum_r-sum_{l-1}>0 \]

\[sum_{l-1}\leqslant sum_r-1 \]

我们可以把前缀和插入动态开点权值线段树来求。

时间,空间复杂度 \(O(n \log W)\)

\(\text{Luogu8019}\)

题目描述

给定一个长度为 \(n\) 的序列 \(a_1, a_2, \cdots, a_n\),请将它划分为 \(m\) 段连续的区间,设第 \(i\) 段的费用 \(c_i\) 为该段内所有数字的异或和,则总费用为 \(c_1 \operatorname{or} c_2 \operatorname{or} \cdots \operatorname{or} c_m\)。请求出总费用的最小值。

\(n \leqslant 5\times 10^5\)

思路点拨

比较好玩的脑子题。

看到希望或的和最小,我们可以从高位往低位贪心。那么我们就希望对于二进制位 \(bit\) ,一段中该位为 \(1\) 的数的个数是偶数。

我们考虑如果一段内的该位为 \(1\) 的数的数量刚好为偶数的时候就断开,这样每一段该位的异或都是 \(0\) ,并且段数尽量多。你可能会问,如果段数比 \(m\) 大怎么办?我们可以认为合并一些段,异或和不会变。

这样贪心还有一个小问题,就是不可以将原来不可以分开的段分开。我们可以给不可与分开 (两个二进制位为 \(1\) 的数之间) 打上标记,每一次划分的时候不可以划到有标记的位置的头上。

比较抽象,可以看代码理解(很短):

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5e5+10;
int n,m,a[MAXN];
bool vis[MAXN];
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int ans=0;
	for(int j=60;j>=0;j--){
		int flag=0,sum=0;
		for(int i=1;i<=n;i++){
			if(a[i]&(1ll<<j)) flag^=1;
			if(!flag&&!vis[i]) sum++;
		}
		if(flag||sum<m) ans+=(1ll<<j);
		else{
			for(int i=1;i<=n;i++){
				if(a[i]&(1ll<<j)) flag^=1;
				if(flag&&!vis[i]) vis[i]=1;
			}
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2023-09-15 17:27  Diavolo-Kuang  阅读(15)  评论(0编辑  收藏  举报