牛客 周赛112 20251012
牛客 周赛112 20251012
https://ac.nowcoder.com/acm/contest/118247
A:
题目大意:
void solve(){
int n, k;
cin >> n >> k;
int a = ceil(0.1 * n);
int sum = 0;
sum += a;
if (sum >= k){
cout << "Gold Medal";
return ;
}
int b = 2 * a;
sum += b;
if (sum >= k){
cout << "Silver Medal";
return;
}
int c = 3 * a;
sum += c;
if (sum >= k)
cout << "Bronze Medal";
else cout << "Da Tie";
}
签到
B:
题目大意:
void solve(){
int n;
cin >> n;
int mx = 0;
for (int i = 0; i < n; i ++){
int x;
cin >> x;
mx = max(x, mx);
}
cout << max(n, mx) << '\n';
}
如果能通过弃牌得到一张最大点数的牌,那么一定是全部弃掉
所以判断原有的点数最大的牌和总牌数量,输出较大者
C:
题目大意:
void solve(){
string s;
cin >> s;
int l = 0, r = s.size() - 1;
while (l < r){
if (s[l] == s[r]){
l ++ ;
r --;
}else break;
}
if (l >= r) cout << 1 << '\n';
else cout << 2 << '\n';
}
操作次数最多为两次,第一次选取全部的 \(0\) 字符,第二次选取全部的 \(1\) 字符
当且仅当原有的字符串本身就是一个回文串时,才能通过一次操作得到规定的序列
D:
题目大意:给定 \(2\times n\) 个元素,将他们平分为两组,每组的配对值为所有元素的按位与;输出在所有分组方式中配对值的最大值。
void solve(){
int n;
cin >> n;
vector<LL> a(2 * n);
for (auto &x : a) cin >> x;
vector<bool> vis(2 * n);
LL ans = 0;
for (int bit = 40; bit >= 0; bit --){
int cnt = 0;
for (int i = 0; i < 2 * n; i ++){
if (vis[i]) continue;
if ((a[i] >> bit) & 1) cnt ++;
}
if (cnt >= n){
ans |= (1 << bit);
for (int i = 0; i < 2 * n; i ++)
if (!((a[i] >> bit) & 1)) vis[i] = 1;
}
}
cout << ans << '\n';
}
转化题意,只需要选出 \(n\) 个元素出来,令他们的按位与最大
试填法,对于二进制位 \(i\) 如果这一位能够被保留下来,那么一定优于 \([1,i -1]\) 这些位被保留下来
换句话说,\(01000\) 一定大于 \(00111\) 。所以可以从大到小遍历二进制位,判断在当前待选的元素中能否使这一位保留
if ((a[i] >> bit) & 1) cnt ++;
如果 cnt
的数量大于等于 \(n\) ,那么就可以在 cnt
个待选的元素中选 \(n\) 个出来使得这一二进制位得到保留
然后对于在这一位上不为 \(1\) 的剩余待选元素,利用标记数组 vis
标记他们一定不能被选
if (cnt >= n){
ans |= (1 << bit);
for (int i = 0; i < 2 * n; i ++)
if (!((a[i] >> bit) & 1)) vis[i] = 1;
}
E:
题目大意:
const int mod = 998244353;
LL ksm(LL a, LL b, LL p){
LL res = 1;
while (b){
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
void solve(){
int n;
cin >> n;
vector<int> a(n);
for (auto &x : a) cin >> x;
sort(a.begin(), a.end());
map<int, int> mp;
for (auto x : a) mp[x] ++;
LL sum = 0;
for (int gcd = 1; gcd <= n; gcd ++){
int cnt = 0;
if (mp[gcd] == 0) continue;
for (int i = 2 * gcd; i <= n; i += gcd)
cnt += mp[i];
sum += ksm(2, cnt, mod) * (ksm(2, mp[gcd], mod) - 1) % mod;
sum %= mod;
}
LL ans = (ksm(2, n, mod) - 1 - sum) % mod;
if (ans < 0) ans += mod;
cout << ans << '\n';
}
因为 \(\gcd(a,b) \le a,b\) ,所以一个不美丽的数组一定满足数组中的所有元素都是数组最小值的倍数,且数组最小值一定为数组的 \(\gcd\)
\(O(n)\) 的枚举数组的最小值(\(\gcd\)),然后再 \(O(n/\gcd)\) 的找所有是 \(\gcd\) 的倍数的元素
那么不美丽的数组的个数可以在 \(O(n\log n)\) 的时间复杂度下计算出来
记当前枚举 \(\gcd\) 的元素的个数为 \(x\) ,其余为 \(\gcd\) 的倍数的元素个数为 \(y\),那么这种情况下的方案数为:
因为是 \(\gcd\) 的元素至少要选 \(1\) 个,所以方案数为 \(2^x-1\)
最后用总方案数减去不美丽数组的个数得到美丽数组的个数为 \(2^n-\sum (2^x-1)\times 2^y\)
F:
题目大意:
const int mod = 1e9 + 7;
void solve(){
int n;
cin >> n;
vector<int> a, m;
for (int i = 0; i < n; i ++){
string s;
cin >> s;
int val = 0;
int mn = 1e9;
for (auto c : s){
if (c == '(') val++;
else val--;
mn = min(mn, val);
}
a.push_back(val);
m.push_back(mn);
}
vector<LL> sum(1 << n), dp(1 << n);
for (int mask = 0; mask < 1 << n; mask ++)
for (int i = 0; i < n; i ++)
if (mask & (1 << i)) sum[mask] += a[i];
if (sum.back() != 0){
cout << 0 << '\n';
return ;
}
dp[0] = 1;
for (int mask = 0; mask < 1 << n; mask ++)
for (int i = 0; i < n; i ++)
if ((mask & (1 << i)) && sum[mask ^ (1 << i)] + m[i] >= 0)
dp[mask] = (dp[mask] + dp[mask ^ (1 << i)]) % mod;
cout << dp.back() << '\n';
}
经典的状压DP,定义 \(dp_i\) 表示选取了 \(i\) 状态的括号串且前缀和大于等于 \(0\) 的合法方案数
将 '(' 看作 \(1\) ,')' 看作 \(-1\) ,把括号串转化为前缀和来存储
对于一个合法的括号串,一定满足从 \([1,n]\) 的任意一段前缀和都大于等于 \(0\)
所以对于这 \(n\) 个括号子串,我们需要维护整个串的前缀和以及这个串的最小值
在拼接时,当前面的括号串的前缀和大于等于被拼接串的最小值时,才能保证合法性
if (!(mask >> i & 1) && sum[mask] + m[i] >= 0)
dp[mask | (1 << i)] = (dp[mask | (1 << i)] + dp[mask]) % mod;
预处理出所有 mask
状态对应的前缀和 sum
后,总时间复杂度为 \(O(2^nn)\)
特别的,如果选取了所有括号串后前缀和不为 \(0\) ,那么一定没有合法的方案