A. Two Screens
難點是讀題,找到最長公共字首即可。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const i32 inf = INT_MAX / 2;
const int mod = 1e9 + 7;
void solve(){
string s, t;
cin >> s >> t;
int res = s.size() + t.size();
int i = 0;
while(i < s.size() and i < t.size() and s[i] == t[i])
i ++, res --;
if(i > 0) res ++;
cout << res << "\n";
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
cin >> T;
while(T --) solve();
return 0;
}
B. Binomial Coefficients, Kind Of
打表找規律
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const i32 inf = INT_MAX / 2;
const int mod = 1e9 + 7;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int N = 1e5;
vi f(N + 1);
f[0] = 1;
for(int i = 1; i <= N; i ++)
f[i] = f[i - 1] * 2 % mod;
int T;
cin >> T;
vi n(T), k(T);
for(auto &i : n) cin >> i;
for(auto &i : k) cin >> i;
for(int i = 0 ; i < T; i ++) {
if(n[i] == k[i]) cout << "1\n";
else cout << f[k[i]] << "\n";
}
return 0;
}
C. New Game
首先我們可以統計出每個數字出現的次數。
可以用雙指標求出最大的\([l,r]\),滿足區間內的數至少出現一次且\(r < l + k\)。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const i32 inf = INT_MAX / 2;
const int mod = 1e9 + 7;
void solve(){
int n, k;
cin >> n >> k;
map<int, int> cnt;
for(int i = 0, x; i < n ; i ++)
cin >> x, cnt[x] ++;
vector<pii> a;
for(auto it : cnt) a.push_back(it);
int m = a.size();
int res = 0, sum = 0;
for(int l = 0, r = -1; r < m;) {
if(r < l) {
if(l >= m) break;
assert(sum == 0);
sum = a[l].second , r = l;
}
while(r + 1 < m and a[r + 1].first < a[l].first + k and a[r + 1].first == a[r].first + 1)
r ++, sum += a[r].second;
res = max(res, sum);
sum -= a[l].second, l ++;
}
cout << res << "\n";
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
cin >> T;
while(T --) solve();
return 0;
}
D. Attribute Checks
考慮字首中\(i\)中有\(sum\)點屬性點,我們用\(f[x]\)表示當前分配\(x\)點智力,\(y = sum - x\)點力量可以滿足多少次檢查。
現在有三種情況。
如果當前是智力檢測\(r_i\),則分配了\(x\in[r_i, sum]\)的\(f[x]\)均加\(1\)。
如果當前是力量檢測\(r_i\),則分配了\(y\in[-r_i, sum]\)的\(f[x]\)均加\(1\)。
這兩種情況是區間修改。
如果當前是一個待分配的智力點,則\(f[x]\)可以從\(f[x],f[x-1]\)轉移過來。
這種情況是單點查詢。
因此我們可以用一個樹狀陣列來維護。
但是,我們注意到\(dp\)操作,因此我們可以直接新建一個樹狀陣列更新好之後再賦值回去。
\(f[x]\)的值域是\(m\),至多重建\(m\)次,因此重建的總複雜度是\(O(m^2\log m)\)。
區間修改的次數是\(n\),複雜度是\(O(n\log m)\)。
因此最終的複雜度就是\(O(n \log m)\)。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const i32 inf = INT_MAX / 2;
const int mod = 1e9 + 7;
struct BinaryIndexedTree{
#define lowbit(x) ( x & -x )
int n;
vector<int> b;
BinaryIndexedTree(int n) : n(n) , b(n + 1 , 0){};
void modify(int i , int y) {
for(; i <= n ; i += lowbit(i) ) b[i] += y;
return;
}
void modify(int l, int r, int y) {
l ++ , r ++;
modify(l, y), modify(r + 1, -y);
}
int calc(int i){
i ++;
int sum = 0;
for( ; i ; i -= lowbit(i) ) sum += b[i];
return sum;
}
};
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
int sum = 0;
BinaryIndexedTree f(m + 1);
for(int i = 0 , x ; i < n; i ++) {
cin >> x;
if(x == 0) {
sum ++;
BinaryIndexedTree g(m + 1);
g.modify(0 , 0, f.calc(0));
for(int j = 1, x; j <= sum; j ++) {
x = max(f.calc(j), f.calc(j - 1));
g.modify(j, j, x);
}
f = move(g);
} else if(x > 0) {
if(x > sum) continue;
f.modify(x, sum , 1);
} else {
x = - x;
if(x > sum) continue;
f.modify(0, sum - x, 1);
}
}
int res = 0;
for(int i = 0; i <= m; i ++)
res = max(res, f.calc(i));
cout << res;
return 0;
}
其實可以注意到區間只有當獲得能力值是才會查詢,並且每次查詢是要查詢全部值。因此我們可以直接用差分來實現區間修改,對於查詢我們可以直接求出字首和,然後再進行轉移。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const i32 inf = INT_MAX / 2;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
vi f(m + 2);
for (int x, sum = 0; n; n--) {
cin >> x;
if (x == 0) {
sum ++;
for (int i = 1; i <= m; i++)
f[i] += f[i - 1];
for (int i = m; i > 0; i--)
f[i] = max(f[i], f[i - 1]);
for (int i = m; i > 0; i--)
f[i] = f[i] - f[i - 1];
} else if (x > 0) {
if (x > sum) continue;
f[x]++, f[sum + 1]--;
} else {
x = -x;
if (x > sum) continue;
f[0]++, f[sum - x + 1]--;
}
}
for (int i = 1; i <= m; i++)
f[i] += f[i - 1];
cout << ranges::max(f);
return 0;
}
E. Card Game
對於花色為\(1\)的牌,Alice 拿到的牌數一定不少於 Bob。對於花色不為\(1\)的牌,Alice 拿到的牌數一定不多於Bob。
我們在分配的時候可以一次分配兩張牌,其中較大的給 Alice,另一張給 Bob。
設 dp 狀態\(f[i][j]\)為某種花色的牌,對於前\(i\)張牌,分配了\(i-j\)張牌,剩下\(j\)張牌的方案數,因此一定有\((i-j) | 2\),也就是\(i,j\)奇偶性相同。
對於第\(i\)牌有兩種情況。如果不分配,則前\(i-1\)張牌分配了\(j-1\)張。如果分配,則需要一張之前沒有分配的牌,因此前\(i-1\)張分配了\(j+1\)張。因此有如下轉移
考慮只有第一種花色Alice可以多拿。其他花色只能 Bob 多拿,並且 Bob 每多拿一張,Alice 就要消耗一張花色 1 的牌。因此我們可以記 dp 狀態為\(g[i][j]\),表示前\(i\)種花色的牌,此時 Alice還多出來了幾張花色為 1 的牌。對於花色 1 的牌,因為只能是 Alice 多拿,因此\(g[1][i] = f[m][i]\)。對於其他花色的牌,我們可以列舉出 Bob 多拿了\(k\)張,則存在轉移
注意這裡的\(j,k\)都只能是偶數。
對於第二個 dp 可以\(O(N^3)\)轉移即可。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int,int>;
const int mod = 998244353;
struct mint {
int x;
mint(int x = 0) : x(x) {}
int val() {
return x = (x % mod + mod) % mod;
}
mint &operator=(int o) { return x = o, *this; }
mint &operator+=(mint o) { return (x += o.x) >= mod && (x -= mod), *this; }
mint &operator-=(mint o) { return (x -= o.x) < 0 && (x += mod), *this; }
mint &operator*=(mint o) { return x = (i64) x * o.x % mod, *this; }
mint &operator^=(int b) {
mint w = *this;
mint ret(1);
for (; b; b >>= 1, w *= w) if (b & 1) ret *= w;
return x = ret.x, *this;
}
mint &operator/=(mint o) { return *this *= (o ^= (mod - 2)); }
friend mint operator+(mint a, mint b) { return a += b; }
friend mint operator-(mint a, mint b) { return a -= b; }
friend mint operator*(mint a, mint b) { return a *= b; }
friend mint operator/(mint a, mint b) { return a /= b; }
friend mint operator^(mint a, int b) { return a ^= b; }
};
i32 main() {
int n, m;
cin >> n >> m;
vector f(m + 1, vector<mint>(m + 1));
f[0][0] = 1;
for(int i = 1; i <= m; i ++)
for(int j = 0; j <= i; j ++) {
if(i % 2 != j % 2) continue;
if(j - 1 >= 0) f[i][j] += f[i - 1][j - 1];
if(j + 1 <= m) f[i][j] += f[i - 1][j + 1];
}
vector g(n + 1, vector<mint>(m + 1));
for(int i = 0; i <= m; i += 2)
g[1][i] = f[m][i];
for(int i = 2; i <= n; i ++) {
for(int j = 0; j <= m; j += 2) {
for(int k = 0; k + j <= m; k += 2) {
g[i][j] += g[i - 1][k + j] * f[m][k];
}
}
}
cout << g[n][0].val();
return 0;
}