The 2022 ICPC Asia Xi'an Regional Contest
C. Clone Ranran
最优解一定是先复制,在做题。最多只需要复制大约 30 次,直接枚举即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a , b, c;
void solve(){
cin >> a >> b >> c;
int res = LLONG_MAX;
for( int i = 0 , t = 1 ; i < 31 ; i ++ , t *= 2 )
res = min(res , a * i + (c + t -1) / t * b);
cout << res << "\n";
}
int32_t main(){
int T;
for( cin >> T ; T ; T -- )
solve();
}
E. Find Maximum
所谓的\(f\)函数实际上就是把\(x\)转为三进制,三进制的长度加每一位数字之和。
所以最大值,一定长度和\(r\)相同,然后我们枚举\(r\)的每一位,把当前位减一,后面的位全部变成二。这样取最大值即可,同时要保证操作后的数字大于\(l\)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int inf = 1e18;
using vi = vector<int>;
unordered_map<int, int> g;
int f(int x) {
if (g[x] != 0) return g[x];
if (x % 3 == 0) return g[x] = f(x / 3) + 1;
else return g[x] = f(x - x % 3) + x % 3;
}
void solve() {
int l, r;
cin >> l >> r;
vector<int> a;
for (int x = r; x; x /= 3) a.push_back(x % 3);
std::reverse(a.begin(), a.end());
int res = a.size() + accumulate(a.begin(), a.end(), 0ll);
vector<int> b;
for (int x = l; x; x /= 3) b.push_back(x % 3);
while( b.size() < a.size() ) b.push_back(0);
std::reverse(b.begin(), b.end());
for (int i = 0; i < a.size(); i++) {
auto c = a;
if( c[i] == 0 ) continue;
c[i] --;
for( int j = i + 1 ; j < a.size() ; j ++ ) c[j] = 2;
if( c < b ) continue;
res = max( res , (int)c.size() + accumulate( c.begin(), c.end() , 0ll ) - (c.front() == 0) );
}
cout << res << "\n";
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
g[0] = 1;
for (cin >> t; t; t--)
solve();
return 0;
}
F. Hotel
根据性别数量,可以判断出住房的方案,在多个方案中取最优解即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main(){
ios::sync_with_stdio(false) , cin.tie(nullptr);
int n , c1 , c2 , res = 0;
cin >> n >> c1 >> c2;
string s;
for( int i = 1 , cnt ; i <= n ; i ++ ){
set<char> sex;
cin >> s;
for( auto c : s ) sex.insert( c );
cnt = min( 3 * c1 , 3 * c2 );
if( sex.size() < 3 ){
cnt = min( { cnt , c1 + c2 , c2 + c2 });
}
res += cnt;
}
cout << res << "\n";
return 0;
}
G. Perfect Word
字符串哈希,然后枚举子串,判断子串是否存在即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
using hashv = pair<int, int>;
const hashv mod = mp(1e9 + 7, 998244353);
const hashv base = mp(13331, 23333);
hashv operator+(hashv a, hashv b) {
int c1 = a.first + b.first, c2 = a.second + b.second;
if (c1 >= mod.first) c1 -= mod.first;
if (c2 >= mod.second) c2 -= mod.second;
return mp(c1, c2);
}
hashv operator-(hashv a, hashv b) {
int c1 = a.first - b.first, c2 = a.second - b.second;
if (c1 < 0) c1 += mod.first;
if (c2 < 0) c2 += mod.second;
return mp(c1, c2);
}
hashv operator*(hashv a, hashv b) {
return mp(a.first * b.first % mod.first, a.second * b.second % mod.second);
}
const int N = 1e5 + 5;
vector<hashv> p(N);
void hashStr(const string &s, vector<hashv> &v) {
v.resize(s.size() + 1);
for (int i = 1; i <= s.size(); i++)
v[i] = v[i - 1] * base + mp(s[i - 1], s[i - 1]);
return;
}
hashv getHash(int l, int r, const vector<hashv> &v) {
if (l > r) return mp(0, 0);
return v[r] - v[l - 1] * p[r - l + 1];
}
void init() {
p[0] = mp(1, 1);
for (int i = 1; i < N; i++)
p[i] = p[i - 1] * base;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
init();
int n;
cin >> n;
vector<string> s(n);
vector<vector<hashv>> hv(n);
for (auto &i: s) cin >> i;
set<hashv> vis;
for (int i = 0; i < n; i++)
hashStr(s[i], hv[i]), vis.insert(hv[i].back());
int res = 0;
for (int i = 0, len, f; i < n; i++) {
len = s[i].size(), f = 1;
if (len <= res) continue;
for (int l = 1; f and l <= len; l++)
for (int r = l; f and r <= len; r++)
if (vis.count(getHash(l, r, hv[i])) == 0) f = 0;
if (f) res = len;
}
cout << res << "\n";
return 0;
}
J. Strange Sum
\(i\)取\(n\),即找最大的两个值
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main(){
ios::sync_with_stdio(false) , cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
for( auto &i : a ) cin >> i;
sort( a.begin() , a.end() , greater<int>() );
cout << max( { 0ll , a[0] , a[0] + a[1] } );
}
L. Tree
题目相当于用最少的链和反链数量覆盖一棵有根树。
如果一个点被反链覆盖,但它的儿子没有被反链覆盖,因为必然存在一条链经过它的儿子,所以必然存在一条链经过它,将覆盖该点的反链调整为覆盖它的儿子,答案不会变劣。
因此,每添加一条反链,都相当于剥去当前树上的所有叶子。设 \(f_i\) 表示 \(i\) 的子树内最深的叶子与 \(i\) 的距离,这里距离定义为简单路径上的点数,容易证明 \(i\) 会被第 \(f_i\) 条反链覆盖。
假设我们不再添加反链,则每个叶子至少对应一条链,且每条链可以覆盖至少一个叶子,所以需要的链的数量为叶子数量。
如果我们用了 \(j - 1\) 条反链,那么 \(f_i = j\) 的节点 \(i\) 会裸露成叶子。因此,设 \(cnt_j\) 表示 \(f_i = j\) 的 \(i\) 的数量,则 \(\min\limits_{j = 1} ^ n cnt_j + j - 1\) 即为所求。时间复杂度 \(\mathcal{O}(n)\)。
#include <bits/stdc++.h>
using namespace std;
using vi = vector<int>;
void solve() {
int n;
cin >> n;
vector<vi> e(n + 1);
vi dis(n + 1, 0);
for (int i = 2, x; i <= n; i++)
cin >> x, e[x].push_back(i);
auto dfs = [e, &dis](auto &&self, int x) -> void {
for (auto y: e[x])
self(self, y), dis[x] = max(dis[x], dis[y] + 1);
return;
};
dfs(dfs, 1);
vi cnt(dis[1] + 1);
for (int i = 1; i <= n; i++)
cnt[dis[i]]++;
int res = INT_MAX;
for (int i = 0; i <= dis[1]; i++)
res = min(res, i + cnt[i]);
cout << res << "\n";
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int TC;
for (cin >> TC; TC; TC--)
solve();
return 0;
}