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() << '
';
        for(int i = 0; i < ans.size(); i++) cout << ans[i] << " 
"[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 << '
';
//        }
        if(ok == 0) {
            cout << -1 << '
';
        } 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 << " 
"[i == m];
                        break;
                    }
                }
            }
        }
    }
    return 0;
}

C.度度熊与运算式 1

题意:
有如下运算式:

[1 op_1 1 op_2 1cdots1 op_n 1 ]

每个(op)有两种可能:^或者(?)(?)表示可以为(+)或者^。
给出(op_i),确定表达式最大的值为多少。

思路:
以^为分隔符,运算式则可以分割为连续的几段。
之后有几个重要的观察:

  • 最终答案的奇偶性和(n+1)的奇偶性相同;
  • 每一段都可以对其进行二进制划分;
  • 如果最终存在多个段等于(2^k),可以知道对于(1leq ileq 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 << '
';
    }

    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]] << '
';
    }
    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"  << '
';
        } else {
            f = 0;
            cout << "Possible" << '
';
            dfs(cnt, 1, n);
            cout << '
';
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/heyuhhh/p/11392898.html