AtCoder 419-E 题解
\(\color{black}{题目传送门}\)
题面:
给你一个长度为 \(N\) 的数组 \(A\) 和两个数字 \(M,L\),你可以做以下操作若干次,最后你要使得数组 \(A\) 中的所有长度为 \(L\) 的连续子序列(子串)的和都是 \(M\) 的倍数
- 将 \(A\) 中的任意一个位置上的值增加 \(1\)
你需要求出最少操作次数。
\(1 \leq N,M \leq 500\)
\(1 \leq L \leq N\)
\(0 \leq A_i < M\)
\(\color{white}{别忘下看了啊啊啊!!!脑袋会爆!!!}\)
思路
1、转换
先说结论:
当且仅当以下两个条件同时成立,才能满足数组 \(A\) 中的所有长度为 \(L\) 的连续子序列(子串)的和都是 \(M\) 的倍数。
1、\((A_1+A_2+A_3+...+A_{L}) \mod m = 0\)
2、对于所有的\(i (L+1 \leq i \leq N)\) ,都要满足: \(\left |(A_i - A_{i-L}) \right | \mod m = 0\)
原因:如果满足这两个条件,可以用数学归纳法证明数组 \(A\) 中的所有长度为 \(L\) 的连续子序列(子串)的和都是 \(M\) 的倍数。
首先\((A_1+A_2+A_3+...+A_{L}) \mod m = 0\) 是肯定的,(条件1就是这个)。
接着,我们假设\((A_k+A_{k+1}+A_{k+2}+...+A_{k+L-1})\)是 \(M\) 的倍数
根据条件2,\((A_k+A_{k+1}+A_{k+2}+...+A_{k+L-1}) + (A_{k+L} - A_k)\) 是 \(M\) 的倍数,将\((A_k+A_{k+1}+A_{k+2}+...+A_{k+L-1}) + (A_{k+L} - A_k)\)化简得到\(A_{k+1}+A_{k+2}+...+A_{k+L}\)
证毕
2、做法
部分1:预处理
注意到条件2。
我们可以先把所有的 \(A_i\) 按照 \(i \mod L\) 进行分组,然后由于条件2,所以最终要求每一组中 各个数字\(\mod m\)都要相等。
所以我们可以先预处理:记$cost_{i,j} (i < l, j < m) $表示:使第 \(i\) 组的所有的数字\(\mod m\)都等于 \(j\) 需要多少次操作。
部分2:动态规划做法(正推)
用动态规划解决:\((A_1+A_2+A_3+...+A_L) \mod m = 0\)的最少操作次数(\(A_i\)根据\(A_{i-L}\)而变化,变化的操作次数同样要算在\(dp\)里面)
(1)状态
\(dp_{i,j}\) 表示使 \((A_1+A_2+A_3+...+A_i) \mod m = j\) 的最少操作次数。
(2)转移
首先容易知道:如果 \((A_1+A_2+A_3+...+A_i) \mod m = j\),且\(A_{i+1} \mod m = k\) ,那么 \((A_1+A_2+A_3+...+A_{i+1}) \mod m = (j + k) \mod m\)
并且还能知道,把 \(A_{i+1}\) 所在组全部转换为 \(k\) , 花费是 \(cost_{i,k}\)
而把前\(i\)个值的和转换为 \(\mod j = 0\) 还需要花费 \(dp_{i,j}\)
注意:由于可能有多个方法转换为某一个相同位置,所以要尝试更新而不是直接更新。
转移:
(3) 初始化
由于求最小值,初始化为正无穷
但是把0个值的和转换为\(\mod m = 0\)明显是不需要操作即可实现的。
所以:\(dp_{0,0} = 0\)
(3)答案
答案是\((A_1+A_2+A_3+...+A_L) \mod m = 0\)的最少操作次数,明显就是\(dp_{l,0}\)
(4)伪代码
于是我们就可以写出(伪)代码:
memset(dp, 0x3f); // 初始化,把dp全部设成无穷大
dp[0][0] = 0; // 初始化,把0个值的和转换为mod m = 0明显是不需要操作即可实现的。
for (int i : 0 ~ L) // 只需要计算A的前L个值的和转换成 mod m = 0即可
for (int j : 0 ~ M) // 表示A的前i个值 mod m = j , 由于是 mod m = j,所以j一定只能是 0~m
for (int k : 0 ~ M) // 枚举 A i+1(及其所在组)转换成 mod m = 几
int t = (j + k) % m; // 计算加上 A i+1 之后产生 前i+1个的和值(mod m)
dp[i + 1][t] = min(dp[i + 1][t], cost[i][k] + dp[i][j]); // 尝试更新前i+1个的和值(mod m = t)的最少方案数
cout << dp[l][0] << endl; // 输出把前L个值的和转换成 mod m = 0 的最少操作数
总代码
#include <iostream>
#include <vector>
#include <string.h>
using namespace std;
int a[501]; // 输入
vector<int> v[501]; // 用于分组
long long cost[501][501], dp[501][501]; // cost 为预处理出来的使各组的所有的数字 \mod m 都等于每个值所分别需要的操作次数,dp为动态规划所用到的数组,具体见思路板块。
int main()
{
int n, m, l;
cin >> n >> m >> l;
for (int i = 0; i < n; i++)
{
cin >> a[i];
v[i % l].push_back(a[i]); // 分组
}
for (int i = 0; i < l; i++) // 枚举组
{
for (int j = 0; j < m; j++) // 枚举转换成哪个数字
{
for (int x : v[i]) // 对于这组的每个数字
{
cost[i][j] += (j + m - x) % m; // 预处理一下代价
}
}
}
memset(dp, 0x3f, sizeof(dp)); // 初始化,把dp全部设成无穷大
dp[0][0] = 0; // 初始化,把0个值的和转换为mod m = 0明显是不需要操作即可实现的。
for (int i = 0; i < l; i++) // 只需要计算A的前L个值的和转换成 mod m = 0即可
{
for (int j = 0; j < m; j++) // 表示A的前i个值 mod m = j , 由于是 mod m = j,所以j一定只能是 0~m
{
for (int k = 0; k < m; k++) // 枚举 A i+1(及其所在组)转换成 mod m = 几
{
int t = (j + k) % m; // 计算加上 A i+1 之后产生 前i+1个的和值(mod m)
dp[i + 1][t] = min(dp[i + 1][t], cost[i][k] + dp[i][j]); // 尝试更新前i+1个的和值(mod m = t)的最少方案数
}
}
}
cout << dp[l][0] << endl; // 输出把前L个值的和转换成 mod m = 0 的最少操作数
return 0;
}
完结撒花! ★☆(v)/★

浙公网安备 33010602011771号