A、CCA的词典

因为我们可以随意交换相邻两个数,根据冒泡排序规则,所以我们可以得到由这些字母组成的任意字符串。
那么我们就对每行字符串进行字典序排序,再使用计数器计数即可。

#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define all(__vv__) (__vv__).begin(), (__vv__).end()
#define endl "\n"
#define pai pair<int, int>
#define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__))
#define rep(i, sta, en) for(int i=sta; i<=en; ++i)
#define repp(i, sta, en) for(int i=sta; i>=en; --i)
typedef long long ll; typedef unsigned long long ull; typedef long double ld;
inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar())    s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void print(ll x, int op = 10) { if (!x) { putchar('0'); if (op)    putchar(op); return; }    char F[40]; ll tmp = x > 0 ? x : -x;    if (x < 0)putchar('-');    int cnt = 0;    while (tmp > 0) { F[cnt++] = tmp % 10 + '0';        tmp /= 10; }    while (cnt > 0)putchar(F[--cnt]);    if (op)    putchar(op); }
inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll qpow(ll a, ll b) { ll ans = 1;    while (b) { if (b & 1)    ans *= a;        b >>= 1;        a *= a; }    return ans; }    ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; }
const int dir[][2] = { {0,1},{1,0},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1} };
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
struct Node {
    ll val;
    int id;
    bool operator < (const Node& opt) const {
        return val < opt.val;
    }
};

const int N = 1e6 + 7;
ll n, m;
ll p[N];
void solve() {
    js;
    string s;
    map<string, int> mp;
    cin >> n;
    rep(i, 1, n) {
        cin >> s;
        sort(all(s));
        ++mp[s];
    }
    cin >> m;
    rep(i, 1, m) {
        cin >> s;
        sort(all(s));
        cout << mp[s] << endl;
    }
}

int main() {
    //int T = read();    while (T--)
    solve();
    return 0;
}

B、CCA的搬运

第一行两个整数分别是代表我们有个球次操作。

第二行输入个整数,代表每个球的重量。

第三行输入个整数,代表每次操作要把放在当前球堆的堆顶。

我们每次拿到堆顶的操作,花费的都是当前球上方全部小球重量和。现在要你求解如何安排起始顺序使得花费最小。

我们对于每个考虑从来看,如果是第一次出现的那么它的上方一定会有之前出现过的全部不同数。例如数组为这样的话遍历到的时候,在他上方一定会有两个小球,那么最优的解法一定是把号球放在球的下方。那么解法就出来了。

我们遍历全部的并且使用一个统计第一次出现的他是什么小球以及对应价值。

接下来就对这样的初始进行查找,模拟从顶向下的小球重量。并且每次遇到了对应小球,就把它换到最顶部。

struct Node {
    ll val;
    ll id;
    bool operator < (const Node& opt) const {
        return val < opt.val;
    }
}T[2007];
int top = 0;

const int N = 2000 + 7;
ll n, m;
ll a[N], b[N];
bool vis[N];
vector<int> st;

void solve() {
    n = read(), m = read();
    rep(i, 1, n)    a[i] = read();
    rep(i, 1, m) {
        b[i] = read();
        if (!vis[b[i]]) {
            T[++top] = { a[b[i]],b[i] }; // Node(val , id);
        }
        vis[b[i]] = 1;
    }
    ll ans = 0;
    rep(i, 1, m) {
        rep(j, 1, top) {
            if (T[j].id == b[i]) {
                Node tmp = T[j];
                repp(k, j, 2)   T[k] = T[k - 1];
                T[1] = tmp;
                break;
            }
            else ans += T[j].val;
        }
    }
    print(ans);
}

C、CCA的子树

首先把问题简化思考,如果给你的是一颗二叉树的话,你如何思考这个问题。

从根节点1出发,我们可以找到左边子树的最大值与右边子树的最大值,二者一定可以保证不存在公共的祖先,并且满足了找左右最大值的要求,我们如果找到了这样的两棵子树那么就把他们更新一下答案。

并且在左右子树中也同样是这样的问题,我们依次往下即可求解正确的答案。

现在仅仅是把树形结构从二叉树变化成了多叉树,那么同样的道理就是在多个儿子中间找到最大的两个儿子,如果只有一个儿子就不能更新答案。并且返回包含节点在内的最大子树点。

这里判断只有单链一种情况,如果你把答案初始化到代表无穷小的话,可能会出现这样的数据把你卡掉,这道题目并没有考虑到。

3
1 -1000000000 -1000000000
1 2
1 3
-2000000000
const int N = 2e5 + 7;
const ll INF = 1e18;
ll n, m;
vector<int> edge[N];
ll a[N], ans = -INF;

int dfs(int u, int fa) {
    int max1 = 0, max2 = 0, x;
    for (auto& v : edge[u]) {  // 子树里面找最大的两个点
        if (v == fa)    continue;
        x = dfs(v, u);
        a[u] += a[v];   // 求解u以及它的子树和
        if (max1 == 0 or a[x] >= a[max1])    max2 = max1, max1 = x;
        else if (max2 == 0 or a[x] >= a[max2])    max2 = x;
    }
    if (max2 != 0)
        ans = max(ans, a[max1] + a[max2]);
    if (max1 == 0 or a[u] > a[max1])    max1 = u;
    return max1;
}

void solve() {
    n = read();
    rep(i, 1, n)    a[i] = read();
    int u, v;
    rep(i, 2, n) {
        u = read(), v = read();
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    dfs(1, -1);
    if (ans == -INF) { puts("Error"); }
    else {
        print(ans);
    }
}

D、CCA的小球

我们要求解任意小球颜色都不同的方案数,那么我们就反向思考。

对于给定的,很显然全部的方案数就是那么多种。

如果不存在任何一对相同的数,这就是最终答案。接下来如果存在一对相同的数的话。我们就要减掉这一对相同的数放在一起构造的答案数量。如果存在两对相同的数的话,我们就分别挑出一对相邻的减掉再加上两对同时相邻的答案,这样想先去就很容易想到容斥了。

对于每次我们让一对相邻方案数就是就是给出小球颜色中相同的颜色个数。

那么接下来就是捆绑之后顺序全排列。答案是,但是这里我们还要注意一个点,对于相同的数来说颠倒前后两个数的顺序并没有贡献,所以之后的贡献还不能简单计算还应该把相同数前后放在一起的情况除掉,那么答案就是,理解就是除掉选择出来的那一对,其余对之间全排列计算贡献时出现前后的顺序不计。

接下来只要去枚举每次我们让几队相邻即可,因为涉及取模除法操作,并且发现每次就比前一次少除一个2,所以我们就干脆不在循环内部调用逆元了,再最终求解一次逆元即可。

const int N = 1e6 + 7;
ll n, m, k;
ll jc[N], inv[N];

ll C(int n, int m) {
    return jc[n] * inv[n - m] % MOD * inv[m] % MOD;
}

void solve() {
    jc[0] = 1;
    rep(i, 1, N - 1)    jc[i] = jc[i - 1] * i % MOD;
    inv[N - 1] = qpow(jc[N - 1], MOD - 2, MOD);
    repp(i, N - 2, 0)    inv[i] = inv[i + 1] * (i + 1) % MOD;
    n = read();
    unordered_map<int, int> mp;
    int x;
    rep(i, 1, n) {
        x = read();
        if (mp.count(x))    ++k;
        else mp[x] = 1;
    }
    //print(k);
    ll ans = jc[n], tmp = 1;
    rep(i, 1, k) {
        tmp = tmp * 2;
        if (tmp >= MOD)    tmp -= MOD;
        if (i & 1)
            ans -= C(k, i) * jc[n - i] % MOD * tmp % MOD;
        else
            ans += C(k, i) * jc[n - i] % MOD * tmp % MOD;
        ans = (ans + MOD) % MOD;
    }
    ans = ans * qpow(tmp, MOD - 2, MOD) % MOD;
    print(ans);
}