2019 年百度之星·程序设计大赛 - 初赛二
A.度度熊与数字
签到。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T;
int v;
int calc(int x) {
int res = 0;
while(x) {
res += x % 10;
x /= 10;
}
return res;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> v;
vector <int> ans;
int x = calc(v);
for(int i = 1; i <= x; i++) {
if(x % i == 0 && v % i == 0) ans.push_back(i);
}
cout << ans.size() << '\n';
for(int i = 0; i < ans.size(); i++) cout << ans[i] << " \n"[i == ans.size() - 1];
}
return 0;
}
B.度度熊与排列
题意:
有一个序列\(p\),\(p_i\)表示将第\(i\)个位置的数移到第\(p_i\)位。
现在序列\(p\)丢失了,但输入串和输出串还在。
问是否能找到一个字典序最小的\(p\)。
思路:
- 对于第一对串直接暴力处理,利用一个vector来存储所有可能的位置。
- 之后暴力枚举后面所有的串,然后不断删去一些位置。
- 最后输出即可。
注意不合法条件的判断:
- 输入和输出串相关字符个数不相等。
- 某个字符串没有合法位置。
反正我这么来写细节有点多。
感觉题解思路挺好的,可以注意到如果把每个输入串的第\(i\)个连接起来,如果存在合法情况,那么一定存在某个\(j\),把每个输出串的第\(j\)个连接起来,两个串相等。
然后直接暴力枚举即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 55;
int T;
int n, m;
vector <int> p[N];
char s[N], t[N];
int cnt1[26], cnt2[26];
bool check(char *s, char *t) {
memset(cnt1, 0, sizeof(cnt1)); memset(cnt2, 0, sizeof(cnt2));
for(int i = 1; i <= m; i++) cnt1[s[i] - 'a']++;
for(int i = 1; i <= m; i++) cnt2[t[i] - 'a']++;
bool ok = 1;
for(int i = 0; i < 26; i++) if(cnt1[i] != cnt2[i]) ok = 0;
return ok;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
int ok = 1;
cin >> n >> m;
for(int i = 1; i <= m; i++) p[i].clear();
cin >> s + 1 >> t + 1;
if(!check(s, t)) ok = 0;
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= m; j++) {
if(t[j] == s[i]) p[i].push_back(j);
}
}
for(int i = 1; i < n; i++) {
cin >> s + 1 >> t + 1;
if(!check(s, t)) ok = 0;
for(int j = 1; j <= m; j++) {
for(auto it = p[j].begin(); it != p[j].end(); ++it) {
if(t[*it] != s[j]) {
p[j].erase(it); --it;
}
}
}
}
set <int> S;
for(int i = 1; i <= m; i++) {
for(auto it : p[i]) S.insert(it);
if((int)p[i].size() == 0) ok = 0;
}
if((int)S.size() != m) ok = 0;
// for(int i = 1; i <= m; i++) {
// for(auto it : p[i]) {
// cout << it << ' ';
// }
// cout << '\n';
// }
if(ok == 0) {
cout << -1 << '\n';
} else {
S.clear();
for(int i = 1; i <= m; i++) {
for(auto it : p[i]) {
if(S.count(it) == 0) {
S.insert(it);
cout << it << " \n"[i == m];
break;
}
}
}
}
}
return 0;
}
C.度度熊与运算式 1
题意:
有如下运算式:
每个\(op\)有两种可能:^或者\(?\),\(?\)表示可以为\(+\)或者^。
给出\(op_i\),确定表达式最大的值为多少。
思路:
以^为分隔符,运算式则可以分割为连续的几段。
之后有几个重要的观察:
- 最终答案的奇偶性和\(n+1\)的奇偶性相同;
- 每一段都可以对其进行二进制划分;
- 如果最终存在多个段等于\(2^k\),可以知道对于\(1\leq i\leq k\),都存在\(2^i\)的二进制划分。那么把\(2^k\)全部划分为\(1\)答案不可能变差的。
然后就没了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int T;
char s[1 << 21];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
vector <int> v;
while(T--) {
v.clear();
cin >> s + 1;
int n = strlen(s + 1);
int cnt = 1;
for(int i = 1; i <= n; i++) {
if(s[i] == '^') {
v.push_back(cnt);
cnt = 1;
} else cnt++;
}
v.push_back(cnt);
sort(v.begin(), v.end());
int ans = 0;
for(int i = 20; i; i--) {
for(auto &it : v) {
if(it >= (1 << i)) {
it -= (1 << i);
ans ^= (1 << i);
break;
}
}
}
ans ^= ((n + 1) & 1);
cout << ans << '\n';
}
return 0;
}
D.度度熊与组题
题意:
现有\(2n\)道题,每道题有一个难度\(a_i\)。
现在要将这些题分配给两套题,最终套题的难度等于\(\sum|a_{i1}-a_{i2}|\)。
现问使得最终套题难度最小的方案数有多少。
思路:
有一个观察:
- 难度最小方案中,难度小于等于\(v\)的题的数量与难度小于等于\(v-1\)的题的数量相差的绝对值不会超过\(1\)。
其实画图理解起来更加直观一点,但我把图画出来为什么没有意识到这点= =还是太菜了。
那么可以以\(v\)为阶段来进行\(dp\),因为阶段之间的关系已经明确了。
之后分奇偶情况讨论即可(满足情况在限制条件之中)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5, MOD = 1e9 + 7;
int T, n;
int a[N], b[N];
void Hash() {
sort(b + 1, b + n + 1);
b[0] = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + b[0] + 1, a[i]) - b;
}
ll dp[N], fac[N], inv[N];
int cnt[N];
ll qp(ll a, ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans;
}
void pre() {
fac[0] = 1;
for(int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % MOD;
inv[N - 1] = qp(fac[N - 1], MOD - 2);
for(int i = N - 2; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % MOD;
}
ll C(int n, int m) {
if(n < m || n < 0 || m < 0) return 0;
return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
pre();
cin >> T;
while(T--) {
cin >> n; n <<= 1;
for(int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
Hash();
for(int i = 1; i <= b[0]; i++) cnt[i] = 0;
dp[0] = 1;
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++) cnt[a[i]]++;
int tot = 0;
for(int i = 1; i <= b[0]; i++) {
if(tot & 1) {
if(cnt[i] & 1) {
dp[i] = dp[i - 1] * C(cnt[i], cnt[i] >> 1) % MOD;
} else {
dp[i] = dp[i - 1] * (C(cnt[i], cnt[i] >> 1) + C(cnt[i], cnt[i] / 2 - 1)) % MOD;
}
} else {
if(cnt[i] & 1) {
dp[i] = dp[i - 1] * C(cnt[i], cnt[i] >> 1) % MOD * 2ll % MOD;
} else {
dp[i] = dp[i - 1] * C(cnt[i], cnt[i] >> 1) % MOD;
}
}
tot += cnt[i];
}
cout << dp[b[0]] << '\n';
}
return 0;
}
E.度度熊与整数集合
题意:
有一个正整数集合\(S\),一开始为\(1\)~\(N\)。
- 每次选择一个集合,对于集合中的\(i,\cdots,j\),把\(a_i,\cdots,a_j\)都加上\(1\)。
- 选择一个\(x\),以\(x\)为分界将集合划分为两个集合,\(x\)包含在左边集合。
- 原集合消失。
不断重复上面过程,最终会得到序列\(a\)。
现在有最终的序列\(a\),求出字典序最小的\(x\)序列,或者可能不存在合法的\(x\)序列。
思路:
这是一个很有意思的题。
一开始想感觉很多东西都想不清楚,但只要思路正确一下就比较明朗了。
只要注意到每次操作就相当于在树上多出两个子节点,并且每个结点的深度即为目前的\(a\)值。
那么给出的\(a_i\)就是最终的叶子结点,每次的询问就相当于在这颗树上先往左边走得到的答案。
接下来考虑把树构建出来就行了。
构树的话就是不断将结点合并,每次合并深度会减一,如果将这颗树上的每个结点都理解为一个区间,就比较好想了。
可以用栈来模拟这个过程。
详见代码:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int a[N];
int T, n;
int sta[N], top, cnt;
int node[N][2], sz[N];
int f;
void dfs(int rt, int l, int r) {
if(rt == 0) return;
int ls = node[rt][0], rs = node[rt][1];
int mid = l + sz[ls] - 1;
if(ls && rs) {
if(f) cout << ' '; f = 1;
cout << mid;
}
dfs(ls, l, mid); dfs(rs, mid + 1, r);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= 2 * n; i++) node[i][0] = node[i][1] = 0, sz[i] = 1;
top = 0; cnt = n;
for(int i = 1; i <= n; i++) {
int x = i;
while(top && a[sta[top]] == a[x]) {
node[++cnt][0] = sta[top];
node[cnt][1] = x;
sz[cnt] = sz[sta[top]] + sz[x];
a[cnt] = a[x] - 1;
x = cnt; --top;
}
sta[++top] = x;
}
if(top != 1 || a[cnt] != 0) {
cout << "Impossible" << '\n';
} else {
f = 0;
cout << "Possible" << '\n';
dfs(cnt, 1, n);
cout << '\n';
}
}
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。