Educational Codeforces Round 150
A. Game with Board
当\(n>4\)时,Alice 可以把序列变成n-2,1,1
,Bob只能操作成n-2,2
,此时 Alice获胜。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
constexpr int inf = 1e9;
void solve(){
int n;
cin >> n;
if( n < 5 ) cout << "Bob\n";
else cout << "Alice\n";
return ;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
for( cin >> t ; t ; t -- )
solve();
return 0;
}
B. Keep it Beautiful
手推就可以发现,满足条件的序列是由两个递增的部分形成的。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
constexpr int inf = 1e9;
void solve() {
int n, last;
cin >> n >> last;
cout << 1;
int f = 0, t = last;
for (int i = 2, x; i <= n; i++) {
cin >> x;
if (f == 0) {
if (x >= last) last = x, cout << 1;
else if (x <= t) last = x, f = 1, cout << 1;
else cout << 0;
} else if( f == 1 ){
if (last <= x and x <= t) last = x, cout << 1;
else if (x == t) f = 2, cout << 1;
else cout << 0;
}else {
if( t == x ) cout << 1;
else cout << 0;
}
}
cout << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
for (cin >> t; t; t--)
solve();
return 0;
}
C. Ranom Numbers
先说说我的做法,首先把序列进行反序。这样每个数的符号只取决于前缀最大值与当前值的大小关系。
这样就可以进行DP,设状态为\(f[i][j][0/1]\)表示前\(i\)位,前缀最大值为\(j\),且是否进行过一次操作。
这样只需要枚举前一位的前缀最大值,在枚举当前位不修改还是修改成何值,就可以推出当前的状态和状态对应的答案。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
constexpr int inf = 1e18;
const int d[] = {1, 10, 100, 1000, 10000};
void solve() {
string s;
cin >> s;
int n = s.size();
vector<int> a(1);
for (int i = n; i >= 1; i--)
a.push_back(s[i - 1] - 'A');
vector f(n + 1, vector(5, vector(2, -inf)));
f[0][0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= 4; j++) {
if (j > a[i]) { //
f[i][j][0] = max(f[i][j][0], f[i - 1][j][0] - d[a[i]]);
f[i][j][1] = max(f[i][j][1], f[i - 1][j][1] - d[a[i]]);
} else { // a[i] >= j
f[i][a[i]][0] = max(f[i][a[i]][0], f[i - 1][j][0] + d[a[i]]);
f[i][a[i]][1] = max(f[i][a[i]][1], f[i - 1][j][1] + d[a[i]]);
}
for (int k = 0; k <= 4; k++) {
if (j > k) {
f[i][j][1] = max(f[i][j][1], f[i - 1][j][0] - d[k]);
} else {
f[i][k][1] = max(f[i][k][1], f[i - 1][j][0] + d[k]);
}
}
}
int res = -inf;
for (int i = 0; i <= 4; i++)
res = max({res, f[n][i][1], f[n][i][0]});
cout << res << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
for (cin >> t; t; t--)
solve();
return 0;
}
再说一下 cup 的贪心思路。首先我们修改只有两种情况
- 把一个值本身变大,这样修改越靠后越好
- 把一个值变小,使得其他的数符号变为正,这样修改越靠前越好
所以我们找出每种值出现的第一个和最后一个位置,然后暴力枚举把这一位修改成何值,然后计算答案取最大值即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
using vi = vector<int>;
constexpr int inf = 1e18;
const int d[] = {1, 10, 100, 1000, 10000};
void solve() {
string s;
cin >> s;
int n = s.size(), res = -inf;
vector<int> a(n);
for (int i = 0; i < n; i++) a[i] = s[i] - 'A';
reverse(a.begin(), a.end());
vi l(5, LLONG_MAX), r(5, LLONG_MIN);
for (int i = 0; i < n; i++)
l[a[i]] = min(l[a[i]], i), r[a[i]] = max(r[a[i]], i);
l.insert(l.end(), r.begin(), r.end());
auto b = a;
for (auto index: l) {
if( index < 0 or index >= n ) continue;
for (b[index] = 0; b[index] < 5; b[index]++) {
int preMAX = 0, sum = 0;
for (const int &i: b) {
if (preMAX > i) sum -= d[i];
else sum += d[i], preMAX = max(preMAX, i);
}
res = max(res, sum);
}
b[index] = a[index];
}
cout << res << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
for (cin >> t; t; t--)
solve();
return 0;
}
D. Pairs of Segments
我们枚举区间的组合情况,如果两个区间相交,则求出区间的并集\([a,b]\)
这样相当于让我们选择若干个\([a_1,b_1],[a_2,b_2],\dots\),并且任意两个区间不想交,这样就可以转换成简单的线性 dp,求出最多可以选择的区间数量后,每一个区间都对应原始的两个区间。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
using vi = vector<int>;
#define mp make_pair
constexpr int inf = 1e18;
pii inter(const pii &x, const pii &y) {
return mp(max(x.first, y.first), min(x.second, y.second));
}
bool isInter(const pii &x, const pii &y) {
auto s = inter(x, y);
return s.first <= s.second;
}
pii uni(const pii &x, const pii &y) {
return mp(min(x.first, y.first), max(x.second, y.second));
}
#define hash(a, x) ( lower_bound(a.begin(),a.end(),x) - a.begin() + 1 )
void solve() {
int n;
cin >> n;
vector<int> num;
vector<pii> g(n);
for (auto &[l, r]: g)
cin >> l >> r, num.push_back(l), num.push_back(r);
sort(num.begin(), num.end());
num.resize(unique(num.begin(), num.end()) - num.begin());
for (auto &[l, r]: g)
l = hash(num, l), r = hash(num, r);
int m = num.size() + 1;
vector<vector<int>> pos( m+1);
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (isInter(g[i], g[j])) {
auto it = uni(g[i], g[j]);
pos[it.second].push_back(it.first);
}
vector<int> f(m+1);
for( int i = 1 ; i <= m ; i ++){
f[i] = f[i-1];
for( auto j : pos[i] )
f[i] = max( f[i] , f[j-1] + 1 );
}
cout << n - f.back() * 2 << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
for (cin >> t; t; t--)
solve();
return 0;
}
E. Fill the Matrix
横着看,相当于被分成的若干个线段。可以想到的结论是优先填较长的线段更优。
这样的话,我们要求出所有的线段。
我们从下往上看,每一行都和上一行基本相同,只有当这一行有新的节点,会把上一行的某些线段,阶段成两段。所以我们可以根据节点被插入的时间计算出线段产生和消失的时间点,进一步计算出线段的数量。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
using vi = vector<int>;
#define mp make_pair
constexpr int inf = 1e18;
void solve() {
int n;
cin >> n;
vi cnt(n + 1), time(n + 2);
vector<vi> pos(n + 1);
for (int i = 1, x; i <= n; i++)
cin >> x, pos[x].push_back(i);
int m;
cin >> m;
set<int> s;
s.insert(0), s.insert(n + 1);
time[0] = time[n + 1] = n;
for (int i = n; i >= 0; i--) {
for (auto x: pos[i]) {
auto it = s.lower_bound(x);
int pre = *prev(it), nxt = *it;
int from = min(time[pre], time[nxt]);
cnt[nxt - pre - 1] += from - i;
s.insert(x), time[x] = i;
}
}
int res = 0;
for (int i = n, t; i >= 1; i--) {
t = min(cnt[i], m / i);
res += t * (i - 1);
cnt[i] -= t, cnt[i - 1] += cnt[i] , m -= t * i;
}
cout << res << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
for (cin >> t; t; t--)
solve();
return 0;
}