Educational Codeforces Round 103 (Rated for Div. 2)部分題解(A,B,C,D)
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
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]
程式碼
#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;
}