点击打开链接
点击打开链接
令输入中每一段连续的数字长度为 X i X_i Xi ,答案显然是 ∑ ⌊ X i 2 ⌋ \sum \lfloor\frac{X_i}{2}\rfloor ∑⌊2Xi⌋ 。
时间复杂度 O ( N ) O(N) O(N) 。
#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template void writeln(T x) {
write(x);
puts("");
}
int n, a[MAXN];
int main() {
read(n);
int now = 0, cnt = 0, ans = 0;
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] == now) cnt++;
else {
now = a[i];
ans += cnt / 2;
cnt = 1;
}
}
ans += cnt / 2;
writeln(ans);
return 0;
}
首先,若 B > A B>A B>A 或 B > D B>D B>D ,答案显然为 No 。
否则,有 B ≤ A , B ≤ D B\leq A,B\leq D B≤A,B≤D ,若 B ≤ C B\leq C B≤C ,答案显然为 Yes 。
考虑 B ≤ A , B ≤ D , B > C B\leq A,B\leq D,B>C B≤A,B≤D,B>C 的情况,此时,可能出现无解的情况一定是当前剩余物品 X X X 数不足 B B B ,但多余 C C C 时进行购买的情况。在模 B B B 意义下考虑 X X X 的变化,只有进行 + D +D +D 操作时会改变 X X X 模 B B B 的值。我们需要判断的,即为对于通过 + D +D +D 操作可以使得 X ≡ v ( m o d B ) X\equiv v\ (mod\ B) X≡v (mod B) 的 v v v ,是否都满足 f ( v ) ≥ B f(v)\geq B f(v)≥B ,其中 f ( v ) f(v) f(v) 表示 > C >C >C 的最小的模 B B B 余 v v v 的数。
显然, v v v 越大越有可能不满足上述不等式,因此判断可以取到的最大的 v v v 对应的不等式是否成立即可。
时间复杂度 O ( T L o g V ) O(TLogV) O(TLogV) 。
#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template void writeln(T x) {
write(x);
puts("");
}
bool work(ll a, ll b, ll c, ll d) {
if (b > a || b > d) return false;
if (b <= c) return true;
ll g = __gcd(b, d);
return b - ((g - a % g == 0) ? g : (g - a % g)) <= c;
}
int main() {
int T; read(T);
while (T--) {
ll a, b, c, d;
read(a), read(b), read(c), read(d);
if (work(a, b, c, d)) puts("Yes");
else puts("No");
}
return 0;
}
搜索字符串前 N N N 个字符的颜色,由题设,我们已经可以知道两种颜色各自拼接后的结果。
进行简单 O ( N 2 ) O(N^2) O(N2) dp 计算满足拼接结果的后 N N N 个字符的涂色方案数即可。
时间复杂度 O ( 2 N N 2 ) O(2^NN^2) O(2NN2) 。
#include
using namespace std;
const int MAXN = 40;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template void writeln(T x) {
write(x);
puts("");
}
char s[MAXN], a[MAXN], b[MAXN];
int n, dp[MAXN][MAXN]; ll ans;
void work(int pos, int la, int lb) {
if (pos == n) {
memset(dp, 0, sizeof(dp)), dp[n][la] = 1;
for (int i = n; i >= 1; i--)
for (int j = max(0, i - lb), k = i - j; j <= la && j <= i; j++, k--) {
if (s[i] == a[j]) dp[i - 1][j - 1] += dp[i][j];
if (s[i] == b[k]) dp[i - 1][j] += dp[i][j];
}
ans += dp[0][0];
return;
}
a[la + 1] = s[pos];
work(pos - 1, la + 1, lb);
b[lb + 1] = s[pos];
work(pos - 1, la, lb + 1);
}
int main() {
read(n), scanf("%s", s + 1);
work(n * 2, 0, 0);
writeln(ans);
return 0;
}
首先可以考虑删去所有不在任意一个 2 × 2 2\times 2 2×2 的正方形中的方格,每删去一个,便将答案 × 2 \times 2 ×2 。
考虑用 2 × 2 2\times 2 2×2 的正方形的样式代替 1 × 1 1\times 1 1×1 的正方形,来表示剩余部分。
考虑一些显然的性质, \array{\array{0,0}\\array{1,1}} 的上下必然是 \array{\array{1,1}\\array{0,0}}, \array{\array{1,0}\\array{1,0}} 的左右必然是 \array{\array{0,1}\\array{0,1}} 。
因此,我们称存在 \array{\array{0,0}\\array{1,1}} 的列是一个关键的列,考虑计算 d p i dp_i dpi 表示第 i i i 列是关键列时,前 i i i 列的涂色方式数,可以通过枚举下一个关键列的方式转移。
关于转移系数的计算,可以注意到,对于不存在 \array{\array{0,0}\\array{1,1}} 或 \array{\array{1,1}\\array{0,0}} 的一行,其上方一行的涂色方式恰好有 2 2 2 种,直接计算可以自由决定的行数 c n t cnt cnt , 2 c n t 2^{cnt} 2cnt 即为转移系数。
时间复杂度 O ( N 3 + N 2 L o g V ) O(N^3+N^2LogV) O(N3+N2LogV) ,可简单优化至 O ( N 2 ) O(N^2) O(N2) 。
官方题解中介绍了一种 O ( N L o g V ) O(NLogV) O(NLogV) 的做法,本质上可以看做是用笛卡尔树的思想对该算法的优化。
#include
using namespace std;
const int MAXN = 105;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template void writeln(T x) {
write(x);
puts("");
}
int power(int x, ll y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
ll cnt; int dp[MAXN];
int n, ans, a[MAXN], b[MAXN];
int main() {
read(n), ans = 1;
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] == 1) {
cnt += 1;
a[i] = 0;
}
}
for (int i = 1; i <= n; i++) {
int Max = max(a[i - 1], a[i + 1]);
if (a[i] > Max) {
cnt += a[i] - Max;
a[i] = Max;
}
}
ans = power(2, cnt);
for (int i = 1; i <= n - 1; i++) {
b[i] = min(a[i], a[i + 1]);
if (b[i]) b[i]--;
}
dp[0] = 1;
for (int i = 0; i <= n - 1; i++) {
for (int j = i + 1; j <= n; j++) {
static int c[MAXN];
memset(c, 0, sizeof(c));
for (int k = i + 1, Min = min(b[k], b[i]); k <= j - 1; k++, chkmin(Min, b[k]))
chkmax(c[k], Min);
for (int k = j - 1, Min = min(b[k], b[j]); k >= i + 1; k--, chkmin(Min, b[k]))
chkmax(c[k], Min);
ll cnt = 0;
for (int k = i + 1; k <= j - 1; k++) {
c[k] = b[k] - c[k];
if (c[k] - c[k - 1] > 0) cnt += c[k] - c[k - 1];
}
if (b[i] == 0 && (b[j] != 0 || i != j - 1)) cnt++;
update(dp[j], 1ll * dp[i] * power(2, cnt) % P);
if (b[j] == 0) break;
}
}
writeln(1ll * ans * dp[n] % P);
return 0;
}
将字符串尽可能地分割,使得在保证同一组字符均在每一段中的情况下,字符串被分成的段数尽量多。
注意到我们需要最大化字典序,对于字符串 S + T S+T S+T ,分别最大化 S S S 和 T T T 的字典序即可最大化 S + T S+T S+T 的字典序,因此,可以考虑分别计算每一段的答案,再通过简单 dp 合并答案。
完成分割后,可以发现,同一段内的每一组字符的先后顺序一定是固定的,否则一定可以细分。
对于先后顺序是 ab 的段,最优的方案显然是贪心地选取尽可能多的 ab ,使得不存在连续的 a 。
对于先后顺序是 ba 的段,考虑枚举第一对选的字符 i i i ,由分段的性质,可以贪心地发现选取 i i i 后的每一对字符得到的结果一定是最优的,因此枚举后比较即可。
时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
#include
using namespace std;
const int MAXN = 6005;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template void writeln(T x) {
write(x);
puts("");
}
char s[MAXN];
int n, tot, rk[MAXN];
string res[MAXN], dp[MAXN];
int la, lb, a[MAXN], b[MAXN];
int main() {
read(n), scanf("\n%s", s + 1);
for (int i = 1; i <= 2 * n; i++)
if (s[i] == 'a') a[++la] = i, rk[i] = la;
else b[++lb] = i, rk[i] = lb;
int last = 0;
for (int i = 1; i <= n; i++) {
if (i == n || max(a[i], b[i]) < min(a[i + 1], b[i + 1])) {
if (a[i] < b[i]) {
res[++tot] = ""; int Max = 0;
for (int j = last + 1; j <= i; j++)
if (a[j] > Max) {
res[tot] += "ab";
Max = b[j];
}
} else {
res[++tot] = "";
for (int j = last + 1; j <= i; j++) {
string tmp = "";
for (int k = last * 2 + 1; k <= i * 2; k++)
if (rk[k] >= j) tmp += s[k];
chkmax(res[tot], tmp);
}
}
last = i;
}
}
for (int i = tot; i >= 1; i--)
dp[i] = max(dp[i + 1], res[i] + dp[i + 1]);
cout << dp[1] << endl;
return 0;
}
记数列奇数位的数和为 B B B ,偶数位的数和为 W W W 。
考虑 N N N 为偶数的情况,此时,先手初始时走 1 1 1 和 N N N 处可以分别保证得到 B B B 分和 W W W 分,因此先手得分不低于 max ( B , W ) \max(B,W) max(B,W) ;同时后手存在策略将先手得分控制在 max ( B , W ) \max(B,W) max(B,W) 以内,因此 N N N 为偶数时先手得分为 max ( B , W ) \max(B,W) max(B,W) 。
对于 N N N 为奇数的情况 ,首先,若先手初始时走 1 1 1 处,可以保证得到 B B B 分,并且,走在其余奇数位置,后手能够保证先手的得分不超过 B B B 。因此,先手想要得到多于 B B B 分,必须初始时走在偶数位置。
考虑此时游戏的进程:
在先手玩家的回合,先手玩家可能选择得到 B B B 分,结束游戏;或者走在某个偶数位置。
此时,后手玩家可以选择该位置的一侧,先手玩家得到该侧的 W W W 分,游戏在另一侧继续。
那么,先手玩家的决策可以写作一棵二叉树的形式,区间 [ L , R ] [L,R] [L,R] 对应的根节点表示先手玩家走在的偶数位置,若不存在,则表示先手玩家选择得到 B B B 分,结束游戏。左右子树对应了不同的后手玩家的决策。
不难发现,对于给定的决策树,后手玩家可以决定游戏结束的子树,因此,树的结构并不重要,先手玩家需要最大化所有结束区间的 B − W B-W B−W 的最小值。
二分答案后可以通过前缀和简单判断。
时间复杂度 O ( N L o g V ) O(NLogV) O(NLogV) 。
#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template void writeln(T x) {
write(x);
puts("");
}
int n, w, b, a[MAXN], s[MAXN];
bool check(int mid) {
int Min = 0;
for (int i = 2; i <= n; i += 2)
if (s[i - 1] - Min >= mid) chkmin(Min, s[i]);
return s[n] - Min >= mid;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(a[i]);
if (i & 1) b += a[i], s[i] = s[i - 1] + a[i];
else w += a[i], s[i] = s[i - 1] - a[i];
}
if (n % 2 == 0) {
printf("%d %d\n", max(w, b), min(w, b));
return 0;
}
int l = -w - b, r = w + b;
while (l < r) {
int mid = (l + r + 2 * w + 2 * b + 1) / 2 - w - b;
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d %d\n", w + l, b - l);
return 0;
}