A - Wrong Answer (abc343 A)
題目大意
給定\(a,b\),輸出 \(c\),使得 \(a+b \neq c\)
解題思路
從\(0\)開始列舉\(c\)的取值即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int a, b;
cin >> a >> b;
int ans = 0;
while (ans == a + b)
++ans;
cout << ans << '\n';
return 0;
}
B - Adjacency Matrix (abc343 B)
題目大意
給定一個鄰接矩陣,對於第\(i\)個點,輸出與之有連邊的點的編號,升序。
解題思路
即輸出第\(i\)行中值為 \(1\)的列即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int x;
cin >> x;
if (x)
cout << j + 1 << " ";
}
cout << '\n';
}
return 0;
}
C - 343 (abc343 C)
題目大意
給定一個數\(n\),問不超過 \(n\)的最大的數 \(x\),其是迴文數,且是某個數\(y\)的三次方。
解題思路
我們可以列舉\(y\)而不是 \(x\),這樣得到的 \(x=y^3\)就一定是個三次方數,剩下的就是判斷 \(x\)是不是迴文數。
\(n\)只有 \(10^{18}\),對應的\(y\)的範圍就只有\(10^6\),判斷迴文數的複雜度是 \(O(\log )\),因此直接列舉判斷即可。
判斷迴文數可以直接用 to_string
轉換成字串然後對應位比較。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
LL n;
cin >> n;
int up = 1e6;
LL ans = 0;
for (int i = 1; i <= up; ++i) {
LL x = 1ll * i * i * i;
if (x > n)
break;
string s = to_string(x);
bool ok = true;
int len = s.size();
for (int i = 0; i < len; ++i) {
ok &= s[i] == s[len - i - 1];
}
if (ok) {
ans = x;
}
}
cout << ans << '\n';
return 0;
}
D - Diversity of Scores (abc343 D)
題目大意
初始\(n\)個人都是 \(0\)分。
第\(i\)個時刻,第 \(a_i\)個人分數增加 \(b_i\) 。
問每個時刻後,不同分數的個數。
解題思路
用map
維護每個數出現的次數,當其次數減為\(0\)的就刪去該元素。
每個時刻的答案就是 map
的元素個數。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, t;
cin >> n >> t;
map<LL, int> cnt;
vector<LL> score(n);
cnt[0] = n;
for (int i = 0; i < t; ++i) {
int a, b;
cin >> a >> b;
--a;
cnt[score[a]]--;
if (cnt[score[a]] == 0)
cnt.erase(score[a]);
score[a] += b;
cnt[score[a]]++;
cout << cnt.size() << '\n';
}
return 0;
}
E - 7x7x7 (abc343 E)
題目大意
三維座標,三個\(7 \times 7 \times 7\)的方塊放置。
給定 \(v1,v2,v3\),要求
- 恰好屬於一個方塊的體積是 \(v1\)。
- 恰好屬於兩個方塊的體積是 \(v2\)。
- 恰好屬於三個方塊的體積是 \(v3\)。
輸出任意一種擺放方式。
解題思路
由於數都不大,考慮直接列舉。
由於空間的平移不變形,第一個方塊放置位置可以是\((0,0,0)\)。
考慮列舉第二個和第三個的方塊,其座標範圍則是\([-7,7]\)。再遠的地方和 \(-7,7\)的放置效果是一樣的。
花\(O(2^6 7^6)\)列舉三個方塊位置後,要求\(v1,v2,v3\),再花\(O(2^3 7^3)\)統計的話總複雜度就是 \(10^{10}\),會超時了。
考慮 \(v3\)怎麼求,其實這跟求兩個線段的交線長度、兩個矩形的相交面積一樣,分別考慮每一維的交線長度,即右端點的最小值 \(-\)左端點的最大值,然後三維長度乘起來就是相交體積。
\(v2\)就是倆倆相交,再減去 \(v3\)對應的部分。
\(v1\)就是所有體積加起來,減去 \(v2\)和 \(v3\)對應的部分。
這樣就可以\(O(1)\)判斷,最終的時間複雜度是 \(O(2^6 7^6)\)。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int v1, v2, v3;
cin >> v1 >> v2 >> v3;
array<array<int, 3>, 3> a;
a[0] = {0, 0, 0};
constexpr int len = 7;
constexpr int sz = 4 * len;
array<array<array<int, sz>, sz>, sz> cnt{};
for (int i = 0; i < len; ++i) {
for (int j = 0; j < len; ++j) {
for (int k = 0; k < len; ++k) {
cnt[i][j][k]++;
}
}
}
auto calc = [&](auto& cnt) {
array<int, 4> tmp{};
for (auto& i : cnt)
for (auto& j : i)
for (auto& k : j) {
tmp[k]++;
}
return tmp;
};
auto calc3 = [&](int i, int j, int k, int l, int m, int n, int o, int p,
int q) {
int a = max(0, min({i + len, l + len, o + len}) - max({i, l, o}));
int b = max(0, min({j + len, m + len, p + len}) - max({j, m, p}));
int c = max(0, min({k + len, n + len, q + len}) - max({k, n, q}));
return a * b * c;
};
auto calc2 = [&](int i, int j, int k, int l, int m, int n) {
int a = max(0, min(i + len, l + len) - max(i, l));
int b = max(0, min(j + len, m + len) - max(j, m));
int c = max(0, min(k + len, n + len) - max(k, n));
return a * b * c;
};
auto solve = [&]() {
for (int i = -len; i <= len; ++i) {
for (int j = -len; j <= len; ++j) {
for (int k = -len; k <= len; ++k) {
for (int l = -len; l <= len; ++l) {
for (int m = -len; m <= len; ++m) {
for (int n = -len; n <= len; ++n) {
int n3 = calc3(0, 0, 0, i, j, k, l, m, n);
int n2 = calc2(0, 0, 0, i, j, k) +
calc2(0, 0, 0, l, m, n) +
calc2(i, j, k, l, m, n) - 3 * n3;
int n1 = 3 * len * len * len - n2 * 2 - n3 * 3;
if (n1 == v1 && n2 == v2 && n3 == v3) {
a[1] = {i, j, k};
a[2] = {l, m, n};
return true;
}
}
}
}
}
}
}
return false;
};
if (solve()) {
cout << "Yes" << '\n';
for (auto& i : a)
for (auto& j : i)
cout << j << ' ';
cout << '\n';
} else
cout << "No" << '\n';
return 0;
}
F - Second Largest Query (abc343 F)
題目大意
\(n\)個數\(a_i\), \(q\)個詢問 ,分兩種:
1 p x
,將\(a_p = x\)2 l r
,問\(a_{l..r}\)的次大值的出現次數,
解題思路
可以考慮帶修莫隊,但貌似會超時。
注意到次大值的出現次數
這資訊是可合併
的,即兩個區間的這一資訊,可以合併起來,得到更大區間的這一資訊。
因此用線段樹維護這一資訊即可,對應的是單點修改和區間查詢。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 2e5 + 8;
class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
public:
array<int, 2> max[N << 2];
array<int, 2> cmax[N << 2];
void pushup(int root) {
array<array<int, 2>, 4> tmp = {max[lson], max[rson], cmax[lson],
cmax[rson]};
map<int, int> cnt;
for (auto& x : tmp) {
cnt[x[0]] += x[1];
}
max[root] = {cnt.rbegin()->first, cnt.rbegin()->second};
cmax[root] = {0, 0};
cnt.erase(cnt.rbegin()->first);
if (!cnt.empty())
cmax[root] = {cnt.rbegin()->first, cnt.rbegin()->second};
}
array<array<int, 2>, 2> combine(array<array<int, 2>, 2> a,
array<array<int, 2>, 2> b) {
array<array<int, 2>, 4> tmp = {a[0], a[1], b[0], b[1]};
map<int, int> cnt;
for (auto& x : tmp) {
cnt[x[0]] += x[1];
}
array<array<int, 2>, 2> ret;
ret[0] = {cnt.rbegin()->first, cnt.rbegin()->second};
ret[1] = {0, 0};
cnt.erase(cnt.rbegin()->first);
if (!cnt.empty())
ret[1] = {cnt.rbegin()->first, cnt.rbegin()->second};
return ret;
}
void build(int root, int l, int r, vector<int>& a) {
if (l == r) {
max[root] = {a[l - 1], 1};
cmax[root] = {0, 0};
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid, a);
build(rson, mid + 1, r, a);
pushup(root);
}
void update(int root, int l, int r, int pos, int val) {
if (l == r) {
max[root] = {val, 1};
cmax[root] = {0, 0};
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
update(lson, l, mid, pos, val);
else
update(rson, mid + 1, r, pos, val);
pushup(root);
}
array<array<int, 2>, 2> query(int root, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return {max[root], cmax[root]};
}
int mid = (l + r) >> 1;
array<array<int, 2>, 2> lret = {{{0, 0}, {0, 0}}},
rret = {{{0, 0}, {0, 0}}};
if (L <= mid)
lret = query(lson, l, mid, L, R);
if (R > mid)
rret = query(rson, mid + 1, r, L, R);
return combine(lret, rret);
}
} sg;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
vector<int> a(n);
for (auto& x : a)
cin >> x;
sg.build(1, 1, n, a);
while (q--) {
int op, x, y;
cin >> op >> x >> y;
if (op == 1) {
sg.update(1, 1, n, x, y);
} else {
auto ret = sg.query(1, 1, n, x, y);
cout << ret[1][1] << '\n';
}
}
return 0;
}
G - Compress Strings (abc343 G)
題目大意
給定\(n\)個字串,問最小長度的字串,使得這 \(n\)個字串都是該串的子串。
解題思路
注意到\(n\)只有 \(20\)。
考慮最樸素的做法,就是花\(O(n!)\)列舉這\(n\)個字串的順序,然後再把倆倆相鄰的字串中,重複的部分(最長前字尾)刪去,得到一個字串。所有這樣的字串長度取個最小值則為答案,
但還是有點小問題,在原來的\(n\)個字串中,對於相同的字串,我們肯定只保留一個,而對於子串的情況,該子串也不需要。因此得事先預處理,將相同串和子串的都去掉,可以用hash
的方法在\(O(n^210^5)\)內做到。
考慮最佳化這個樸素做法,容易發現,當確定了前 \(i\)個字串的順序後, 對於第\(i+1\)個字串取什麼,使得拼接字串的長度所造成的影響,只取決於第 \(i\)個字串是什麼,對於前面的字串的順序怎樣沒關係。
因此我們不必保留前 \(i\)個字串的順序 這一資訊,只需知道我取了哪些字串
,且第 \(i\)個字串是什麼。透過這兩個資訊,我就可以進行轉移:即選擇哪個還未取的字串,以及取了之後最終長度是多少。
因此設\(dp[i][j]\)表示我取的字串的二進位制表示為 \(i\) ,最後一個字串是\(j\)的最小長度。
事先花\(O(n^2 10^5)\)預處理 \(cost[i][j]\)表示字串 \(i\)(左)和字串 \(j\)(右)拼接起來 後增加的長度(列舉前字尾長度,再hash
判斷是否相等)。這樣轉移時列舉下一個字串後,就可以\(O(1)\)得到代價。因此轉移的複雜度是 \(O(n)\)。
總的時間複雜度是 \(O(n^2 2^n)\)
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
typedef long long LL;
typedef unsigned long long ULL;
const int x = 135;
const int N = 2e5 + 10;
const int p1 = 1e9 + 7, p2 = 1e9 + 9;
ULL xp1[N], xp2[N], xp[N];
void init_xp() {
xp1[0] = xp2[0] = xp[0] = 1;
for (int i = 1; i < N; ++i) {
xp1[i] = xp1[i - 1] * x % p1;
xp2[i] = xp2[i - 1] * x % p2;
xp[i] = xp[i - 1] * x;
}
}
struct String {
char s[N];
int length, subsize;
bool sorted;
ULL h[N], hl[N];
ULL hash() {
length = strlen(s);
ULL res1 = 0, res2 = 0;
h[length] = 0; // ATTENTION!
for (int j = length - 1; j >= 0; --j) {
#ifdef ENABLE_DOUBLE_HASH
res1 = (res1 * x + s[j]) % p1;
res2 = (res2 * x + s[j]) % p2;
h[j] = (res1 << 32) | res2;
#else
res1 = res1 * x + s[j];
h[j] = res1;
#endif
// printf("%llu\n", h[j]);
}
return h[0];
}
// 獲取子串雜湊,左閉右開區間
ULL get_substring_hash(int left, int right) const {
int len = right - left;
#ifdef ENABLE_DOUBLE_HASH
// get hash of s[left...right-1]
unsigned int mask32 = ~(0u);
ULL left1 = h[left] >> 32, right1 = h[right] >> 32;
ULL left2 = h[left] & mask32, right2 = h[right] & mask32;
return (((left1 - right1 * xp1[len] % p1 + p1) % p1) << 32) |
(((left2 - right2 * xp2[len] % p2 + p2) % p2));
#else
return h[left] - h[right] * xp[len];
#endif
}
void get_all_subs_hash(int sublen) {
subsize = length - sublen + 1;
for (int i = 0; i < subsize; ++i)
hl[i] = get_substring_hash(i, i + sublen);
sorted = 0;
}
void sort_substring_hash() {
sort(hl, hl + subsize);
sorted = 1;
}
bool match(ULL key) const {
if (!sorted)
assert(0);
if (!subsize)
return false;
return binary_search(hl, hl + subsize, key);
}
void init(const char* t) {
length = strlen(t);
strcpy(s, t);
}
};
const int inf = 1e9;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
init_xp();
int n;
cin >> n;
vector<String> s(n);
for (auto& i : s) {
string x;
cin >> x;
i.init(x.c_str());
i.hash();
}
vector<String> t;
vector<int> sub(n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i == j || sub[j])
continue;
if (s[i].length > s[j].length)
continue;
s[j].get_all_subs_hash(s[i].length);
s[j].sort_substring_hash();
if (s[j].match(s[i].h[0])) {
sub[i] = true;
break;
}
}
if (!sub[i])
t.push_back(s[i]);
}
n = t.size();
int up = (1 << n);
vector<vector<int>> dp(up, vector<int>(n, inf));
dp[0][0] = 0;
auto calc = [&](int i, int j) {
int ans = min(t[i].length, t[j].length);
while (ans > 0 &&
t[i].get_substring_hash(t[i].length - ans, t[i].length) !=
t[j].get_substring_hash(0, ans))
--ans;
return t[j].length - ans;
};
vector<vector<int>> cost(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i == j)
continue;
cost[i][j] = calc(i, j);
}
}
for (int i = 0; i < n; ++i)
dp[1 << i][i] = t[i].length;
for (int i = 0; i < up; ++i) {
for (int j = 0; j < n; ++j) {
if (i & (1 << j)) {
for (int k = 0; k < n; ++k) {
if (j != k && i & (1 << k)) {
dp[i][j] =
min(dp[i][j], dp[i ^ (1 << j)][k] + cost[k][j]);
}
}
}
}
}
cout << ranges::min(dp.back()) << '\n';
return 0;
}