随题记录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\) 而言:
我们可以把前缀和插入动态开点权值线段树来求。
时间,空间复杂度 \(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;
}