T1
中文题意:
T组输入,给你n个盘子,你要向里面放苹果,使得盘子中苹果总数是m的倍数。并且盘子内最多的苹果最小值是多少。并且一定要保证每个盘子都有苹果。
n
,
m
≤
1
0
9
n,m\leq10^9
n,m≤109。
如果
n
≤
m
n\leq m
n≤m,那么直接平铺盘子,直到满足倍数关系,答案就是
m
/
n
+
(
m
%
n
=
=
0
)
m/n+(m\%n==0)
m/n+(m%n==0),后面加法就看要不要额外补充。
如果
n
>
m
n>m
n>m,我们调整m是第一个大于n的倍数即可。
m
=
m
∗
c
e
l
i
(
1.0
∗
n
/
m
)
m=m*celi(1.0*n/m)
m=m∗celi(1.0∗n/m)。
void solve() {
n = read(), m = read();
if (n > m) m = m * ceil(1.0 * n / m);
print(m / n + (m % n != 0));
}
T2
中文题意:
T组输入,每组输入给出长度为n的序列(下标从0开始)。以及一个整数m。从第一个点开始存在超标程度,并且
b
i
=
a
i
∑
j
=
0
i
a
j
b_i=\frac{a_i}{\sum\limits_{j=0}^ia_j}
bi=j=0∑iajai。现在你要使得全部点的超标程度都小于等于
m
100
\frac{m}{100}
100m。你可以在任意一个位置把
a
i
a_i
ai变大一点,问你改变的总和最少需要多少。
n
,
m
≤
100
,
a
i
≤
1
0
9
n,m\leq 100, a_i\leq10^9
n,m≤100,ai≤109。
观察发现,从下标为1到下标为n-1都有可能做为分子,既然这样为什么我们不把从来不作为分子的 a 0 a_0 a0变成我们需要的大小,这样的话某个点计算的时候,我们只调整了分母,是最可能达到目标的。在遍历过去,看看每一个点最小需要调整多少,找到想要最大的那个就是我们的答案了。
const int N = 1e6 + 7;
ll n, m;
ll a[N];
void solve() {
n = read(), m = read();
ll ans = 0, sum = read();
rep(i, 2, n) {
a[i] = read();
if (100 * a[i] > sum * m) {
ll tmp = ceil(100 * a[i] * 1.0 / m - sum);
ans = max(ans, tmp);
}
sum += a[i];
}
print(ans);
}
T3
中文题意:
T组输入,每组输入一个n代表存在n条链。下面输入三行第一行代表C数组,第二行代表A数组,第三行代表B数组。C数组的含义是第i条链有几个节点。并且每条链只能从端点向前一条链连边,他们端点连向的点在前一条边的编号分别是
a
i
,
b
i
a_i,b_i
ai,bi。问最大的简单循环边数是几条。
n
≤
1
0
5
,
c
i
≤
1
0
9
n\leq10^5,c_i\leq10^9
n≤105,ci≤109。
对于我们从前到后枚举链条,通过贪心做取决策即可。我们知道如果 a i ! = b i a_i!=b_i ai!=bi的话,可能存在两种情况一种是绕一圈继续往跟前一条链走,第二种是当场闭合,那么我们是从前往后枚举的,只要判断一下那种选择更优保留即可。如果 a i = b i a_i=b_i ai=bi,那么只能计算当前链长度以及两条白给的边。
void solve() {
n = read();
rep(i, 1, n) c[i] = read();
rep(i, 1, n) a[i] = read();
rep(i, 1, n) b[i] = read();
ll ans = 0, res = 0; // res代表从链的端点出发的最大值。
rep(i, 2, n) {
if (a[i] == b[i]) res = 2;
else {
if (!res) res = abs(a[i] - b[i]) + 2;
// res更新之前保留的是前一条边端点的最大值
else res = max(res + 2 + c[i - 1] - 1 - abs(a[i] - b[i]), abs(a[i] - b[i]) + 2);
}
ans = max(ans, res + c[i] - 1);
}
print(ans);
}
T4
中文题意:
T组输入,每组输入一个长度为n的字符串,字符串只有
L
R
LR
LR两种字符。但是我们存在n+1个点,编号从0到n。我们输入的字符串S下标从1开始,它的第i个字符如果是
L
L
L,说明第i个点有一个去向i-1的有向边,如果第i个字符是
R
R
R,那么说明第i-1个点有个去向i的有向边。现在你没走一次路径,全部的路就会反向一次,既
i
→
j
i\to j
i→j,变成
j
→
i
j\to i
j→i。要你输出从下标0到n全部的点做为起点,每个起点对应可以最大走多少个点。你可以重复踩同一个点,但是贡献只算一次。
n
≤
3
∗
1
0
5
n\leq3*10^5
n≤3∗105。
我们首先看,如果我们通过L去到了左边的一个点,那么左边点如果还要继续前进一定只能是R。因为存在路径反转。那么同理如果通过反转之后之前的R去到了左边的点,那么下一次能走的也一定要原本是L,因为你要继续往左边走,上次走过来的方向要一致,说明最开始我们应该也不同向。那么我们就找到了如果要连续的走只能LRLRLRLR…这样走下去。RR和LL都是断点走不通的。
那么我们使用
L
i
L_i
Li数组数组记录第i个位置往左边最远可以走的距离。
使用
t
o
_
r
i
to\_r_i
to_ri代表这个位置我不能往左边走了,那么既然不能往左边走,那么一定可以从i-1个点往右边来到第i个点,这个数组记录一下每个位置从右边来的最多点数。
同理从后往前处理一下往右边走的最远距离,
R
i
R_i
Ri数组记录。
使用
t
o
_
l
i
to\_l_i
to_li代表第i+1个点通过走左边来到第i个点的最大点数。
那么更新操作也比较简单,从前遍历的时候判断一下是不是L,如果是
L
i
=
t
o
_
r
i
−
1
+
1
L_i=to\_r_{i-1}+1
Li=to_ri−1+1。
否则
t
o
_
r
i
=
L
i
−
1
+
1
to\_r_i=L_{i-1}+1
to_ri=Li−1+1。既然走不到左边去,那么可以从左边来右边次数+1。
从后往前类似。
const int N = 3e5 + 7;
ll n, m;
char s[N];
int to_l[N], to_r[N];
int l[N], r[N];
void solve() {
n = read();
scanf("%s", s + 1);
rep(i, 0, n + 4)
l[i] = r[i] = to_l[i] = to_r[i] = 0;
if (s[1] == 'L') l[1] = 1;
else to_r[1] = 1;
rep(i, 2, n) {
if (s[i] == 'L') l[i] = to_r[i - 1] + 1;
else to_r[i] = l[i - 1] + 1;
}
if (s[n] == 'R') r[n] = 1;
else to_l[n] = 1;
repp(i, n - 1, 1) {
if (s[i] == 'R') r[i] = to_l[i + 1] + 1;
else to_l[i] = r[i + 1] + 1;
}
rep(i, 0, n) {
ll ans = 1;
if (i != 0) ans += l[i];
if (i != n) ans += r[i + 1];
print(ans, 32);
}
puts("");
}