1644A - Doors and Keys
题意: 3 3 3 种颜色的门和钥匙,需要先遇到小写字母的钥匙后打开大写字母的门,问能不能通过去。
简单模拟即可,当存在一个小写字母的时候记录一下,遇到大写字母的时候检查其对应的小写字母是否存在。
参考实现 1:
char s[7];
int main() {
int T; read(T);
while (T--) {
read(s);
bool vis[3] = {0, 0, 0}, flg = 1;
FOR(i, 0, 5) {
if (s[i] == 'r') vis[0] = 1;
else if (s[i] == 'g') vis[1] = 1;
else if (s[i] == 'b') vis[2] = 1;
else if (s[i] == 'R') flg &= vis[0];
else if (s[i] == 'G') flg &= vis[1];
else if (s[i] == 'B') flg &= vis[2];
}
print(flg ? "YES" : "NO");
}
return output(), 0;
}
参考实现 2:
int main()
{
int t;cin>>t;
rep(kase,1,t)
{
set<char> S;
string str;cin>>str;
bool flg=1;
for(auto &c:str)
{
S.insert(c);
if(isupper(c)&&!S.count(tolower(c)))
{
flg=0;
break;
}
}
cout<<(flg?"YES":"NO")<<'\n';
}
return 0;
}
1644B - Anti-Fibonacci Permutation
题意: T T T 组数据,给定 n n n,要求构造 n n n 个“反斐波那契排列”,一个反斐波那契排列满足 ∀ i ( 3 ≤ i ≤ n ) \forall i( 3\le i\le n) ∀i(3≤i≤n),有 p i − 2 + p i − 1 ≠ p i p_{i-2}+p_{i-1}\ne p_i pi−2+pi−1=pi。 t ≤ 48 , n ≤ 50 t\le 48,n\le50 t≤48,n≤50。
div 2 的 B 题,考虑从简单的形式开始思考如何构造。
我们发现,一个形如 1 , 2 , 3 , ⋯ , n 1,2,3,\cdots, n 1,2,3,⋯,n 的排列只存在一个地方不满足限制: 1 + 2 = 3 1+2=3 1+2=3。那么只要将 1 1 1 扔到排列尾部, 2 , 3 , ⋯ , n , 1 2,3,\cdots,n,1 2,3,⋯,n,1 显然是满足要求的排列,然后将前面的 n − 1 n-1 n−1 个元素循环移位一下,形如 i , i + 1 , ⋯ , n , 1 , ⋯ , i − 1 , 1 i,i+1,\cdots,n,1,\cdots,i-1,1 i,i+1,⋯,n,1,⋯,i−1,1。就能构造出 n − 1 n-1 n−1 个满足题意的排列。
还剩下一个,怎么办呢,上面的排列都以 2 , 3 , ⋯ , n 2,3,\cdots, n 2,3,⋯,n 开头,这次我们考虑让 1 1 1 开头。发现 1 , n , 2 , 3 , ⋯ , n − 1 1, n, 2,3,\cdots, n -1 1,n,2,3,⋯,n−1 满足题意,直接输出即可。
int main() {
int T; read(T);
while (T--) {
int n; read(n);
FOR(i, 2, n) {
FOR(j, i, n) print(j, ' ');
FOR(j, 2, i - 1) print(j, ' ');
if (i != 1) print(1, ' ');
putchar('\n');
}
print(1, n, ' ');
FOR(j, 2, n - 1) print(j, ' ');
putchar('\n');
}
return output(), 0;
}
1644C - Increase Subarray Sums
题意:给定一个序列
a
a
a 和一个非负整数
x
x
x,令
f
(
k
)
f(k)
f(k) 为 对
a
a
a 中的
k
k
k 个不同元素都加上
x
x
x 后,
a
a
a 的最大子段和。对于
k
∈
[
0
,
n
]
k\in[0,n]
k∈[0,n] 求出
f
(
k
)
f(k)
f(k),
n
≤
5000
n\le 5000
n≤5000,数据不会爆 int
。
解法 1:基于经典的
O
(
n
)
O(n)
O(n) 最大子段和的 DP,令
f
i
,
j
f_{i,j}
fi,j 为强制选
a
i
a_i
ai,对
j
j
j 个元素加了
x
x
x 后的最大子段和,则类比经典 dp 的转移,我们可以得到
{
f
i
,
0
=
max
(
f
i
−
1
,
0
,
0
)
+
a
i
f
i
,
j
=
max
(
f
i
−
1
,
j
+
a
i
,
a
i
,
f
i
−
1
,
j
−
1
+
a
i
+
x
,
a
i
+
x
)
1
≤
j
≤
i
\begin{cases} f_{i,0} = \max(f_{i-1,0},0)+a_i\\ f_{i,j} = \max(f_{i-1,j}+a_i,a_i,f_{i-1,j-1}+a_i+x,a_i+x)&1\le j\le i \end{cases}
{fi,0=max(fi−1,0,0)+aifi,j=max(fi−1,j+ai,ai,fi−1,j−1+ai+x,ai+x)1≤j≤i
记得输出答案的时候取前缀最大值,然后要和
0
0
0 取
max
\max
max。
const int maxn = 5005;
int a[maxn], n, x;
int f[maxn][maxn], ans[maxn];
int main() {
int T; read(T);
while (T--) {
read(n, x);
FOR(i, 1, n) {
read(a[i]);
FOR(j, 0, n) f[i][j] = -2e9, ans[j] = 0;
}
f[0][0] = 0;
FOR(i, 1, n) {
f[i][0] = max(f[i - 1][0] + a[i], a[i]);
FOR(j, 1, i) {
f[i][j] = max(f[i - 1][j] + a[i], a[i], f[i - 1][j - 1] + a[i] + x, a[i] + x);
}
FOR(j, 0, i) chkmax(ans[j], f[i][j]);
}
FOR(i, 0, n) print(ans[i] = chkmax(ans[i], ans[i - 1]), ' ');
putchar('\n');
}
return output(), 0;
}
解法 2:考虑贪心。既然要求 f ( k ) f(k) f(k),那就求出所有长度至少为 k k k 的子段和,取其最大值加上 k × x k\times x k×x 即可。正确性:去除的子段长度一定要 ≥ k \ge k ≥k 才能给 k k k 个位置加上 x x x。然后同样要对答案取前缀最大值。
#define N 5005
int sum[N][N],a[N],ans[N],maxsum[N],n, x;
int main()
{
int t;cin>>t;
rep(kase,1,t)
{
cin>>n>>x;
rep(i,1,n)cin>>a[i],sum[1][i]=sum[1][i-1]+a[i];
rep(i,2,n)rep(j,i,n)sum[i][j]=sum[1][j]-sum[1][i-1];
per(len,n,0)
{
maxsum[len]=-2e9;
if(len>0) rep(i,1,n-len+1)chkmax(maxsum[len],sum[i][i+len-1]);
if(len<n) chkmax(maxsum[len],maxsum[len+1]);
}
rep(k,0,n)ans[k]=max(maxsum[k]+k*x,0);
rep(k,1,n)chkmax(ans[k],ans[k-1]);
rep(k,0,n)cout<<ans[k]<<' ';
cout<<'\n';
}
return 0;
}
1644D - Cross Coloring
题意: n × m n\times m n×m 的格子一开始全为白色, q q q 次操作,每次操作选择 ( r , c ) (r, c) (r,c),然后将第 r r r 行全部和第 c c c 列全部格子涂成 [ 1 , k ] [1, k] [1,k] 中的任意颜色。问最后有多少种可能的局面。 T ≤ 1 0 4 T\le 10^4 T≤104, 1 ≤ n , m , k , q ≤ 2 × 1 0 5 1\le n,m,k,q\le 2\times 10^5 1≤n,m,k,q≤2×105, ∑ q ≤ 2 × 1 0 5 \sum q\le 2\times 10^5 ∑q≤2×105。
遇到涂色问题不妨倒序考虑,我们会发现,一次涂色操作会对答案产生贡献,当且仅当它影响的行或列至少有一方没被覆盖完。那么我们用 std::set
分别维护一下被占用了的行和列,若当前操作的
r
r
r 或
c
c
c 没有被占用,那么就加入 set
并且让答案乘上
k
k
k。有一个小优化即记录一下占用的行和列的个数,若占用了
n
n
n 行或占用了
m
m
m 列则直接终止循环。
const int maxn = 2e5 + 5;
int n, m, k, q;
int qx[maxn], qy[maxn];
int main() {
int T; read(T);
while (T--) {
read(n, m, k, q);
FOR(i, 1, q) read(qx[i], qy[i]);
int visr = 0, visc = 0;
set<int> remr, remc;
modint ans = 1;
DEC(i, q, 1) {
if (visr == n || visc == m) break;
int flg = 0;
if (!remr.count(qx[i]))
++visr, remr.insert(qx[i]), flg = 1;
if (!remc.count(qy[i]))
++visc, remc.insert(qy[i]), flg = 1;
if (flg) ans *= k;
}
print(ans);
}
return output(), 0;
}
另外,部分代码使用的是 O ( n T ) O(nT) O(nT) 的预处理来维护被占用的行/列,但是由于没有 ∑ n \sum n ∑n 的限制,这样的代码可能过了 Pretests 后由于评测波动被 hack(如果常数偏大)。这启示我们注意观察题目限制条件,并且平时尽可能写常数小的代码。
1644E - Expand the Path
题意:给定
n
×
n
n\times n
n×n 的网格,一个机器人初始在
(
1
,
1
)
(1,1)
(1,1),给定其由若干个 R
和 D
构成的操作序列,两种字母分别代表右移一格和下移一格。你可以以任意次数将操作序列中的某个操作复制一遍,例如 RDR
可以变成 RDDR
也可以变成 RRDR
,但要求机器人不能走出网格。问机器人有可能经过的格子数量。
1
≤
T
≤
1
0
4
1\le T\le 10^4
1≤T≤104,
2
≤
n
≤
1
0
8
2\le n\le 10^8
2≤n≤108,操作序列总长
≤
2
×
1
0
5
\le 2\times 10^5
≤2×105。
首先对于全是 R
或全是 D
的操作序列,答案显然为
n
n
n,特判掉即可。
否则令 R
的个数为
c
n
t
R
\mathrm{cnt}_R
cntR,D
的个数为
c
n
t
D
\mathrm{cnt}_D
cntD,则我们发现,对于第一个 RD
或者 DR
之后的每个格子,其都可以覆盖以其为左上角的
(
n
−
c
n
t
R
+
1
)
×
(
n
−
c
n
t
D
+
1
)
(n - \mathrm{cnt}_R + 1)\times (n - \mathrm{cnt}_D + 1)
(n−cntR+1)×(n−cntD+1) 的矩形,因为可以通过任意倍增 R
或者 D
来实现。
现在的问题就是数出这些格子的数量。画图之后可以发现,我们可以行列分开考虑。对于每行的贡献加上对于每列的贡献再加上右下角的矩形即为答案,当然需要统计一下本身走过的路径,详见代码。
using ll = long long;
const int maxn = 2e5 + 5;
char s[maxn];
int main() {
int T; read(T);
while (T--) {
ll n, cntr = 0, cntd = 0;
read(n);
read(s + 1);
int len = strlen(s + 1);
ll nowx = 1, nowy = 1;
FOR(i, 1, len) {
if (s[i] == 'D') {
++cntd;
} else {
++cntr;
}
}
if (cntr == 0 || cntd == 0) {
print(n);
} else {
ll cntr2 = 0, cntd2 = 0, ans = len + 1; // 首先会覆盖 len + (1,1) 个格子
if (s[1] == 'R') { // 分类讨论
ans += (cntd + 1) * (n - cntr - 1); // 这是关于每行的贡献
FOR(i, 1, len) {
if (s[i] == 'D') break;
++cntr2; // 统计开头的 R 操作的个数
}
ans += (cntr - cntr2 + 1) * (n - cntd - 1); // 统计关于每列的贡献,需要去除开头的连续的不会产生贡献的 R
} else { // 下面的情况同理
ans += (cntr + 1) * (n - cntd - 1);
FOR(i, 1, len) {
if (s[i] == 'R') break;
++cntd2;
}
ans += (cntd - cntd2 + 1) * (n - cntr - 1);
}
ans += (n - cntr - 1) * (n - cntd - 1); // 最后需要计算右下角的矩形
print(ans);
}
FOR(i, 1, len) s[i] = 0;
}
return output(), 0;
}