2018-常州集训提高组-测试一


感谢一堆大佬把题带给我做。。。。。这题还是有点质量的,wzc大佬风采不减当年啊。

总结

  1. 我不带文件过了,结果带了文件,没有编译过,失去了100分。
  2. 我以为DisjointSet的getRoot操作第二次和直接引用的耗时一样,事实证明我zz了
  3. 第二题注意数组开两倍,因为末端点和首端点混在一起离散化的,我调试数据的时候崩溃了一次,查了半天:(
  4. 第三题有点难搞,但是因为这个题给的Hint比较的明显,我好边界调了半天。

动物园

题意

定义高兴值为一条路径上所经过节点的最小值,求整个图所有点的高兴值之和。

题目分析

这道题是一道很经典的最大瓶颈生成树的问题,数据范围你是无法直接枚举两个端点的,于是考虑统计每条边使用的次数,然后进行对答案的更新。
一开始写的是Prim,但是发现后面没有用Kruskal方便,就换成了Kruskal。
用了一个(r[i])表示序号,防止顺序被直接改变。

#include <bits/stdc++.h>

using namespace std;

#define forn(i, n) for(int i = 1; i <= (int)(n); i++)
#define ford(i, n) for(int i = (int)(n) - 1; i >= 0; i--)

typedef long long qword;

const int inf = 0x7fffffff;

const int maxn = 1e5 + 10;
const int maxm = maxn * 10;

int a[maxn], fa[maxn];
int val[maxm], r[maxm], v[maxm], t[maxm];
int cnt[maxm];

bool cmp(int x, int y) {return val[x] > val[y];}
int getRoot(int x) {return x == fa[x] ? x : fa[x] = getRoot(fa[x]);}
#define OK
int main() {
#ifdef OK
    freopen("zoo.in","r",stdin);
    freopen("zoo.out","w",stdout);
#endif // OK
    int n, m;
    qword ans = 0;
    cin >> n >> m;
    forn(i, n) cin >> a[i], fa[i] = i;
    int x, y;
    forn(i, m) {
        cin >> x >> y;
        val[i] = min(a[x], a[y]);
        v[i] = x; t[i] = y;
        r[i] = i; cnt[i] = 1;
    }
    sort(r + 1, r + m + 1, cmp);
    forn(i, m) {
        int k = r[i];
        int vv = getRoot(v[k]), tt = getRoot(t[k]);
        if (getRoot(v[k]) != getRoot(t[k])) {
            ans += 1ll * val[k] * cnt[vv] * cnt[tt];
            cnt[tt] += cnt[vv];
            fa[vv] = fa[tt];
        }
    }
    cout << ans * 2 << endl;
    return 0;
}

线段计数

题目分析

这道题相对于经典的线段计数问题,只是多了一个实时的添加和删除,直接套一个树状数组或者线段树就可以解决。
然后因为这个数据有点点大,于是你再套一个离散化的板子,就很简单了。

然而我因为没有去重,wa了一发(实测不去重可以A3个点)

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 500010*2;

int C[2][maxn];
struct Opt {
	int opt, x, y;
    void Clear() {
        opt = x = y = 0;
    }
}a[maxn];
int b[maxn], cnt;
int c[maxn], t;

void Add(int opt, int x, int v, int n) {for(;x<=n*2;x+=x&-x) C[opt][x]+=v;}
int Query(int opt, int x) {int res=0;for(;x;x-=x&-x) res+=C[opt][x];return res;}
int query(int x, int y) {return Query(1,y) - Query(0,x-1);}
void add(int x, int y, int n) { Add(0, x, 1, n); Add(1, y, 1, n);}
void del(int x, int y, int n) { Add(0, x, -1, n);Add(1, y, -1, n);}

#define rep(i,s,t) for(int i=(int)(s);i<=(int)(t);++i)
#define forn(i) rep(i,1,n)

inline void work() {
    int cnt = 0;
    int t = 0;
    int n;
    cin >> n;
    forn(i) {
        cin >> a[i].opt >> a[i].x;
        if(a[i].opt == 0) {
            c[++t] = i;
            b[++cnt] = a[i].x; b[++cnt] = a[i].x + t;
            a[i].y = a[i].x + t;
        }
    }
    sort(b + 1, b + 1 + cnt);
    cnt = unique(b + 1, b + 1 + cnt) - b - 1;
    forn(i) {
        if(!a[i].opt) {
            int x = lower_bound(b + 1, b + 1 + cnt, a[i].x) - b;
            int y = lower_bound(b + 1, b + 1 + cnt, a[i].y) - b;
            cout << query(x, y) << endl;
            add(x,y,n);
        } else {
            int x = lower_bound(b + 1, b + 1 + cnt, a[c[a[i].x]].x) - b;
            int y = lower_bound(b + 1, b + 1 + cnt, a[c[a[i].x]].y) - b;
            del(x,y,n);
        }
    }
}

inline void Init() {
    memset(C[1], 0, sizeof(C[1]));
    memset(C[0], 0, sizeof(C[0]));
}
#define OK
int main() {
#ifdef OK
    freopen("segment.in","r",stdin);
    freopen("segment.out","w",stdout);
#endif // OK
    int T;
    ios::sync_with_stdio(0);
    cin >> T;
    rep(i,1,T) {
        cout << "Case #" << i << ":" << endl;
        Init();
        work();
    }
	return 0;
}

填数

题目分析

易知, 至少会有一行/列是空的。

我们不妨假设完全空的是一个行。我们把这行以外的元素随意填,只需满足每行的所有元素的积为−1.然后用这行来“收尾”。
显然,这一行有且恰有一种方案来填充,以满足每列的所有元素积为(−1)的条件。于是,我们只需计算除这行外,每行有多少方案,使得这行的元素积为(−1),然后把这些结果乘起来即可。于是问题被转化为了1维上的问题。
显然,答案只与『还需要填的格子数目』和『已经填上的格子里的数的积』有关。不妨设需要填(s)个格子,我们暴力枚举(k),表示其中(k)个格子填了(−1)(当然必须满足所有数的乘积为(−1)),则填法数目显然是(inom{s}{k}),其和即为答案。时间复杂度(O(nm))

代码太多特判,就不写上来了。

原文地址:https://www.cnblogs.com/Alessandro/p/9587779.html