AtCoder Beginner Contest 295

Three Days Ago

我们定义一个只由数字构成的字符串中的字符能够被重排成相同的两份,我们称这个字符串是个好字符串,比如12341234

现在给定一个字符串\(S\),找出所有的\([l,r]\),使得在这段区间中的子段是个好字符串

题解:思维 + 组合计数

首先我们根据题意得到:一个好字符串中所有相同数字出现的次数一定是偶数

我们考虑维护一个前缀\(0-9\)每个数字出现次数的奇偶性的状态

  20230322
0:00000000
1:00100000
2:10100000
3:10000000
4:10010000
5:00010000
6:00000000
7:00100000
8:00000000

我们发现如果前缀\(i\)时的状态等于前缀\(j\)时的状态,说明在\([i+1,j]\)这段区间中每个数字出现的次数都为偶数次,因为如果前缀\(j\)的状态中数字\(x\)出现的次数为奇数次,前缀\(i\)的状态中数字\(x\)出现的次数也是奇数次,说明\([i+1,j]\)之间\(x\)出现的次数一定为偶数;如果\(x\)出现的次数为偶数次不再赘述

所以我们不妨使用哈希表记录每个状态出现的次数,然后如果某个状态出现超过两次,说明一定有合法的区间,我们利用组合计数求出贡献即可

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 5e5 + 10, M = 4e5 + 10;

map<string, int> mp;
int cnt[10];

void solve()
{
    string s;
    cin >> s;
    int n = s.length();
    string t = "0000000000";
    mp[t]++;
    s = " " + s;
    for (int i = 1; i <= n; ++i)
    {
        cnt[s[i] - '0']++;
        if (cnt[s[i] - '0'] % 2 == 0)
            t[s[i] - '0'] = '0';
        else
            t[s[i] - '0'] = '1';
        mp[t]++;
    }
    int ans = 0;
    for (auto [x, y] : mp)
    {
        ans += y * (y - 1) / 2;
    }
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Minimum Reachable City

给定一颗点数为 \(N\) 的树,初始 \(p_i(1\leq p_i \leq i,1 \leq i < N)\) 连向 \(i+1\)

\(Q\) 次操作,有两种:

  • 1 u v\(u\)\(v\) 连一条有向边,保证最开始时 \(v\) 能到达 \(u\)\(u \ne v\)
  • 2 x:询问 \(x\) 能到达的点中编号最小的点。

题解:并查集 \(O(n\alpha(n))\)

我们发现在这颗树上,祖先节点的编号一定小于当前节点的编号;

因为我们又知道操作\(1\)的时候保证\(v\)能到达\(u\),也就是说\(v\)的编号一定比\(u\)小,也就是说\(u\)\(v\)连的一条有向边一定是返祖边,所以如果\(u\)\(v\)连的一条有向边,那么\(v\)\(u\)这条链之间的所有点都能够到达\(v\),所以对于操作\(2\)来说我们不妨利用并查集维护根节点,在合并节点时我们考虑暴力往上合并,我们发现这颗树上的点最多只会被遍历一次,加上合并时的复杂度,最终复杂度为\(O(n\alpha(n))\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, q;
int fa[N];
int par[N];

int find(int x)
{
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void merge(int u, int v)
{
    u = find(u);
    v = find(v);
    if (u != v)
        fa[u] = v;
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        fa[i] = i;
    for (int i = 1; i < n; ++i)
    {
        int x;
        cin >> x;
        par[i + 1] = x;
    }
    cin >> q;
    while (q--)
    {
        int op, u, v;
        cin >> op;
        if (op == 1)
        {
            cin >> u >> v;
            for (int p = u; p > v; p = par[p])
            {
                p = find(p);
                merge(p, v);
            }
        }
        else
        {
            cin >> u;
            cout << find(u) << endl;
        }
    }
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-04-16 19:51  Zeoy_kkk  阅读(15)  评论(0编辑  收藏  举报