• AtCoder Regular Contest 102


    传送门

    C - Triangular Relationship

    题意:
    给出(n,k),现在要你求合法三元组的数量,合法是指对于一个三元组((a,b,c)),每个数都不超过(n),并且(a+b,b+c,a+c)(k)的倍数。

    思路:
    按照模(k)的余数来讨论即可。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n, k;
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n >> k;
        ll ans = n / k;
        ans = ans * ans * ans;
        ll tmp = n / k + (n % k >= k / 2);
        if(k % 2 == 0) ans += tmp * tmp * tmp;
        cout << ans;
        return 0;
    }
    
    

    D - All Your Paths are Different Lengths

    题意:
    给出(L),先要求构造一个点数不超过(20),边数不超过(60)的有向图,并且满足从起点(1)到终点(n)共有(L)条路径,并且路径长度为(0,1,cdots,L-1)(Lleq 10^6)

    思路:

    • 考虑二进制拆分。
    • 假设(L)的二进制的最高位为(r),那么我们首先构造出一条链的形式,有(r)个结点,并且前一个点向后一个点连边(0)(2^i(i<r))
    • 这条链即表示最高位为(0)时的所有情况。
    • 但最高位为(1)时怎么办?
    • 假设这条链是从(1)号点开始,到(n)号点结尾,并且边((i,i+1))的边权为(2^{i-1})。那么从点(i)连接一条边到(n)号结点,边权为(X),可以发现会多出一些长度为(X+0,X+1,cdots,X+2^{i-1}-1)的路径。
    • 根据这一点选择合适的(X)以及结点(i)处理即可。

    代码如下:

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 66;
    int L;
    int sum[N];
    int n, m;
    vector <pair<int, int> > g[N];
    void add(int u, int v, int w, int op) {
        ++m;
        g[u].push_back(make_pair(v, w));
        if(op) sum[v] = sum[u] + w;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> L;
        int r;
        for(int i = 30; i >= 0; i--) if(L >> i & 1) {
            r = i; break;
        }
        for(int i = 2; i <= r + 1; i++) {
            add(i - 1, i, 1 << (i - 2), 1);
            add(i - 1, i, 0, 0);
        }
        int X = (1 << r); --L;
        while(X <= L) {
            int p = upper_bound(sum + 1, sum + r + 1, L - X) - sum - 1;
            add(p, r + 1, X, 0);
            X += sum[p] + 1;
        }
        n = r + 1;
        cout << n << ' ' << m << '
    ';
        for(int u = 1; u <= n; u++) {
            for(auto it : g[u]) {
                int v = it.first, w = it.second;
                cout << u << ' ' << v << ' ' << w << '
    ';
            }
        }
        return 0;
    }
    
    

    E - Stop. Otherwise...

    题意:
    (n)个骰子,每个骰子上面有点数(1,2,cdots,k)
    先求对于所有(x=2,3,cdots,2k),任意两个骰子的点数之和都不为(x)的状态数为多少。
    两个状态为不同的方案,当且仅当至少存在一个点数(i),在两种方案中的出现次数不同。

    思路:
    显然可以考虑容斥来搞。

    • 对于一个点(i),如果出现了,那么说明就不能出现(x-i),在每种情况中,都有若干这种pair关系。

    • 那么在一种情况中,我们容斥掉所有不合法情况即可。

    • 怎么容斥?

    • 假设当前有(cnt)(pair)为非法的,我们直接枚举选了多少对,那么答案就是(sum(-1)^j {cntchoose j}{n-2j+k-1choose k-1})

    • 后面的组合数就是隔板法的运用,我们知道所有点数出现次数之和为(n),那么就相当于求(x_1+x_2+cdots +x_k=n,x_igeq 0)的解的个数。

    这里的隔板法和平时还是有点区别,还是要看具体题目吧,怎样才算不同的情况,以及等式的存在性。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 4005, MOD = 998244353;
    
    int fac[N], inv[N];
    
    int 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] = 1ll * fac[i - 1] * i % MOD;
        inv[N - 1] = qp(fac[N - 1], MOD - 2);
        for(int i = N - 2; ~i; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
    }
    
    ll C(int n, int m) {
        if(n < m) return 0;
        return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
    }
    
    int n, k;
    int tot[N];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        pre();
        cin >> k >> n;
        for(int i = 1; i <= k; i++) {
            ++tot[i + 1]; --tot[i + k + 1];
        }
        for(int i = 1; i <= 2 * k; i++) tot[i] += tot[i - 1];
        for(int i = 2; i <= 2 * k; i++) {
            ll ans = 0;
            int cnt = (tot[i] + 1) / 2;
            for(int j = 0, d = 1; j <= cnt; j++, d = MOD - d) {
                ans += 1ll * d * C(cnt, j) % MOD * C(n - 2 * j + k - 1, k - 1) % MOD;
                ans %= MOD;
            }
            if(ans < 0) ans += MOD;
            cout << ans << '
    ';
        }
        return 0;
    }
    
    

    F - Revenge of BBuBBBlesort!

    题意:
    给出一个(1)(n)的排列,现在你可以执行操作任意多次,该操作为:

    • 选择(3)个元素,满足(a_{i-1}>a_{i}>a{i+1}),那么可以将这三个数翻转。

    问最后是否能将该排列变为(1,2,cdots,n)

    思路:
    很考察分析和观察能力的一个题。
    我们可以考虑一个逆过程,就是对于(1,2,cdots,n),我们可以选择递增的三个数,将其翻转。
    那么就有如下几个观察:

    • 观察1:如果以(i)为中心进行了一次翻转,那么(a_i)则不会发生改变。
    • 观察2:原问题可以划分为若干个独立的区间,每个区间我们可以单独处理。假设这个区间为([l,r]),那么(l+1,l+3,cdots,r-1)这些位置的数不会发生改变。
    • 观察3:对于(a_i),若其先向左移动,则不能再向右移动,并且可以引申:向左移动的数的相对位置不会改变。向右移动同理。

    这些观察的具体证明...第一个和第三个很好证,第二个感性理解一下?
    然后这个题就可以这样直接搞就行了。细节见代码吧。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 3e5 + 5;
    
    int n;
    int a[N];
    
    void YES() {
        cout << "Yes"; exit(0);
    }
    
    void NO() {
        cout << "No"; exit(0);
    }
    
    int tmp[N], cnt;
    int pos[N], dir[N];
    
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> a[i];
        int l = 1, r;
        while(1) {
            while(l <= n && a[l] == l) ++l;
            if(l > n) YES();
            r = l;
            while(1) {
                if((r - l + 1) & 1) {
                    if(a[r] != r) ++r;
                    else break;
                } else {
                    if(a[r] == r) ++r;
                    else break;
                }
                if(r > n) break;
            }
            --r; if((r - l) & 1) --r;
            if(l == r) NO();
            cnt = 0;
            for(int i = l; i <= r; i++) tmp[++cnt] = a[i];
            sort(tmp + 1, tmp + cnt + 1);
            for(int i = 1; i <= cnt; i++) if(tmp[i] != i + l - 1) NO();
            cnt = 0;
            for(int i = l; i <= r; i += 2) {
                tmp[++cnt] = a[i]; pos[a[i]] = i;
            }
            sort(tmp + 1, tmp + cnt + 1);
            for(int i = 1; i <= cnt; i++) {
                dir[pos[tmp[i]]] = 2 * i + l - 2;
            }
            int lastl = 0, lastr = n + 1;
            for(int i = l; i <= r; i += 2) {
                if(dir[i] > i) continue;
                if(dir[i] > lastl) lastl = dir[i];
                else NO();
            }
            for(int i = r; i >= l; i -= 2) {
                if(dir[i] < i) continue;
                if(dir[i] < lastr) lastr = dir[i];
                else NO();
            }
            l = r + 1;
        }
        return 0;
    }
    
    
  • 相关阅读:
    大数据的前景?
    PriorityBlockingQueue深度解析(好文)
    深入CAS原理
    common-lang3工具类-使用手册
    gitlab搭建
    RestTemplate转码bug
    论tigergraph边的方向性
    关于java的wait方法的深入分析
    openjdk的源码下载方式
    一个奇怪的urlencode转码问题
  • 原文地址:https://www.cnblogs.com/heyuhhh/p/11461825.html
Copyright © 2020-2023  润新知