AtCoder Regular Contest 101

Posted heyuhhh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtCoder Regular Contest 101相关的知识,希望对你有一定的参考价值。

传送门

C - Candles

双指针维护一个区间搞一下就行。


Code

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n, k;
int a[N];
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> k;
    for(int i = 1; i <= n; i++) cin >> a[i];
    int p = lower_bound(a + 1, a + n + 1, 0) - a - 1;
    int left = max(1, p - k + 1);
    int ans = INF;
    while(left <= p + 1) {
        int right = left + k - 1;
        if(right > n) break;
        if(a[right] < 0) {
            ans = min(ans, -a[left]);
        } else if(a[left] > 0) {
            ans = min(ans, a[right]);
        } else {
            int res = -a[left];
            ans = min(ans, min(2 * res + a[right], 2 * a[right] + res));
        }
        ++left;
    }
    cout << ans;
    return 0;
}

D - Median of Medians

题意:
给出(n)个数,求所有区间中位数的中位数。

思路:
设最终答案为(ans)
可以发现(ans)具有单调性,什么样的单调性呢?
假设中位数大于(ans)的区间有(y)个,发现随着(ans)的增大,(y)是单调不增的,所以我们就可以这样来搞。

具体做法:

  • 二分答案(ans),将小于它的置为(-1),其余置为(1),易知当区间和为非正数时,该区间的中位数小于(ans),个数为(y)
  • 根据(y)的大小来调整(ans)(y)的大小的标准参照区间个数。
  • 实现可用树状数组。

细节见代码:(真是巧妙啊QAQ)


Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;

int a[N], b[N];
int n;

int c[N];

int lowbit(int x) {return x & (-x);}

void add(int x, int v) {
    for(; x < N; x += lowbit(x)) c[x] += v;
}

int query(int x) {
    int ans = 0;
    for(; x; x -= lowbit(x)) ans += c[x];
    return ans;
}

bool check(int x) {
    b[0] = 0;
    for(int i = 1; i <= n; i++) {
        if(a[i] < x) b[i] = -1;
        else b[i] = 1;
        b[i] += b[i - 1];
    }
    int Min = *min_element(b, b + n + 1);
    if(Min <= 0) for(int i = 0; i <= n; i++) b[i] -= Min - 1;
    ll tot = 0;
    for(int i = 0; i <= n; i++) {
        tot += query(b[i]);
        add(b[i], 1);
    }
    for(int i = 0; i <= n; i++) add(b[i], -1);
    return tot >= (1ll * (n + 1) * n / 2 + 1) / 2;
}

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 = 1e9 + 1, mid;
    while(l < r) {
        mid = (l + r) >> 1;
        if(check(mid)) l = mid + 1;
        else r = mid;
    }
    cout << l - 1;
    return 0;
}

E - Ribbons on Tree

题意:
给出一颗有偶数个点的树,现在问有多少种方案,能够将这些点两两配对并且每一条边都能被至少一对点对的路径经过。

思路:

  • 直接思考不好做,考虑容斥;
  • 容斥的话就枚举边集(E)(E)中的边不会被覆盖,那么答案就为(sum_E(-1)^{|E|}f(E))
  • 现在考虑如何求(f),因为如果确定了边的个数,树就被划分成了几个连通块,假设(g(n))表示(n)个点两两配对的所有方案数。
  • 那么(g(n)=frac{1}{2^frac{n}{2}}C_{n}^{frac{n}{2}}*frac{n}{2}!=1cdot 3cdot...cdot (n-1))
  • 具体含义就是先从(n)个点中选出(frac{n}{2})个点,然后每个点依次选择其配对点,但有些可能会重复选择,除掉就行。至于后面两个式子为什么相等,这涉及到阶乘中(2)的个数,稍微推一推就行。
  • 然后直接(dp)就是了,(dp[u][x])表示以(u)为根的子树中,有(x)个点和(u)的父亲在同一个连通块的方案数。之后类似于树背包那样转移就行。
  • 特别地:当(x=0)时,就相当于此时划分出了一个连通块,就需要统计块内的方案数,同时乘以一个容斥系数(-1)(相当于多出一条边,改变了奇偶性)。
  • 最后多乘了一个(-1),所以答案为(-dp[1][0])

感觉很神奇吧,独立做感觉很难做出来= =


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
//#define Local
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 5005, MOD = 1e9 + 7;
 
int n;
struct Edge{
    int v, next;
}e[N << 1];
int head[N], tot;
void adde(int u, int v) {
    e[tot].v = v; e[tot].next = head[u]; head[u] = tot++;
}
 
ll g[N];
void init() {
    g[0] = 1;
    for(int i = 2; i < N; i += 2) g[i] = g[i - 2] * (i - 1) % MOD;
}
 
ll f[N][N], sz[N], tmp[N];
void dfs(int u, int fa) {
    f[u][sz[u] = 1] = 1;
    for(int i = head[u]; ~i; i = e[i].next) {
        int v = e[i].v;
        if(v == fa) continue;
        dfs(v, u);
        for(int j = 0; j <= sz[u] + sz[v]; j++) tmp[j] = f[u][j], f[u][j] = 0;
        for(int j = 1; j <= sz[u]; j++) {
            for(int k = 0; k <= sz[v]; k++) {
                (f[u][j + k] += (tmp[j] * f[v][k]) % MOD) %= MOD;
            }
        }
        sz[u] += sz[v];
    }
    for(int i = 2; i <= sz[u]; i += 2) {
        f[u][0] -= (f[u][i] * g[i] % MOD);
        f[u][0] %= MOD;
    }
    if(f[u][0] < 0) f[u][0] += MOD;
}
 
void run() {
    for(int i = 1; i <= n; i++) head[i] = -1; tot = 0;
    for(int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        adde(u, v); adde(v, u);
    }
    dfs(1, 0);
    cout << (MOD - f[1][0]) % MOD << '
';
}
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
#ifdef Local
    freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
    init();
    while(cin >> n) run();
    return 0;
}

F - Robots and Exits

题意:
数轴上有(n)个点,(m)个出口,之后可以让所有点左移或者右移一格,若遇到出口点就消失。
最后问当所有点消失时,能够用到的出口总数。

思路:

  • 每个点用一个点对((x,y))表示,(x,y)分别表示当前点与左边最近的出口和右边最近的出口的距离。
  • 发现左移一格可以表示为((x-1,y)),右移一格可以表示为((x,y-1))
  • 为什么是这样?
  • 我们更在意的是他们的相对位置,所以另一数值不变不影响,就算变了,最后还是要回去对吧。
  • 那么就可以看作每个点都可以沿着一条折现触碰坐标轴。
  • 转换一下思路,我们从原点出发画一条单调不降的折线,并且我们可以将折现平移,总会有一个点最先触碰到这条折线,那么我们直接(dp)枚举这个点进行转移即可。

感觉在折线上(dp)这种题挺有意思的,细节也比较多。还需要仔细品味一下。
详见代码:


Code

#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define INF 0x3f3f3f3f
//#define Local
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 5, MOD = 1e9 + 7;

int n, m;
int x[N], y[N], d[N];
struct Point{
    int a, b;
    bool operator < (const Point& A) const {
        if(a == A.a) return b > A.b;
        return a < A.a;
    }
}p[N];

int c[N];
int lowbit(int x) {return x & (-x);}

void add(int x, int v) {
    for(; x < N; x += lowbit(x)) c[x] = (c[x] + v) % MOD;
}

int query(int x) {
    int ans = 0;
    for(; x > 0; x -= lowbit(x)) ans = (ans + c[x]) % MOD;
    return ans;
}

void run() {
    d[0] = 0;
    memset(c, 0, sizeof(c));
    for(int i = 1; i <= n; i++) cin >> x[i];
    for(int i = 1; i <= m; i++) cin >> y[i];
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        if(x[i] > y[1] && x[i] < y[m]) {
            int left = lower_bound(y + 1, y + m + 1, x[i]) - y - 1;
            int right = left + 1;
            int l = x[i] - y[left], r = y[right] - x[i];
            p[++cnt] = {l, r};
            d[++d[0]] = r;
        }
    }
    sort(d + 1, d + d[0] + 1);
    d[0] = unique(d + 1, d + d[0] + 1) - d - 1;
    for(int i = 1; i <= cnt; i++) {
        p[i].b = lower_bound(d + 1, d + d[0] + 1, p[i].b) - d;
    }
    sort(p + 1, p + cnt + 1);
    int ans = 1;
    for(int i = 1; i <= cnt; i++) {
        if(p[i].a == p[i - 1].a && p[i].b == p[i - 1].b) continue;
        int res = query(p[i].b - 1) + 1;
        if(res >= MOD) res -= MOD;
        add(p[i].b, res);
        ans = (ans + res) % MOD;
    }
    cout << ans << '
';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
#ifdef Local
    freopen("../input.in", "r", stdin);
    freopen("../output.out", "w", stdout);
#endif
    while(cin >> n >> m) run();
    return 0;
}

以上是关于AtCoder Regular Contest 101的主要内容,如果未能解决你的问题,请参考以下文章

刷题AtCoder Regular Contest 001

AtCoder Regular Contest 094

[Atcoder Regular Contest 060] Tutorial

AtCoder Regular Contest 103

AtCoder Regular Contest 128

AtCoder Regular Contest 119 C