A | B | C | D | E | F | G | H | I | J | K | L |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 2 | 1 | 0 | 2 | 2 | 0 | 2 | 0 | 0 | 1 |
0:未完成
1:賽時做出
2:賽后補
總結:
3題鐵牌。比賽時負責C和G,想法的方向是正確的,但是想的過於浮於表面,沒有深入地去優化。簽到題常規題基本都是 復雜度高做法+使勁優化 = 正解。想好寫的做法,多尋找問題所擁有的特別性質,觀察數據范圍有助於發現突破口。
A - Accelerator(分治fft)
即求類似\(a_1a_2a_3 + a_2a_3+a_3\)這樣的式子。關鍵是求每一項的總和。就是每一項取與不取的,即
然后多項式每一項的系數就是需要的值。還需要乘上每一項對應的序列前面的組合個數。
有點卡時間,可以預處理出原根的次冪,減少常數。
#include <bits/stdc++.h>
typedef long long ll;
#define endl '\n'
using namespace std;
const int N = 3e5 + 10;
const int M = 998244353;
inline ll qpow(ll a ,ll b, ll m) {
ll res = 1;
while(b) {
if(b & 1) {
res = (res * a) % m;
}
a = (a * a) % m;
b >>= 1;
}
return res;
}
int rev[N];
void change(vector<int> &y, int len) {
for(int i = 0; i < len; i++) {
rev[i] = rev[i >> 1] >> 1;
if(i & 1) {
rev[i] |= len >> 1;
}
}
for(int i = 0; i < len; i++) {
if(i < rev[i]) {
swap(y[i], y[rev[i]]);
}
}
return ;
}
int ngn[N];
int rgn[N];
int inv[N];
void ntt(vector<int> &y, int len, int on) {
change(y, len);
for(int h = 2; h <= len; h <<= 1) {
int gn = ngn[h];
if(on == -1) {
gn = rgn[h];
}
for(int j = 0; j < len; j += h) {
ll g = 1;
for(int k = j; k < j + h / 2; k++) {
int u = y[k];
int t = g * y[k + h / 2] % M;
y[k] = (u + t) % M;
y[k + h / 2] = (u - t + M) % M;
g = g * gn % M;
}
}
}
if(on == -1) {
int iv = inv[len];
for(int i = 0; i < len; i++) {
y[i] = 1ll * y[i] * iv % M;
}
}
}
int get(int x) {
int res = 1;
while(res < x) {
res <<= 1;
}
return res;
}
int arr[N];
int fact[N];
vector<int> solve(int l, int r) {
if(l == r) {
vector<int> res(2);
res[0] = 1;
res[1] = arr[l];
return res;
}
int mid = (l + r) / 2;
vector<int> f = solve(l, mid);
vector<int> g = solve(mid + 1, r);
int tdeg = f.size() + g.size() - 2;
int len = get(tdeg + 1);
f.resize(len, 0);
g.resize(len, 0);
ntt(f, len, 1);
ntt(g, len, 1);
for(int i = 0; i < len; i++) {
f[i] = 1ll * f[i] * g[i] % M;
}
ntt(f, len, -1);
f.resize(tdeg + 1);
return move(f);
}
int main() {
fact[0] = 1;
for(int i = 1; i < N; i++) {
fact[i] = 1ll * fact[i - 1] * i % M;
inv[i] = qpow(i, M - 2 ,M);
}
for(int i = 1; i < N; i <<= 1) {
ngn[i] = qpow(3, (M - 1) / i, M);
rgn[i] = qpow(ngn[i], M - 2, M);
}
int t;
scanf("%d", &t);
while(t--) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &arr[i]);
vector<int> res = solve(1, n);
ll ans = 0;
for(int i = 1; i <= n; i++) {
ans += 1ll * fact[i] * fact[n - i] % M * res[i] % M;
}
ans %= M;
printf("%d\n", ans * qpow(fact[n] , M - 2, M) % M);
}
}
C - Club Assignment(分治,暴力)
將所有數排序,然后枚舉最高位,每次可以分為最高位為0/1兩部分。可以發現,橫跨兩組的數之間異或起來一定比組內的之間異或的大,即橫跨兩組的數之間異或對答案沒有貢獻(因為不會是最小值),即兩組之間沒有分配的限制,不用管。於是就可以繼續分成兩部分處理。
一直分下去,如果都是相同的數,相同的數大於2,說明答案為0;否則將它們分到不同的集合。否則最后一定會分成小於等於4的集合。對於每個小於等於4的集合,直接暴力枚舉每一種分配的組合,從中選擇結果最大的分配。最后答案就是所有這些結果的最小值。
這個最優的分配方案和異或最小生成樹也有關。
#include <bits/stdc++.h>
typedef long long ll;
#define endl '\n'
using namespace std;
const int N = 3e5 + 10;
const int M = 998244353;
#define INF 0x3f3f3f3f3f3f3f3f
typedef pair<int, int> PII;
PII arr[N];
int ans[N];
ll solve(int l, int r, int cur) {
if(r - l + 1 == 1) {
ans[arr[l].second] = 1;
return INF;
}
if(r - l + 1 <= 4) {
if(r - l + 1 == 2) {
ans[arr[l].second] = 1;
ans[arr[r].second] = 2;
return INF;
}
vector<PII> s1, s2;
ll mx = 0;
for(int i = l; i <= r; i++) {
for(int j = i + 1; j <= r; j++) {
vector<PII> a, b;
a.push_back(arr[i]);
a.push_back(arr[j]);
for(int k = l; k <= r; k++) {
if(k == i || k == j) continue;
b.push_back(arr[k]);
}
ll va = INF, vb = INF;
va = a.front().first ^ a.back().first;
if(b.size() > 1) vb = b.front().first ^ b.back().first;
if(min(va, vb) >= mx) {
s1 = move(a);
s2 = move(b);
mx = min(va, vb);
}
}
}
for(auto p : s1) ans[p.second] = 1;
for(auto p : s2) ans[p.second] = 2;
return mx;
}
if(cur < 0) {
if(r - l + 1 >= 3) {
for(int i = l; i <= r; i++) ans[arr[i].second] = 1;
return 0;
}
if(r - l + 1 == 2) {
ans[arr[l].second] = 1;
ans[arr[r].second] = 2;
} else {
ans[arr[l].second] = 1;
}
return INF;
}
int p = l;
while(p <= r) {
if((arr[p].first & (1 << cur))) {
break;
}
p++;
}
ll mi = INF;
if(p > l) mi = min(mi, solve(l, p - 1, cur - 1));
if(p <= r) mi = min(mi, solve(p, r, cur - 1));
return mi;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--) {
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
int x;
cin >> x;
arr[i] = {x, i};
}
sort(arr + 1, arr + 1 + n);
cout << solve(1, n, 30) << endl;
for(int i = 1; i <= n; i++) {
cout << ans[i];
}
cout << endl;
}
}
G - Game on Sequence(暴力)
設\(f(i)=1\),代表棋子到位置\(i\)是必勝的,反之亦然。然后就有很簡單的轉移方程
\(j\)代表\(i\)能轉移到的位置。顯然這樣時間復雜度太高。觀察發現,如果位置\(i\)的值為\(A\),在它之后也有一個位置\(j\)值為\(A\),那么\(f(i)=1\)。因為如果\(f(j)=0\),有\(f(i)=1\);如果\(f(j)=1\),說明\(j\)之后有個位置\(k\)有\(f(k)=0\),那么就有\(f(i)=1\)。因此只需維護最后面的不同的\(A\)的對應位置的\(f\)值即可。\(A\)的值域只有255,直接暴力計算即可。時限為6s,非常充裕。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 5e5 + 10;
const int M = 500;
const double eps = 1e-5;
int pos[500];
int num[500];
int npos[500];
bitset<500> vis;
bitset<500> flag;
int arr[N];
vector<int> np[500];
int len;
void add(int x, int p) {
if(!npos[x]) {
npos[x] = ++len;
num[len] = x;
} else {
for(int i = npos[x]; i + 1 <= len; i++) {
num[i] = num[i + 1];
npos[num[i]] = i;
}
num[len] = x;
npos[x] = len;
}
pos[x] = p;
arr[p] = x;
}
bool que(int p) {
if(p < pos[arr[p]]) return true;
vis.reset();
flag.reset();
for(int i = len; i >= 1; i--) {
int x = num[i];
bool ok = 0;
for(int nt : np[x]) {
if(vis[nt]) {
ok |= (!flag[nt]);
}
}
flag.set(x, ok);
vis.set(x);
}
return flag[arr[p]];
}
bool chk(int a, int b) {
int c = a ^ b;
int cnt = 0;
while(c) {
if(c & 1) cnt++;
c >>= 1;
}
return cnt <= 1;
}
int main() {
IOS;
for(int i = 0; i < 256; i++) {
for(int j = 0; j < 256; j++) {
if(chk(i, j)) np[i].push_back(j);
}
}
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) {
int x;
cin >> x;
add(x, i);
}
int cur = n + 1;
while(m--) {
int op;
cin >> op;
if(op == 1) {
int x;
cin >> x;
add(x, cur++);
} else {
int p;
cin >> p;
if(que(p)) cout << "Grammy" << endl;
else cout << "Alice" << endl;
}
}
}
I - Nim Cheater(輕重鏈性質)
Bob必勝就是石子數異或和為0,那么用簡單的背包dp,就可以得到答案。
每次都在序列尾部加入和刪除一個數的一系列操作,可以構成一顆樹。加入操作等價於在當前結點下插入一個結點;刪除操作相當於返回父親結點。
因此構造出這個樹,然后直接在樹上跑dp即可。空間復雜度為\(O(nm)\),其中\(n\)代表操作數(結點數),\(m\)代表石頭數值域。
顯然這樣空間會超,因此題解提供一個優秀的解法:找到這個樹輕重兒子,因為每個結點最多只有一個重兒子,每條路徑最多包含\(\log n\)個輕兒子,因此可以先遍歷輕兒子,再遍歷重兒子;每次到輕兒子存下當前dp狀態,等返回時再還原;重兒子則直接更新dp狀態。這樣空間復雜度就只有\(O(m\log n)\)。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 2e4 + 10;
const int M = 16385;
const double eps = 1e-5;
int ans[N];
int fa[N];
int val[N], cost[N];
vector<int> np[N];
int si;
bool hson[N];
int dfs(int p) {
int cnt = 1;
int mx = 0, tar = -1;
for(int nt : np[p]) {
int res = dfs(nt);
if(res > mx) {
mx = res;
tar = nt;
}
cnt += res;
}
if(tar >= 0) hson[tar] = 1;
return cnt;
}
int res[M];
void solve(int p, int tot) {
int *bk;
if(!hson[p]) {
bk = new int[M];
for(int i = 0; i < M; i++) bk[i] = res[i];
}
for(int i = 0; i < M; i++) {
res[i ^ val[p]] = min(res[i ^ val[p]], res[i] + cost[p]);
}
ans[p] = res[tot ^ val[p]];
int tar = -1;
for(int nt : np[p]) {
if(hson[nt]) {
tar = nt;
continue;
}
solve(nt, tot ^ val[p]);
}
if(tar >= 0) solve(tar, tot ^ val[p]);
if(!hson[p]) {
for(int i = 0; i < M; i++) res[i] = bk[i];
delete [] bk;
}
}
int num;
void printans(int p) {
if(!num) return ;
if(p) {
cout << ans[p] << endl;
num--;
}
for(int nt : np[p]) {
if(!num) return ;
printans(nt);
if(!num) return ;
cout << ans[p] << endl;
num--;
}
}
int main() {
IOS;
int n;
cin >> n;
int cur = 0;
for(int i = 1; i <= n; i++) {
string op;
cin >> op;
if(op == "ADD") {
si++;
cin >> val[si] >> cost[si];
np[cur].push_back(si);
fa[si] = cur;
cur = si;
} else {
cur = fa[cur];
}
}
memset(res, INF, sizeof res);
res[0] = 0;
dfs(0);
solve(0, 0);
num = n;
printans(0);
}