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\)\(L\leq 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 << '\n';
    for(int u = 1; u <= n; u++) {
        for(auto it : g[u]) {
            int v = it.first, w = it.second;
            cout << u << ' ' << v << ' ' << w << '\n';
        }
    }
    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 {cnt\choose j}{n-2j+k-1\choose k-1}\)

  • 后面的组合数就是隔板法的运用,我们知道所有点数出现次数之和为\(n\),那么就相当于求\(x_1+x_2+\cdots +x_k=n,x_i\geq 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 << '\n';
    }
    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;
}

posted @ 2019-09-04 22:15  heyuhhh  阅读(239)  评论(0编辑  收藏  举报