title: AtCoder Beginner Contest 367
categories:

  • ICPC
    tags:
  • null
    abbrlink: 9364694a
    date: 2024-11-17 00:00:00

AtCoder Beginner Contest 367

待补:ABC 367 G 题解 - spdarkle - 博客园 (cnblogs.com)

D.一个湖周围有 NN 个休息区。
这些休息区按顺时针顺序编号为 1122 、…、 NN
从休息区 ii 顺时针走到休息区 i+1i+1 需要 AiA_i 步。(其中休息区 N+1N+1 指的是休息区 11 )。
从休息区 ss 顺时针走到休息区 ttsts \neq t )所需的最小步数是 MM 的倍数。
(s,t)(s,t) 的可能对数。

Sol:注意只能顺时针,所以固定起点的时候,到达任何一个点的路程都是确定的。考虑破环成链,维护%mod意义下的前缀和一个map桶,像滑动窗口维护这个map。但要注意边界,因aia_{i}表示的是一条边。

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = n + 1; i <= 2 * n; i++) a[i] = a[i - n];
    map<int, int> mp;
    for (int i = 1; i <= 2 * n; i++) a[i] += a[i - 1], a[i] %= m;
    int ans = 0;
    // int sum = 0;
    //for (int i = 0; i <= 2 * n; i++) cerr << a[i] << " ";
   // cerr << endl;
   // deb(0);
   // for (int i = 0; i <= n - 1; i++) cerr << a[i] << " ";
    //cerr << endl;

    for (int i = 0; i <= n - 1; i++) mp[a[i]]++;
   
    ans = mp[0]-1;
    //deb(ans,mp);
    for (int i = 1; i <= n - 1; i++) {
        //deb(i);
        //for (int j = i; j <= i + n - 1; j++) cerr << a[j] << " ";
        //cerr << endl;
        mp[a[i - 1]]--;
        if (mp[a[i - 1]] == 0)
            mp.erase(a[i - 1]);
        mp[a[i + n - 1]]++;
        ans += mp[a[i]]-1;
        //deb(ans,mp);
    }
    cout << ans << endl;
}

E:基环树倍增模板:

给你一个长度为 NN 的序列 XX ,其中每个元素都介于 11NN 之间(含),以及一个长度为 NN 的序列 AA
打印在 AA 上执行以下操作 KK 次的结果。

  • BB 替换 AA ,使得 Bi=AXiB_i = A_{X_i} .

Sol:发现可以建图i向xix_{i}连边。然后形成了内向基环树森林。线性dp做法的实现难度较大,倍增对于在环上走很多步非常有用。

int n, m;
int a[N];
int x[N];
int st[N][65];
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> x[i];
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) st[i][0] = x[i];

    for (int j = 1; j <= 60; j++) {
        for (int i = 1; i <= n; i++) {
            st[i][j] = st[st[i][j - 1]][j - 1];
        }
    }
    for (int i = 1; i <= n; i++) {
        int pos = i;
        for (int j = 60; j >= 0; j--) {
            if ((m >> j) & 1)
                pos = st[pos][j];
        }
        cout << a[pos] << " ";
    }
}

F.

  • 序列哈希板子:考虑对于一个序列,如果我们发现每次关心的都是其排序后结果,这意味着我们不在乎出现位置。我们考虑维护值域大小为M的桶(string代替),如果是集合那么其实是个01字符串。如果是可重集合,那么字符串第i个位置有cnt[i],表示这个可重集合中i这个值出现了cnt[i]次。选择一个质数base,记第i个位置的哈希值贡献为base cnt[i]base \ ^{cnt[i]}.

题意:给你长度为 NN 的正整数序列: A=(A1,A2,,AN)A=(A_1,A_2,\ldots,A_N)B=(B1,B2,,BN)B=(B_1,B_2,\ldots,B_N) .

您需要依次处理 QQ 个查询。 ii -th 查询的解释如下。

  • 给定正整数 li,ri,Li,Ril_i,r_i,L_i,R_i 。如果可以重新排列子序列 (Ali,Ali+1,,Ari)(A_{l_i},A_{l_i+1},\ldots,A_{r_i}) 以匹配子序列 (BLi,BLi+1,,BRi)(B_{L_i},B_{L_i+1},\ldots,B_{R_i}) ,则打印 “是”,否则打印 “否”。
constexpr u64 P = u64(1E18) + 9;
//constexpr u64 P = 998244353;
using Z = ModInt64<P>;
int n, m;
int a[N];
int b[N];
Z hsa[N], hsb[N];
random_device rd;
mt19937_64 gen(rd());
static int findprime() {
    int n = gen() % 1000000 + 1e6;
    if (n % 2 == 0)
        n++;
    while (true) {
        bool ok = 1;
        for (int i = 3; i * i <= n; i += 2) {
            if (n % i == 0) {
                ok = 0;
                n += 2;
                break;
            }
        }
        if (ok)
            return n;
    }
}
Z pwn[N];
const ull base = findprime();
void solve() {
    cin >> n >> m;
    pwn[0] = (u64)1;
    for (int i = 1; i <= N - 2; i++) pwn[i] = (pwn[i - 1] * base);
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= n; i++) {
        hsa[i] = hsa[i - 1] + pwn[a[i]];
        hsb[i] = hsb[i - 1] + pwn[b[i]];
    }
    for (int i = 1; i <= m; i++) {
        int l1, l2, r1, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        if (hsa[r1] - hsa[l1 - 1] == hsb[r2] - hsb[l2 - 1])
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
}