2020暑假牛客多校训练营第二场

文章目录

  • F.Fake Maxpooling
  • C .Cover the Tree
  • B.Boundary
  • J.Just Shuffle
  • H.Happy Triangle

F.Fake Maxpooling

题目描述
给定一个 n ∗ m n*m nm的矩阵,矩阵的每个值为 a [ i ] [ j ] = l c m ( i , j ) a[i][j] = lcm(i,j) a[i][j]=lcm(i,j),求该矩阵中所有 k∗k 子矩阵的最大值之和。
思路
k为固定长度,求滑动区间的最大值或最小值就是滑动窗口,用单调队列维护就行。这题先维护一遍 n n n行的最值,把最值记录下来,再跑一遍 m m m列刚才所记录的最值,同时输出最大值。
比赛的时候发现单调队列的队列用deque时被卡常了,改成手写队列就可以过。刚才开了O2优化交了一发deque写的单调队列,发现可以过emmm
代码
这里的代码贴的单调队列是O2优化+deque的

#pragma GCC optimize(2)
#include 
using namespace std;

typedef long long LL;
const int N = 5010;
int a[N][N];
int b[N][N];
int n, m, k;
LL res;

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

int lcm(int a, int b) {
    return a / gcd(a, b) * b;
}

void get_max(int row) {
	deque<int> q;
	for(int i = 1; i <= m; i++) {
		while(!q.empty() && q.front() <= i - k) q.pop_front();
		while(!q.empty() && a[row][q.back()] <= a[row][i]) q.pop_back();
		q.push_back(i);
		if(i >= k) b[row][i] = a[row][q.front()];
	}
}

void get_col_max(int j) {
	deque<int> q;
	for(int i = 1; i <= n; i++) {
		while(!q.empty() && q.front() <= i - k) q.pop_front();
		while(!q.empty() && b[q.back()][j] <= b[i][j]) q.pop_back();
		q.push_back(i);
		if(i >= k) res += (LL)b[q.front()][j];
	}
}

void solve() {
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			a[i][j] = lcm(i, j);
		}
	}
	for(int i = 1; i <= n; i++) {
		get_max(i);
	}
	for(int i = k; i <= m; i++) {
		get_col_max(i);
	}
	printf("%lld\n", res);
}

int main() {
//	freopen("in.txt", "r", stdin);
	solve();
	return 0;
}

C .Cover the Tree

题目描述
给定一棵无根树,需要用最少的链覆盖整棵树,链可重叠,输出数量与方案。
思路
比赛的时候队友想到了dfs序的做法并ac了,但是无法给出严格证明。看了官方题解理解了证明。
整体想法就是需要连成覆盖尽可能多的链,这条链一定要穿过根节点。
做法:方案数很显然,设叶子数为s,答案就是 s + 1 2 \frac{s+1}{2} 2s+1 。方案就是找到一个非叶子结点当成根,然后跑一遍dfs对叶子结点建立dfs序,每次输出的点为 n o d e i node_i nodei n o d e i + s / 2 node_{i+s/2} nodei+s/2
证明(对官方题解的理解):
如果对于一个有叶子结点的结点point,其叶子结点的结点序为 ( L , R ) (L,R) (L,R)
1、若 R ≤ s 2 R\le\frac{s}{2} R2s, L r → L r + s 2 L_r\rightarrow L_{r+\frac{s}{2}} LrLr+2s覆盖,就表示从这个结点下面所有的叶子结点出发,这条链一定经过根节点
2、若 L > s 2 L>\frac{s}{2} L>2s,同理,这些叶子所出发的链一定经过根节点,所以一定覆盖。
3、如果 R ≤ s 2 R\leq\frac{s}{2} R2s L > s 2 L>\frac{s}{2} L>2s 的点都覆盖完了,中间还有一部分点分布在 s 2 \frac{s}{2} 2s左右两侧,这个时候一定就剩下中间的一部分区域了,就可以把这些叶子结点的父结点看成一个根节点来覆盖链。
如果s为奇数,那么多数来的一个点只要随便挑一个非自己的点就可以了。
代码

#include
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
vector<int> e[N];
int res[N], idx;

void dfs(int u, int fa) {
    if(e[u].size() == 1) res[++idx] = u;
    for(int i = 0; i < e[u].size(); i++) {
        int v = e[u][i];
        if(v == fa) continue;
        dfs(v, u);
    }
}

void solve() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for(int i = 1; i <= n; i++) {
        if(e[i].size() > 1) {
            dfs(i, 0);
            break;
        }
    }
    if(n == 2) {
        puts("1 2");
        return;
    }
    printf("%d\n", (idx + 1) / 2);
    for(int i = 1; i <= (idx + 1) / 2; i++) {
        printf("%d %d\n", res[i], res[i + idx / 2]);
    }
}

int main() {
    //freopen("in.txt", "r", stdin);
    solve();
    return 0;
}

B.Boundary

题目描述
给定n个点,问最多有几个点在同一个圆上并且 ( 0 , 0 ) (0, 0) (0,0)点一定在这个圆上。
思路
暴力。三个不共线的点可以确定一个圆,已知 ( 0 , 0 ) (0,0) (0,0)点,那就只要枚举另外两个点就行。先假设一个点一定在圆上,那么对于其它所有点,只要判断有多少个圆心重合即可,最后输出最大值。每次用map记录一下圆心点,然后统计一下最大值即可。防止卡精度可以开long double。另外需要特判1,2两个条件即可。
如果精度卡的实在太高那就把分子分母记录下来就行。
这题比较可惜,因为水平不足自己被卡死在J题,队友没有简短的板子被卡在B题,队友的思路什么都是对的了,最后还剩下十几分钟的时候再来看这个题已经没什么状态了。应当在卡J题半小时左右的时候放弃J题来想B题,这类似的题目以前碰到过,牛客小白月赛21的A题就用到了这个计算几何的板子。
代码

#include
using namespace std;
typedef long long LL;

typedef pair<long double, long double> PLDLD;
const int N = 2010;
struct point {
    double x, y;
}a[N];
map<PLDLD, int> cnt;
long double dx, dy, de;

bool cacl(int i, int j) {
    long double xa = 0, ya = 0, xb = a[i].x, yb = a[i].y, xc = a[j].x, yc = a[j].y;
	de = 2 * (xa - xb) * (yc - yb) - 2 * (ya - yb) * (xc - xb);
	if(de == 0) return false; // 三点共线
	dx = (yc - yb) * (xa * xa + ya * ya - xb * xb - yb * yb) - (ya - yb) * (xc * xc + yc * yc - xb * xb - yb * yb);
	dy = (xa - xb) * (xc * xc + yc * yc - xb * xb - yb * yb) - (xc - xb) * (xa * xa + ya * ya - xb * xb - yb * yb);
	dx /= de;
	dy /= de;
	return true;
}

void solve() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%lf%lf", &a[i].x, &a[i].y);
    }
    if(n <= 2) {
        printf("%d\n", n);
        return;
    }
    int res = 0;
    long double x, y;
    for(int i = 1; i <= n; i++) {
        cnt.clear();
        for(int j = i + 1; j <= n; j++) {
            if(!cacl(i, j)) continue;
            cnt[{dx, dy}]++;
            res = max(res, cnt[{dx, dy}]);
        }
    }
    printf("%d\n", res + 1);
}

int main() {
//    freopen("in.txt", "r", stdin);
    solve();
    return 0;
}

J.Just Shuffle

题目描述
给定一个长度为 n n n 的排列 A A A , 一个质数 k k k,求一个排列变换P,使得在 k k k 次对排列 { 1 , 2 , . . . , n } \{1,2,...,n\} {1,2,...,n} 变换以后形成 A A A
思路
学了hdu巨巨K0u1e的思路。
如果存在一个 P P P满足题意,那么这个变换 P P P一定存在一个或者多个环。
同时也可得排列 A A A一定也存在环。只考虑一个环,设环内有 m m m个数,那么对于这个环,就是求一个置换,使得在 k m o d    m k\mod m kmodm 次置换以后变成相对应的 A A A。(如果想要求出环,先要预存一下每个数字的最终的位置。)
对于每一个置换,会把当前位置上的数换到下一个数上去。设置换数组为 t t t, 一次置换就等价于将 t i t_i ti 位置的数挪到了 t i + 1 t_{i+1} ti+1 上。排列 A A A中存在一个数 A i = x A_i=x Ai=x,数字 x x x的初始位置一定是 t j = x t_j=x tj=x,那么经过 k k k次置换以后所表示的位置就到了 t ( j + k ) m o d    m = i t_{(j+k)\mod m}=i t(j+k)modm=i。找到这个相应的 i i i后以 i i i为起点把剩下的数一个个存入 t t t中。如果中间存在异常直接返回。
最后把 t t t 存入到结果当中去,其实就相当于一个从 t i t_i ti t i + 1 t_{i+1} ti+1的过程。
比赛中糊涂了,纠结于只寻找一个环,也没有和队友讨论自己的思路,在很晚的时候再和队友讨论被指出有多个环的时候差不多已经放弃了这题。还是水平太菜了。
代码

#include
using namespace std;

const int N = 1e5 + 10;
int n, k;
int a[N], b[N], t[N], res[N];
bool vis[N];

bool check(int x) {
    int m = 0;
    // 确定环的长度
    while(!vis[x]) {
        vis[x] = 1;
        x = b[x];
        m++;
    }
    for(int i = 0; i < m; i++) t[i] = 0;
    int j = 0;
    while(vis[x]) {
        if(t[j]) return false;
        t[j] = x;
        vis[x] = 0;
        x = b[x];
        j = (j + k) % m;
    }
    for(int i = 0; i < m; i++) {
        res[t[(i + 1) % m]] = t[i];
    }
    return true;
}

void solve() {
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    for(int i = 1; i <= n; i++) {
        b[a[i]] = i;
    }
    for(int i = 1; i <= n; i++) {
        if(!res[i] && !check(i)) {
            puts("-1");
            return;
        }
    }
    for(int i = 1; i <= n; i++) {
        printf("%d ", res[i]);
    }
}

int main() {
//    freopen("in.txt", "r", stdin);
    solve();
    return 0;
}

H.Happy Triangle

题目描述
给一个multiset,有以下三种操作
1、向multiset中插入一个x
2、在multiset中删除一个x
3、给定一个 x x x,在multiset中是否存在两个数 a a a, b b b 满足长度 a , b , x a,b,x a,b,x 的三边构成一个三角形
思路
对于 a , b , x a, b, x a,b,x有三种情况,下面来分别讨论:
1 、 a ≤ b ≤ x 1、a\leq b\leq x 1abx,只要找到最大的小于等于x的两个数即可
2 、 a < x < b 2、a2a<x<b,只要找到第一个大于x的数且第一个小于x的数即可
3 、 x ≤ a ≤ b 3、x\leq a\leq b 3xab,这种情况需要找到 m i n ( b − a ) min(b-a) min(ba),显然不能很轻松的找到,在一个区间中找到任意两个数的最小差值,可以用线段树进行维护查找。
先对每一个所加入或删除的数进行离散化,按顺序排列在一个区间中,对于每一个叶子,其实就是维护当前这个点与后继的差,所以维护更新的时候只要单点修改就行了。
插入一个数或者删除一个数,这个数只会影响到其旁边的数,因此我们只需要考虑这个数的前驱和后继,其实就是各种细节上的小处理,判断插入的数是否有前驱后继,删除这个数以后前驱后继的情况变化如何等等。就是注意细节,码就行了。
在写这题的时候为了防止自己写晕,中间也加了很多中文注释。
代码

#include
using namespace std;

typedef multiset<int>::iterator iter;
const int N = 2e5 + 10;
const int inf = 0x3f3f3f3f;
struct node {
    int l, r;
    int num;
}tr[N << 2];
int op[N], val[N], b[N];
int n, m;
multiset<int> s;

int find(int x) {
    return lower_bound(b + 1, b + 1 + m, x) - b;
}

void build(int u, int l, int r) {
    if(l == r) {
        tr[u] = {l, r, inf};
        return;
    }
    tr[u] = {l, r, inf};
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}

void push_up(int u) {
    tr[u].num = min(tr[u << 1].num, tr[u << 1 | 1].num);
}

void update(int u, int x, int val) {
    if(tr[u].l == tr[u].r) {
        tr[u].num = val;
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(x <= mid) update(u << 1, x, val);
    else update(u << 1 | 1, x, val);
    push_up(u);
}

bool check1(int i) {
    iter r = s.upper_bound(i);
    int x, y;
    if(r == s.begin()) return false;
    r--; x = *r;
    if(r == s.begin()) return false;
    r--; y = *r;
    return b[x] + b[y] > b[i];
}

bool check2(int i) {
    iter r = s.lower_bound(i);
    iter l = s.lower_bound(i);
    if(r == s.end()) return false;
    if(l == s.begin()) return false;
    int x, y;
    l--;
    y = *r;
    x = *l;
    return b[x] + b[i] > b[y];
}

int query(int u, int l, int r) {
    if(l <= tr[u].l && tr[u].r <= r) {
        return tr[u].num;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    int x = inf;
    if(l <= mid) x = min(x, query(u << 1, l, r));
    if(r > mid) x = min(x, query(u << 1 | 1, l, r));
    return x;
}

void solve() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d%d", &op[i], &val[i]);
        b[i] = val[i];
    }
    sort(b + 1, b + 1 + n);
    m = unique(b + 1, b + 1 + n) - b - 1;
    build(1, 1, m);
    for(int i = 1; i <= n; i++) {
        val[i] = find(val[i]);
    }
    for(int i = 1; i <= n; i++) {
        if(op[i] == 1) {
            if(s.count(val[i]) >= 1) update(1, val[i], 0);
            else {
                iter r = s.upper_bound(val[i]);
                iter l = r;
                // 更新当前位置
                if(r == s.end()) update(1, val[i], inf);    // 没有后继
                else update(1, val[i], b[*r] - b[val[i]]);  // 存在后继
                // 若存在前驱,更新前驱
                if(l != s.begin()) {
                    l--;
                    if(s.count(*l) <= 1) update(1, *l, b[val[i]] - b[*l]);
                }
            }
            s.insert(val[i]);
        }
        else if(op[i] == 2) {
            s.erase(s.find(val[i]));
            // 当前为位置这个数已经没有了,考虑更改前驱
            if(s.count(val[i]) == 0) {
                iter r = s.lower_bound(val[i]);
                iter l = r;
                update(1, val[i], inf);
                // 存在前驱
                if(l != s.begin()) {
                    l--;
                    // 前驱数大于1,那么最小值还是0
                    if(s.count(*l) > 1) continue;
                    if(r == s.end()) update(1, *l, inf);    // 前驱没有后继
                    else update(1, *l, b[*r] - b[*l]);      // 前驱有后继
                }
            }
            // 还有多余的数,考虑当前这个数的后继
            else {
                iter r = s.upper_bound(val[i]);
                // 存在多个必然是0,只考虑单个情况
                if(s.count(val[i]) == 1) {
                    if(r == s.end()) update(1, val[i], inf);    // 没有后继
                    else update(1, val[i], b[*r] - b[val[i]]);  // 存在后继
                }
            }
        }
        else {
            if(check1(val[i]) || check2(val[i]) || query(1, val[i], m) < b[val[i]]) puts("Yes");
            else puts("No");
        }
    }
}

int main() {
    //freopen("in.txt", "r", stdin);
    solve();
    return 0;
}

你可能感兴趣的:(牛客,训练日志,比赛)