T1:路径问题(二)
特殊的图,每个点的出度为 \(1\),有向图
一些普遍的图论算法可能不好用
注意题目本身的条件的特殊性,环不需要依靠通用的很复杂的算法,我们可以直观暴力地找
环上的点,它们的答案其实就是环的大小
环外的点,答案为环大小+到环的距离
其实就是记忆化
从 \(v\) 出发,不停走后继,如果一路上都是答案不确定的点,那么我们就 \(O(n)\) 地处理所有遇到的点的答案
如果走着走着遇到一个已经知道答案的点?
利用这个答案就好了
代码实现
#include <bits/extc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using std::cin;
using std::cout;
using std::vector;
using ll = long long;
int main() {
int n;
cin >> n;
vector<int> t(n);
rep(i, n) cin >> t[i], t[i]--;
vector<int> ans(n);
vector<int> memo(n, -1);
int cnt = 0;
vector<int> ord(n);
rep(v, n) {
if (ans[v] == 0) { // 这是一个不知道 ans 的点,有必要从它出发走一走看看
int u = v;
vector<int> s;
while (memo[u] == -1) {
memo[u] = cnt; // 记录下来在哪一批处理掉
ord[u] = s.size(); // 记录下来这个点是一路上第几个遇到的,方便后面求环的大小
s.push_back(u); // 记录一路下来我们遇到了哪些点
u = t[u];
}
// 接下来保证了 memo[u]>=0,分两类,这个点是我们新遇到的一个环,还是一个以前已经处理过的点
// 1. 我们新遇到的一个环上的一个点
if (memo[u] == cnt) {
int c = s.size()-ord[u];
for (int i = ord[u]; i < s.size(); ++i) ans[s[i]] = c; // 先把环上的点处理好
rep(i, ord[u]) ans[s[i]] = c+(ord[u]-i);
}
// 2. 一个以前处理过的新点
else {
rep(i, s.size()) ans[s[i]] = ans[u]+(s.size()-i);
}
cnt++;
}
}
rep(v, n) cout << ans[v] << '\n';
return 0;
}
T2: 平整序列(二)
操作分为两步:
- 选择 \(m\) 个数,任意修改它们的数值
- 区间修改
目标是把所有数变成 \(0\)
当 \(m = 0\)时,其实就是 铺设道路
回到本题,\(m\) 次修改,怎么办?
把握好以下几点:
- \(n\) 只有 \(300\),复杂度可能是 \(O(n^3)\)
- 修改数字的时候,如果把一个数改成和相邻的数一样大?其实效果上相当于删掉了一个数。
- \(m\) 次修改,假设答案为 \(ans'\)
\(ans \leqslant ans'\)
\(ans \geqslant ans'\)
这里需要一点数学证明,效果上,修改最多相当于删除
\(n\) 个数,选择 \(m\) 个数把它们删了,使得 \(\sum \max(0, a_i-a_{i-1})\) 最小
可以考虑 \(dp\)
记 dp[i][j] 表示前 \(i-1\) 个数里面,删了 \(j\) 个数时 \(\sum \max(0, a_i-a_{i-1})\) 最小值
最后的答案就是 \(dp[n+1][m]\)
转移方程:
\( dp[i][j] = \min\{dp[i-1-k][j-k]+\max(0, a_i-a_{i-1-k})\} \)
特例:\(j = i-1\) 时,\(dp[i][j] = \max(0, a_i-a_0)\)
代码实现
#include <bits/extc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using std::cin;
using std::cout;
using std::max;
using std::vector;
using ll = long long;
const ll INF = 1e18;
inline void chmin(ll& x, ll y) { if (x > y) x = y; }
const int MX = 305;
ll dp[MX][MX];
int main() {
int n, m;
cin >> n >> m;
vector<int> a(n+2);
rep(i, n) cin >> a[i];
rep(i, n+1) {
for (int j = 0; j <= i-1; ++j) {
if (j == i-1) dp[i][j] = max(0, a[i]-a[0]);
else {
ll now = INF;
for (int k = 0; k <= j; ++k) {
chmin(now, dp[i-1-k][j-k] + max(0, a[i]-a[i-1-k]));
}
dp[i][j] = now;
}
}
}
cout << dp[n+1][m] << '\n';
return 0;
}
T3:方形最大值
本题是“求一个区域的最值”的二维版本
一维版本:
给定 \(n\) 个数 \(a_1, a_2, \cdots, a_n\) 和一个整数 \(k\),要求所有连续 \(k\) 个数的最值
显然是用单调队列做
以最大值为例
如果 \(a_j\) 在 \(a_i\) 右边,且 \(a_j > a_i\),那么我们考虑所有右端点大于等于 \(j\) 的长度为 \(k\) 的区间。这些区间的最大值是不用考虑 \(a_i\)
所以我们的考虑内容应该是一个向右递减的队列
怎么变到二维?
\(b_{ij}\) 表示 \(a_{i,j-k+1}, \cdots, a_{ij}\) 这 \(k\) 个数里的最大值,\(j \geqslant k\)
那么我们只需要对 \(b_{i-k+1, j}, \cdots, b_{ij}\) 再求一次最大值
就是 \(ans[i][j]\)
代码实现
#include <bits/extc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using std::cin;
using std::cout;
using std::vector;
using std::deque;
inline void read(int &x) {
x = 0;
char c = getchar();
while (c < '0' or c > '9') {
c = getchar();
}
while ('0' <= c and c <= '9') {
x = (x<<3) + (x<<1) + (c^48);
c = getchar();
}
}
struct Node {
int id; // 代表第几个数
int x;
};
deque<Node> q;
int main() {
int n, k;
cin >> n >> k;
vector a(n, vector<int>(n));
rep(i, n)rep(j, n) read(a[i][j]);
// b[i][j] 表示 a[i][j-k+1],...,a[i][j] 这连续 k 个数的最大值(横着)
vector b(n, vector<int>(n));
// 用单调队列的思想处理出 b 数组
rep(i, n) {
rep(j, k) { // 单调队列的前 k 个数
while (q.size() and q.back().x < a[i][j]) q.pop_back();
q.push_back({j, a[i][j]});
}
b[i][k-1] = q.front().x;
for (int j = k; j < n; ++j) {
while (q.size() and q.back().x < a[i][j]) q.pop_back();
q.push_back({j, a[i][j]});
if (q.front().id+k <= j) q.pop_front();
b[i][j] = q.front().x;
}
q.clear();
}
// c[i][j] 表示 b[i][j-k+1],...,b[i][j] 这连续 k 个数的最大值(竖着)
vector c(n, vector<int>(n));
// 用单调队列的思想处理出 c 数组
rep(j, n) {
rep(i, k) { // 单调队列的前 k 个数
while (q.size() and q.back().x < b[i][j]) q.pop_back();
q.push_back({i, b[i][j]});
}
c[k-1][j] = q.front().x;
for (int i = k; i < n; ++i) {
while (q.size() and q.back().x < b[i][j]) q.pop_back();
q.push_back({i, b[i][j]});
if (q.front().id+k <= i) q.pop_front();
c[i][j] = q.front().x;
}
q.clear();
}
for (int i = k-1; i < n; ++i) {
for (int j = k-1; j < n; ++j) {
cout << c[i][j] << " \n"[j == n-1];
}
}
return 0;
}
T4:零的数量
本题可以直接做,暴力地去算:个位 \(0\) 有多少个,十位 \(0\) 有多少个,\(\cdots\)
法一:暴力
个位0:\(\lfloor\frac{n}{10}\rfloor\)
中间的位置0:根据 \(n\) 的这一位是否为 \(0\),分类讨论,如果不为 0,则简单;如果为 0,则前面的位置填到 \(n\) 的极限的时候,要特殊处理一下
代码实现
#include <bits/extc++.h>
using std::cin;
using std::cout;
using ll = long long;
int main() {
ll n;
cin >> n;
// 接下来一位一位地考虑
// 我们用 m=1 的地方,表示我们现在处理的是哪一位
// 比如 m=10,表示我们在处理十位的0,m=1 表示我们在处理个位的0
ll ans = 0;
for (ll m = 1; m <= n/10; m *= 10) {
if (m == 1) ans += n/10;
else {
ll a = n/m, b = n%m; // 把 n 根据这一位拆成两半,a 的最低位就是 m 考虑的数位
// 判断 n 的这一位是否为0
if (a%10 == 0) {
ans += (a/10-1)*m + b + 1;
}
else {
ans += a/10*m;
}
}
}
cout << ans << '\n';
return 0;
}
法二:数位dp
dfs(int pos, bool prezero, bool limit) 的返回值,表示前 \(pos\) 个位置已经填完了余下的位置任意填,可以发现的 0 的个数,prezero == true 表示前面填的都是 \(0\),也就是前导 \(0\)
limit == true 表示前面填的东西都是 \(n\) 的东西
代码实现
#include <bits/extc++.h>
using std::cin;
using std::cout;
using std::string;
using ll = long long;
ll dp[20], ten[20]; // dp[i] 表示 i 个 0 到 i 个 9 中 0 的个数,dp[3] 表示 000~999 中 0 的个数
string s;
int len;
ll n;
ll dfs(int pos, bool prezero, bool limit) {
// 先处理最普通的情况
if (prezero == false and limit == false) {
return dp[len-pos];
}
if (pos == len-1) {
if (prezero) return 0;
else return 1;
}
ll res = 0;
if (prezero) {
// 1. 下一位继续填 0
res += dfs(pos+1, true, false);
// 2. 下一位不填 0
res += 9*dfs(pos+1, false, false);
}
if (limit) {
// 前面填到了极限,考虑一下 n 的这一位是不是 0
if (s[pos] == '0') {
res += n%ten[len-pos-1]+1;
res += dfs(pos+1, false, true);
}
else {
// 填 0
res += ten[len-pos-1];
res += dfs(pos+1, false, false);
// 填 1~9 中的非极限数字
res += (s[pos]-'0'-1)*dfs(pos+1, false, false);
// 填极限数字
res += dfs(pos+1, false, true);
}
}
return res;
}
int main() {
cin >> s;
len = s.size();
if (len == 1) {
puts("0");
return 0;
}
ten[0] = 1;
for (int i = 1; i <= len; ++i) {
ten[i] = ten[i-1]*10;
n = n*10 + (s[i-1]-'0');
dp[i] = i*ten[i-1];
}
// 枚举 n 的最高位的取值
ll ans = dfs(1, true, false);
for (int i = 1; i <= s[0]-'0'; ++i) {
if (i == s[0]-'0') ans += dfs(1, false, true);
else ans += dfs(1, false, false);
}
cout << ans << '\n';
return 0;
}
浙公网安备 33010602011771号