B. RPG Protagonist
题意
给两个容量分别为 p p p和 f f f的背包,和 c n t s cnt_s cnts个重量为 s s s, c n t w cnt_w cntw个重量为 w w w,价值都为 1 1 1的物品,求背包能装的物品的最大价值。
思路
假设 s ≤ w s\leq w s≤w(如果 s > w s > w s>w可以交换两物品),枚举第一个背包中重量为 s s s的物品的个数,剩下的重量为 s s s的物品尽可能地放进第二个背包,最后两个背包剩余的空间尽可能地放重量为 w w w的物品。
代码
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <iostream>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
int m1, m2, cnts, cntw, s, w;
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
int main()
{
int kase;
kase = getint();
while (kase--)
{
m1 = getint(); m2 = getint();
cnts = getint(); cntw = getint();
s = getint(); w = getint();
if (w < s) swap(s, w), swap(cnts, cntw);
int ans = 0;
fo(i, 0, cnts)
if (i * s <= m1)
{
int cntw1 = min((m1 - i * s) / w, cntw);
int cnts2 = min(m2 / s, cnts - i);
int cntw2 = min((m2 - cnts2 * s) / w, cntw - cntw1);
ans = max(ans, i + cntw1 + cnts2 + cntw2);
}
else break;
printf("%d\n", ans);
}
return 0;
}
C. Binary String Reconstruction
题意
对于一个 01 01 01串 w w w和给定的 x x x, 01 01 01串 s s s有 s i = w i − x ∣ w i + x s_i=w_{i-x}|w_{i+x} si=wi−x∣wi+x(当 i − x < 0 i-x<0 i−x<0时 w i − x = 0 w_{i-x}=0 wi−x=0, i + x i+x i+x同理),现已知串 w w w和 x x x求串 s s s,若 s s s不存在则输出 − 1 -1 −1。
思路
根据 s s s的定义,当 s i = 0 s_i=0 si=0时, w i − x = w i + x = 0 w_{i-x}=w_{i+x}=0 wi−x=wi+x=0,先把所有 w w w串上述位置填 0 0 0,其他位置填 1 1 1,最后检查 w w w是否合法即可。
代码
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <iostream>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
const int maxn = 1e5 + 5;
int n, x;
char s[maxn], w[maxn];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
bool check()
{
fo(i, 1, n)
{
char ch;
ch = i - x >= 1 && w[i - x] == '1' || i + x <= n && w[i + x] == '1'? '1' : '0';
if (s[i] != ch) return false;
}
return true;
}
int main()
{
int kase;
kase = getint();
while (kase--)
{
scanf("%s", s + 1);
n = strlen(s + 1);
x = getint();
memset(w, 0, sizeof(w));
w[n + 1] = '\0';
fo(i, 1, n)
if (s[i] == '0')
{
if (i - x >= 1) w[i - x] = '0';
if (i + x <= n) w[i + x] = '0';
}
fo(i, 1, n)
if (!w[i]) w[i] = '1';
if (check()) printf("%s\n", w + 1);
else printf("-1\n");
}
return 0;
}
D. Zigzags
题意
给一个长度为 n n n的序列 { a i } \{a_i\} {ai},求满足以下条件的四元组 ( i , j , k , l ) (i,j,k,l) (i,j,k,l)的个数:
- 1 ≤ i < j < k < l ≤ n 1 \leq i < j < k < l\leq n 1≤i<j<k<l≤n;
- a i = a k a_i=a_k ai=ak且 a j = a l a_j=a_l aj=al;
思路
枚举 k , l k,l k,l,用桶统计所有 ( a i , a j ) (a_i,a_j) (ai,aj)的出现次数,累加到答案, k k k每向右移动一位,更新桶。
代码
#include <bits/stdc++.h>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
typedef long long ll;
const int maxn = 3e3 + 5;
int n;
int a[maxn], cnt[maxn * maxn];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
inline int getnum(int a, int b)
{
return (a - 1) * n + b;
}
int main()
{
int kase;
kase = getint();
while (kase--)
{
n = getint();
fo(i, 1, n) a[i] = getint();
memset(cnt, 0, sizeof(cnt));
ll ans = 0;
fo(k, 1, n)
{
fo(l, k + 1, n)
ans += cnt[getnum(a[k], a[l])];
fo(i, 1, k - 1) cnt[getnum(a[i], a[k])]++;
}
printf("%lld\n", ans);
}
return 0;
}
E. Clear the Multiset
题意
给定一个长度为 n n n序列 { a i } \{a_i\} {ai},每次可以执行以下两种操作之一:
- 选定一个区间 [ l , r ] [l,r] [l,r],所有 a k ( l ≤ k ≤ r ) a_k(l \leq k \leq r) ak(l≤k≤r)必须满足 a k > 0 a_k>0 ak>0, a k = a k − 1 a_k=a_k-1 ak=ak−1。
- 选定两个数 i i i和 x ( x ≤ a i ) x(x \leq a_i) x(x≤ai), a i = a i − x a_i=a_i-x ai=ai−x。
求将序列清零的最少操作数。
思路
解法一
dp,设状态
f
[
i
]
[
j
]
f[i][j]
f[i][j]为序列的前
i
i
i个数清零,且位置
i
i
i上执行了
j
(
j
≤
a
i
)
j(j \leq a_i)
j(j≤ai)次操作一,所需的最少操作数。由于操作顺序不影响结果,我们先考虑执行操作一,再考虑执行操作二。
考虑转移,状态
f
[
i
]
[
j
]
f[i][j]
f[i][j]可以从状态
f
[
i
−
1
]
[
k
]
f[i-1][k]
f[i−1][k]转移而来,下面对
k
k
k进行分类讨论:
- 0 ≤ k ≤ j 0 \leq k \leq j 0≤k≤j,状态 f [ i ] [ j ] f[i][j] f[i][j]的前 k k k次操作一和状态 f [ i − 1 ] [ k ] f[i-1][k] f[i−1][k]一起进行,后 j − k j-k j−k次操作单独进行,若 a i − j > 0 a_i-j>0 ai−j>0则另需 1 1 1次操作二将 a i a_i ai清零,所以对 f [ i ] [ j ] f[i][j] f[i][j]的贡献是 f [ i − 1 ] [ k ] + j − k + [ a i − j > 0 ] f[i-1][k]+j-k+[a_i-j>0] f[i−1][k]+j−k+[ai−j>0](中括号的值为真则返回值为 1 1 1,否则为 0 0 0)。
- j < k ≤ m a x { a i } j < k \leq max\{a_i\} j<k≤max{ai},状态 f [ i ] [ j ] f[i][j] f[i][j]的 j j j次操作都可以和状态 f [ i − 1 ] [ k ] f[i-1][k] f[i−1][k]一起进行,若 a i − j > 0 a_i-j>0 ai−j>0则另需 1 1 1次操作二将 a i a_i ai清零,所以对 f [ i ] [ j ] f[i][j] f[i][j]的贡献是 f [ i − 1 ] [ k ] + [ a i − j > 0 ] f[i-1][k]+[a_i-j>0] f[i−1][k]+[ai−j>0]。
综上
f
[
i
]
[
j
]
=
{
f
[
i
−
1
]
[
k
]
+
j
−
k
+
[
a
i
−
j
>
0
]
0
≤
k
≤
j
f
[
i
−
1
]
[
k
]
+
[
a
i
−
j
>
0
]
j
<
k
≤
m
a
x
{
a
i
}
f[i][j]=\begin{cases} f[i-1][k]+j-k+[a_i-j>0] & 0 \leq k \leq j \\ f[i-1][k]+[a_i-j>0] & j < k \leq max\{a_i\} \end{cases}
f[i][j]={f[i−1][k]+j−k+[ai−j>0]f[i−1][k]+[ai−j>0]0≤k≤jj<k≤max{ai}
该dp可以进一步优化,有效的
j
j
j的取值为
{
a
i
}
\{a_i\}
{ai}中的数,只枚举有效的
j
j
j,并且维护
f
[
i
−
1
]
[
k
]
−
k
f[i-1][k]-k
f[i−1][k]−k的前缀最小值,
f
[
i
−
1
]
[
k
]
f[i-1][k]
f[i−1][k]的后缀最小值,即可达到
O
(
1
)
O(1)
O(1)转移,总时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
我们又可以发现,若对一个区间
[
l
,
r
]
[l,r]
[l,r]整体减
x
x
x,那么,
x
x
x是区间
[
l
,
r
]
[l,r]
[l,r]中的最小值,换句话说,区间
[
l
,
r
]
[l,r]
[l,r]中存在一个数只执行操作一清零,且该数为
x
x
x。
据此,我们从新定义dp状态,设
f
[
i
]
[
0
]
f[i][0]
f[i][0]为前
i
i
i个数清零,且
a
i
a_i
ai只执行操作一清零,
f
[
i
]
[
1
]
f[i][1]
f[i][1]为前
i
i
i个数清零,
a
i
a_i
ai执行操作二清零。
根据定义,只执行操作二清零
f
[
i
]
[
1
]
=
m
i
n
{
f
[
i
−
1
]
[
0
]
,
f
[
i
−
1
]
[
1
]
}
+
[
a
i
>
0
]
f[i][1]=min\{f[i-1][0],f[i-1][1]\}+[a_i>0]
f[i][1]=min{f[i−1][0],f[i−1][1]}+[ai>0],
对于状态
f
[
i
]
[
0
]
f[i][0]
f[i][0],有三种情况:
- 单独使用 a i a_i ai次操作一,此时对 f [ i ] [ 0 ] f[i][0] f[i][0]的贡献是 f [ i − 1 ] [ 1 ] + a [ i ] f[i-1][1]+a[i] f[i−1][1]+a[i]。
- 共用 a i a_i ai次操作一,即 a i a_i ai为上述 x x x,枚举操作一的左边界 j ( a i ≤ a j ) j(a_i \leq a_j) j(ai≤aj), j j j满足 m i n { a j + 1 ⋯ a i − 1 } ≥ a i min\{a_{j+1} \cdots a_{i-1}\} \geq a_i min{aj+1⋯ai−1}≥ai, a j + 1 ⋯ a i a_{j+1} \cdots a_{i} aj+1⋯ai与 a j a_j aj共用 a i a_i ai次操作一,然后对 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i−1]中的数用操作二清零,所以此时对 f [ i ] [ 0 ] f[i][0] f[i][0]的贡献是 f [ j ] [ 0 ] + i − j − 1 f[j][0]+i-j-1 f[j][0]+i−j−1。
- 共用 a j a_j aj次操作一,单独使用 a i − a j ( a i > a j ) a_i-a_j(a_i>a_j) ai−aj(ai>aj)次操作一,同理 j j j满足 m i n { a j + 1 ⋯ a i − 1 } ≥ a j min\{a_{j+1} \cdots a_{i-1}\} \geq a_j min{aj+1⋯ai−1}≥aj,同理对 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i−1]中的数用操作二清零,此时第 f [ i ] [ 0 ] f[i][0] f[i][0]的贡献是 f [ j ] [ 0 ] + i − j − 1 + a i − a j f[j][0]+i-j-1+a_i-a_j f[j][0]+i−j−1+ai−aj。
可能的疑惑:
- 为什么在1.中的贡献不是 m i n { f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] } + a [ i ] min\{f[i-1][0],f[i-1][1]\}+a[i] min{f[i−1][0],f[i−1][1]}+a[i]?因为 f [ i − 1 ] [ 0 ] + a [ i ] f[i-1][0]+a[i] f[i−1][0]+a[i]不可能是最优解,显然 a i a_i ai与 a i − 1 a_{i-1} ai−1共用部分操作一会更优,在2.和3.中已经考虑过。
- 为什么在2.中 j j j满足 m i n { a j + 1 ⋯ a i − 1 } ≥ a i min\{a_{j+1} \cdots a_{i-1}\} \geq a_i min{aj+1⋯ai−1}≥ai?因为要保证 a i a_i ai和 a j a_j aj在同一个操作一的区间内。3.中同理。
- 为什么在2.中只用操作二清零 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i−1]中的数?假若存在一组更优的解, [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i−1]中的数使用操作一,那么该操作一的右边界可以扩展到 i i i,该解在后两种情况中已经考虑,所以只考虑用操作二。
综上
f
[
i
]
[
1
]
=
m
i
n
{
f
[
i
−
1
]
[
0
]
,
f
[
i
−
1
]
[
1
]
}
+
[
a
i
>
0
]
f[i][1]=min\{f[i-1][0],f[i-1][1]\}+[a_i>0]
f[i][1]=min{f[i−1][0],f[i−1][1]}+[ai>0]
f
[
i
]
[
0
]
=
m
i
n
{
f
[
j
]
[
0
]
+
i
−
j
−
1
a
i
≤
a
j
f
[
j
]
[
0
]
+
i
−
j
−
1
+
a
i
−
a
j
a
i
>
a
j
f[i][0]=min\begin{cases} f[j][0]+i-j-1 & a_i \leq a_j\\ f[j][0]+i-j-1+a_i-a_j & a_i>a_j \end{cases}
f[i][0]=min{f[j][0]+i−j−1f[j][0]+i−j−1+ai−ajai≤ajai>aj
对于
f
[
i
]
[
0
]
f[i][0]
f[i][0]的两种情况都有
1
≤
j
<
i
1 \leq j < i
1≤j<i和
m
i
n
{
a
j
+
1
⋯
a
i
−
1
}
≥
m
i
n
{
a
i
,
a
j
}
min\{a_{j+1} \cdots a_{i-1}\} \geq min\{a_i,a_j\}
min{aj+1⋯ai−1}≥min{ai,aj}。
直接转移的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2),可以再优化一下。
考虑条件
m
i
n
{
a
j
+
1
⋯
a
i
−
1
}
≥
m
i
n
{
a
i
,
a
j
}
min\{a_{j+1} \cdots a_{i-1}\} \geq min\{a_i,a_j\}
min{aj+1⋯ai−1}≥min{ai,aj},可以发现对于两个可能的转移
j
1
j_1
j1和
j
2
j_2
j2,假设
j
1
<
j
2
j_1<j_2
j1<j2,若
a
[
j
1
]
>
a
[
j
2
]
a[j_1]>a[j_2]
a[j1]>a[j2],则
j
1
j_1
j1是不满足以上条件的,即一个合法的转移集合内所有数满足:对于任意
j
1
<
j
2
j_1<j_2
j1<j2,都有
a
[
j
1
]
≤
a
[
j
2
]
a[j_1] \leq a[j_2]
a[j1]≤a[j2],对于
a
[
j
1
]
=
a
[
j
2
]
a[j_1]=a[j_2]
a[j1]=a[j2]只保留
j
2
j_2
j2,因为
j
2
j_2
j2已经考虑了从
j
1
j_1
j1转移过来的情况。因此可以用单调栈优化转移,从栈顶开始,将满足
a
i
≤
a
j
a_i \leq a_j
ai≤aj的
j
j
j出栈,同时更新
f
[
i
]
[
0
]
f[i][0]
f[i][0](
a
i
≤
a
j
a_i \leq a_j
ai≤aj的情况),用维护
f
[
j
]
[
0
]
−
j
−
a
j
f[j][0]-j-a_j
f[j][0]−j−aj的前缀最小值(从栈底到某元素)更新
f
[
i
]
[
0
]
f[i][0]
f[i][0](
a
i
>
a
j
a_i>a_j
ai>aj的情况),然将
i
i
i入栈,求出对应的前缀最小值。
到此,时间复杂度降为
O
(
n
)
O(n)
O(n)。
解法二
对于序列 { a i } \{a_i\} {ai},两种处理方法:
- 全使用操作二清零。
- 对整个序列减去最小值 a x a_x ax,递归处理 [ 1 , x ) [1,x) [1,x)和 ( x , n ] (x,n] (x,n]。
两种处理方法取最小值即可。
时间复杂度
O
(
n
2
)
O(n^2)
O(n2)。
另:对于2.可用rmq求法将时间复杂度优化到
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)甚至
O
(
n
)
O(n)
O(n)。
代码
解法一
#include <bits/stdc++.h>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
const int maxn = 5e3 + 5, inf = 1e9;
struct data{int pos, mval;};
stack<data> st;
int n;
int a[maxn];
int f[maxn][2];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
int main()
{
n = getint();
fo(i, 1, n) a[i] = getint();
f[1][0] = a[1]; f[1][1] = 1;
st.push((data){1, f[1][0] - a[1] - 1});
fo(i, 2, n)
{
f[i][1] = min(f[i - 1][0], f[i - 1][1]) + (a[i] > 0);
f[i][0] = f[i - 1][1] + a[i];
while (!st.empty() && a[st.top().pos] >= a[i])
{
data tp = st.top(); st.pop();
f[i][0] = min(f[i][0], f[tp.pos][0] + i - tp.pos - 1);
}
if (!st.empty())
{
f[i][0] = min(f[i][0], a[i] + i - 1 + st.top().mval);
st.push((data){i, min(f[i][0] - a[i] - i, st.top().mval)});
}
else st.push((data){i, f[i][0] - a[i] - i});
}
printf("%d\n", min(f[n][0], f[n][1]));
return 0;
}
解法二
#include <bits/stdc++.h>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
const int maxn = 5e3 + 5, inf = 1e9;
int n;
int a[maxn];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
int solve(int l, int r)
{
if (l > r) return 0;
if (l == r) return a[l] > 0;
int mi = -1, tmp, cnt = 0;
fo(i, l, r)
{
if (mi == -1 || a[i] < a[mi]) mi = i;
cnt += a[i] > 0;
}
tmp = a[mi];
fo(i, l, r) a[i] -= tmp;
return min(tmp + solve(l, mi - 1) + solve(mi + 1, r), cnt);
}
int main()
{
n = getint();
fo(i, 1, n) a[i] = getint();
printf("%d\n", solve(1, n));
return 0;
}