oj: CodeForces
叙述采用倒叙
D. Journey(dp)
oj: CodeForces
题意
有 n + 1 n+1 n+1 个点和 n n n 条边组成的一条链,链上的边只能有一个朝向(向左或者向右)。
有个旅行者从一个点出发后只能顺着边的朝向移动,不过每当他移动一格,所有边的朝向都会改变(朝左的变成朝右,朝右的变成朝左)。
求出旅行者分别在每一个点出发能够走到的最多的点数。
题解
定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] :
当 i i i 为 0 0 0 时表示只向左移动,为 1 1 1 时表示只向右移动。
j j j 为 0 0 0 时表示边是初始朝向,为 1 1 1 时表示边的朝向发生了改变。
k k k 表示以第 k k k 个点为起点。
d p dp dp 值表示第 k k k 个点只向左(或者只向右,取决于 i i i )能够走到的最多的点数。
边界:
所有的点都不移动,即走过的点只有自己: d p [ i ] [ j ] [ k ] = 1 dp[i][j][k] = 1 dp[i][j][k]=1 。
转移:
考虑旅行者只向左移动时(即 i = 0 i=0 i=0 ),当左邻边朝向左,旅行者只能在朝向未改变时通过。当左邻边朝向右,旅行者只能在朝向改变时通过。
所以有:
if(s[i - 1] == 'L') dp[0][0][i] += dp[0][1][i - 1];
else dp[0][1][i] += dp[0][0][i - 1];
考虑旅行者只向右移动时(即 i = 1 i=1 i=1 ),当右邻边朝向左,旅行者只能在朝向改变时通过。当右邻边朝向右,旅行者只能在朝向未改变时通过。
所以有:
if(s[i] == 'R') dp[1][0][i] += dp[1][1][i + 1];
else dp[1][1][i] += dp[1][0][i + 1];
因为向左和向右时我们都计算了本身的贡献,所以第 i i i 位的答案应该是 d p [ 0 ] [ 0 ] [ i ] + d p [ 1 ] [ 0 ] [ i ] dp[0][0][i]+dp[1][0][i] dp[0][0][i]+dp[1][0][i] -1。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n"
using namespace std;
typedef long long LL;
const int maxn = 300005;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
char s[maxn];
LL dp[2][2][maxn];
void init() {
_for(j, 2) _for(i, n + 3) dp[j][0][i] = dp[j][1][i] = 0;
}
void sol() {
init();
scanf("%s", s + 1);
_rep(i, 1, n + 1) dp[0][0][i] = dp[0][1][i] = 1;
_rep(i, 2, n + 1) {
if(s[i - 1] == 'L') dp[0][0][i] += dp[0][1][i - 1];
else dp[0][1][i] += dp[0][0][i - 1];
}
_rep(i, 1, n + 1) dp[1][0][i] = dp[1][1][i] = 1;
for(int i = n; i >= 1; --i) {
if(s[i] == 'R') dp[1][0][i] += dp[1][1][i + 1];
else dp[1][1][i] += dp[1][0][i + 1];
}
_rep(i, 1, n + 1) printf("%lld ", dp[0][0][i] + dp[1][0][i] - 1);
printf("\n");
}
int main() {
int T = read();
_for(i, T) {
n = read();
sol();
}
return 0;
}
C. Longest Simple Cycle(dp)
oj: CodeForces
题意
给你多干条链,每条链有三个属性a,b,c。
c表示这条链有c个点。
a表示这条链的第1个点和上一条链的第a个点连在一起。
b表示这条链的最后一个点和上一条链的第b个点连在一起。
如图所示:
求出图中的最长简单环。
如图:
题解
定义dp[i]为第i条链能从前面获得的最大贡献(包含第i条链连向上一条链的两个边,不包含自身的长度)。
当我们考虑第i条链能形成的最长的环时,第i条链本身以及连上上一条链的两个边一定会被计算在内。剩下的有两种情况,其一是计算a和b之间的长度形成闭环。所以有:
dp[i] = 2 + abs(b[i] - a[i]);
其二是通过a和b两端进入更前面的链形成闭环。不过需要注意的是当a=b时,我们无法延伸到更前面的链,当i为1时,前面也没有链可供延伸。所以有:
if(a[i] != b[i] && i > 1)
dp[i] = max(dp[i], 2 + dp[i - 1] + a[i] - 1 + c[i - 1] - b[i] - max(0, a[i] - b[i]) * 2);
这两种情况对后续的连法无任何影响,所以我们取最大值即可。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
typedef long long LL;
const int maxn = 100005;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
LL a[maxn], b[maxn], c[maxn];
LL dp[maxn];
void init() {
_for(i, n) dp[i] = 0;
}
void sol() {
init();
_for(i, n) c[i] = read();
_for(i, n) a[i] = read();
_for(i, n) b[i] = read();
LL ans = 0;
for(int i = 1; i < n; ++i) {
dp[i] = 2 + abs(b[i] - a[i]);
if(a[i] != b[i] && i > 1) dp[i] = max(dp[i], 2 + dp[i - 1] + a[i] - 1 + c[i - 1] - b[i] - max(0, a[i] - b[i]) * 2);
ans = max(ans, c[i] - 1 + dp[i]);
}
printf("%lld\n", ans);
}
int main() {
int T = read();
_for(i, T) {
n = read();
sol();
}
return 0;
}
B. Inflation
oj: CodeForces
题意
有一件商品每个月都会涨价,初始价格是 p 0 p_0 p0 ,之后每个月都会涨 p i p_i pi 。
每次涨价的通货膨胀系数是 p i ∑ j = 0 i − 1 p j × 100 \frac{p_i}{\sum_{j=0}^{i-1}{p_j}}\times 100 ∑j=0i−1pjpi×100 。
你需要通过增加一些 p i p_i pi 使得每次涨价的通货膨胀系数都不超过 k k k 。
题解
考虑到后面的涨价对前面的没有影响,前面的涨价对后面的有影响,所以我们从后往前枚举。
把 ∑ j = 0 i − 1 p j \sum_{j=0}^{i-1}{p_j} ∑j=0i−1pj 统称为 s u m sum sum 。由于影响 p i p_i pi 的因素只有 s u m sum sum ,而且我们只关心 s u m sum sum 的大小,而不关心具体的分配方式,所以我们利用 p i p_i pi 求出所需的最小的 s u m sum sum ,在和实际有的前缀和做差,这个差值就是我们需要在前面补上的最小的涨价数(差值为 0 0 0 或小于 0 0 0 就不补)。
针对每一个 p i p_i pi 都求出一个差值,所有的差值取最大值就是答案。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
typedef long long LL;
const int maxn = 1005;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n, k;
LL a[maxn], d[maxn];
void sol() {
_for(i, n) a[i] = read();
d[0] = a[0];
for(int i = 1; i < n; ++i) d[i] = d[i - 1] + a[i];
LL ans = 0;
for(int i = 1; i < n; ++i) {
ans = max(ans, (a[i] * 100 + k - 1) / k - d[i - 1]);
}
printf("%lld\n", ans);
}
int main() {
int T = read();
_for(i, T) {
n = read(), k = read();
sol();
}
return 0;
}
A. K-divisible Sum
oj: CodeForces
题意
把若干个 k k k 分成 n n n 份,每份都不为 0 0 0 ,求出最小的最大块。
题解
先求出能保证每份都不为 0 0 0 的最小的 k k k 的倍数作为 k k k ,然后判断 k k k 能否被 n n n 整除即可。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int main() {
int T = read();
_for(i, T) {
LL n = read(), k = read(), t = k;
if(k % n) k = (n + k - 1) / k * k;
if(k % n == 0) printf("%d\n", k / n);
else printf("%d\n", k / n + 1);
}
return 0;
}