2022“杭电杯”中国大学生算法设计超级联赛(6)

更好的阅读体验

Maex

题意

给定一棵树,你需要给树上的每个点赋一个权值 a i a_i ai,要求不存在两个点 i , j i,j i,j存在 a i = a j a_i=a_j ai=aj,每个点的 b i = M E X ( s u n t r e e ( a i ) ) b_i=MEX(suntree(a_i)) bi=MEX(suntree(ai)),求 ∑ i = 1 n b i \sum_{i=1}^{n}b_i i=1nbi

思路

通过观察发现 答案为每点的子树大小加上子节点中能凑出的最大答案。

int n, son[N];
vector<int> G[N];
ll sz[N], mx[N];
void dfs(int x, int fa) {
    sz[x] = 1;
    ll res = 0;
    for (int it : G[x]) {
        if (it == fa)continue;
        dfs(it, x);
        res = max(res, mx[it]);
        sz[x] += sz[it];
    }
    mx[x] = res + sz[x];
}
int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i < n; i++) {
            int x, y;
            cin >> x >> y;
            G[x].push_back(y);
            G[y].push_back(x);
        }
        dfs(1, 0);
        cout << mx[1] << endl;
        for (int i = 1; i <= n; i++) G[i].clear(), sz[i] = mx[i] = 0;
    }
    return 0;
}

Shinobu loves trip

题意

给定 p , a , n , q p,a,n,q p,a,n,q,接下来 n n n次操作,每次给定 s i , d i s_i,d_i si,di表示从 s i s_i si开始,每次走到 s i ∗ a k % P    0 ≤ k ≤ d i s_i*a^k \%P \ \ 0\le k\le d_i siak%P  0kdi,的位置,之后 q q q次询问,每次询问给出一个 x x x,输出 x x x这个位置被多少对 s i , d i s_i,d_i si,di走过。

思路

对于每个 s i , d i s_i,d_i si,di,我们都能判断是否存在一个 k k k使得$s_i*a^k =x\ \ k \le d_i $即可。

ll s[N], d[N], ans[N], a[N], invs[N];
int main() {
    int T;
    cin >> T;
    while (T--) {
        ll p, A, n, q;
        cin >> p >> A >> n >> q;
        gp_hash_table<int, int> mp;
        for (int i = 1; i <= n; i++) cin >> s[i] >> d[i], invs[i] = qpow(s[i], p - 2, p);
        a[0] = 1;
        for (int i = 1; i <= 200000; i++) a[i] = a[i - 1] * A % p;
        for (int i = 200000; i >= 0; i--) mp[a[i]] = i;
        for (int i = 1; i <= q; i++) {
            ll x;
            cin >> x;
            int ans = 0;
            for (int j = 1; j <= n; j++) {
                if (s[j] == 0) {
                    ans += (x == 0);
                    continue;
                }
                ll tmp = x * invs[j] % p;
                auto it = mp.find(tmp);
                if (mp.find(tmp) != mp.end() && it->second <= d[j]) ans++;
            }
            cout << ans << endl;
        }
    }
    return 0;
}

Shinobu Loves Segment Tree

题意

给定一个 n n n x x x,假设 d e g t r e e i ( t r [ x ] ) degtree_i(tr[x]) degtreei(tr[x])表示区间长度为 i i i建立的线段树中第 x x x号节点的区间长度,求 ∑ i = 1 n d e g t r e e i ( t r [ x ] ) \sum_{i=1}^{n} degtree_i(tr[x]) i=1ndegtreei(tr[x])

思路

通过打表观察我们发现,对于一个 x x x,当区间长度小于一定值的时候答案为 0 0 0,我们可以以 l o g log log的复杂度算出这个长度,然后后面的数字便有以下规律:

  • x x x为奇数,则对于所有大于 0 0 0的长度答案分别为 2 k 2^k 2k 1 1 1 2 k 2^k 2k 2 2 2 2 k 2^k 2k 3 3 3 2 k 2^k 2k 3 3 3
  • x x x为偶数,则对于所有大于 0 0 0的长度答案分别为 2 k − 2^{k-} 2k 1 1 1 2 k 2^k 2k 2 2 2 2 k 2^k 2k 3 3 3 2 k 2^k 2k 3 3 3

以上 k = l o g ( x ) − 1 k=log(x)-1 k=log(x)1

int n;
ll x;
ll get(vector<int> now) {
    ll res = 1;
    for (int it : now) {
        if (it == 1 || res == 1) res += res;
        else res += res - 1;
    }
    return res;
}
int main() {
    int T;
    cin >> T;
    while (T--) {
        ll n, x;
        cin >> n >> x;
        if (x == 1) {
            ll res = (n + 1) * n / 2;
            cout << res << endl;
            continue;
        }
        vector<int> now;
        ll tmp = x;
        while (tmp) {
            now.push_back(tmp % 2);
            tmp /= 2;
        }
        now.pop_back();
        ll tot = get(now);
        tmp = 1ll << now.size();
        // cerr<<tmp<<' '<<tot<<endl;
        ll ans = 0;
        ll p = 2;
        if (tot > n) {
            cout << 0 << endl;
            goto h;
        }
        n -= tot - 1;
        if (x % 2 == 0) {
            if (n < tmp / 2) { cout << n << endl; goto h; }
            else n -= tmp / 2, ans = tmp / 2;
        }
        else {
            if (n < tmp) { cout << n << endl; goto h; }
            else n -= tmp, ans = tmp;
        }
        while (n >= tmp) {
            ans += tmp * p;
            n -= tmp;
            p++;
        }
        ans += n * p;
        cout << ans << endl;
    h:;
    }
    return 0;
}

Planar Graph

题意

给定一个平面图,问最少需要在哪几条边上开口,使得所有的面都连通,输出字典序最小的方案

思路

将面看作点,问题便可以装化成生成树问题,跑一次最大生成树,不在树上的边都是要删掉的边。此处图可能不连通,所以应该算是最大生成森林。

struct node {
    int l, r, w;
}a[N];
int f[N], m, n;
int find(int x) {
    return f[x] == x ? x : f[x] = find(f[x]);
}
int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) f[i] = i;
        for (int i = 1; i <= m; i++) cin >> a[i].l >> a[i].r, a[i].w = i;
        sort(a + 1, a + 1 + m, [](node A, node  B) {
            return A.w > B.w;
            });
        vector<int> ans;
        for (int i = 1; i <= m; i++) {
            int f1 = find(a[i].l), f2 = find(a[i].r);
            if (f1 == f2) ans.push_back(a[i].w);
            else {
                f[f1] = f2;
            }
        }
        cout << ans.size() << endl;
        sort(ans.begin(), ans.end());
        for (int it : ans) cout << it << ' ';
        cout << endl;
    }
    return 0;
}

Loop

题意

给定一个序列,每次可以选择一个数字将其删除,并在之后的某个位置将其插进去,求 k k k次操作之后字典序最大的答案。

思路

从第一个位置开始之后的 k k k a i < a i + 1 a_i<a_{i+1} ai<ai+1的情况都是需要向后移动的,从前向后遍历的过程中,用一个优先队列存已经删掉的数字,再次遍历删除过这些数字后的序列, 若遇到某个位置 a i < q . t o p ( ) a_i<q.top() ai<q.top(),就将该数字插在这里。

int n, k, a[N];
int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> k;
        for (int i = 1; i <= n; i++) cin >> a[i];
        vector<int>ans;
        priority_queue<int> q;
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (!ans.size()) {
                ans.push_back(a[i]);
                continue;
            }
            while (ans.size() && a[i] > ans.back() && cnt < k) {
                cnt++;
                q.push(ans.back());
                ans.pop_back();
            }
            ans.push_back(a[i]);
        }
        vector<int> res;
        for (int it : ans) {
            while (q.size() && it < q.top()) res.push_back(q.top()), q.pop();
            res.push_back(it);
        }
        while (q.size()) res.push_back(q.top()), q.pop();
        cout << res[0];
        for (int i = 1; i < res.size(); i++) cout << ' ' << res[i];
        cout << endl;
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值