CF1417 题解

下午打 div3 成绩十分可怜,晚上打 div2 虚拟赛居然能 AK

CF1417A

Description

给定一个长为 $n$ 的数列 $a$,每次可以指定 $i,j$ ,令 $a_j=a_i+a_j$,求使得在所有数都不超过 $k$ 的情况下最多的操作次数。

$n\le 1000,k\le10^4$

Sol

贪心,最小的数不动,每次都给每个数加上最小的数,直到不能再加为止。

时间复杂度 $\mathcal{O}(n)$。

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int a[10005];
signed main() {
    int T = Read();
    while(T--) {
        int n = Read(), k = Read(), minn = 1E9, mpos;
        for(int i = 1; i <= n; i++) {
            a[i] = Read();
            if(a[i] < minn)  minn = a[i], mpos = i;
        }
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            if(i != mpos)  ans += (k - a[i]) / minn;
        }
        cout << ans << endl;
    }
    return 0;
}
View Code

CF1417B

Description

给定一个长为 $n$ 的数列 $a$ ,给定 $k$,定义一个数列的不完美度 $f(S)$ 为 $\sum_{i=1}^n\sum_{j=1}^n [S_i+S_j=k]$,将给定数列中的数划分为两个数列 $A,B$,求 $f(A)+f(B)$ 最小时的划分方案。

$n\le 10^5, k\le10^9$

Sol

当 $k$ 为奇数时,我们直接把 $\le \frac k2$ 的数划分为一堆,$\ge \frac k2$ 的数划分为一堆,这样第一堆任意两数的和必定 $\le k$,第二堆任意两数之和必定 $\ge k$。

当 $k$ 为偶数时,我们单独把 $=\frac k2$ 的数提出来,记录这些数的数目,其他数按照上述方式划分,最后将提出来的那些数平分给 $A,B$ 即可。

时间复杂度 $\mathcal{O} (n)$。

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int T, n, k, a[100005], b[100005];
signed main() {
    T = Read();
    while(T--) {
        memset(b, -1, sizeof(b));
        n = Read(), k = Read(); int sum = 0, flag = 0;
        for(int i = 1; i <= n; i++)  a[i] = Read();
        for(int i = 1; i <= n; i++) {
            if(a[i] < k / 2)  b[i] = 1;
            if(a[i] > k / 2)  b[i] = 0;
            if(a[i] == k / 2 && k % 2 == 0)  ++sum;
            if(a[i] == k / 2 && k % 2 == 1)  b[i] = 1;
        }
        if(k % 2 == 0) {
            for(int i = 1; i <= n; i++) {
                if(flag < sum / 2 && b[i] == -1)  b[i] = 0, ++flag;
                if(flag >= sum / 2 && b[i] == -1)  b[i] = 1;
            }
        }
        for(int i = 1; i <= n; i++)  cout << b[i] << " ";
        puts("");
    }
    return 0;
}
View Code

CF1417C

Description

给定一个长为 $n$ 的数列 $a$ ,请对于每个 $k\in[1,n]$ 求出在区间 $[1,k],[2,k+1],...[n-k+1,n]$ 中均出现的最小数。

$n\le 3\times 10^5$

Sol

考虑一个数在每个区间中均出现的情况,设其在数列中出现的位置为 $pos_1,pos_2,...,pos_m$,当且仅当 $\forall i>1,pos_i-pos_{i-1}\le k$,且$pos_1 \le k,n - pos_m\le k $。所以我们可以先对于每个数处理其最小能在哪个区间中出现。

然后用上述记录的东西来推出每个区间能够放置的最小的数,注意到当某个数满足一个 $k$ 时,那么所有大于 $k$ 的区间其均可以满足。

更新后输出即可。

时间复杂度 $\mathcal{O}(n)$。

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int T, n, a[300005], ldis[300005], mdis[300005], ans[300005];
signed main() {
    T = Read();
    while(T--) {
        memset(ldis, 0, sizeof(ldis));
        memset(mdis, 0, sizeof(mdis));
        memset(ans, 10, sizeof(ans));
        int n = Read();
        for(int i = 1; i <= n; i++)  a[i] = Read();
        for(int i = 1; i <= n; i++)
            mdis[a[i]] = max(mdis[a[i]], i - ldis[a[i]]), ldis[a[i]] = i;
        for(int i = 1; i <= n; i++)  mdis[a[i]] = max(mdis[a[i]], n + 1 - ldis[a[i]]);
        for(int i = 1; i <= n; i++)
            ans[mdis[a[i]]] = min(ans[mdis[a[i]]], a[i]);
        for(int i = 2; i <= n; i++)
            ans[i] = min(ans[i], ans[i - 1]);
        for(int i = 1; i <= n; i++) {
            if(ans[i] > n)  printf("%d ", -1);
            else  printf("%d ", ans[i]);
        }
        puts("");
    }
    return 0;
}
View Code

CF1417D

Description

给定一个长度为 $n$ 的数列 $a$,你可以进行下列操作:

  • 选择 $3$ 个数 $i,j,x$,使 $a_i=a_i-i\times x,a_j=a_j+i\times x$,$a_i,j$需要保持非负。

你需要在至多 $3n$ 次操作内将数列中所有数变为同一个数,问一个可行方案。

$n\le 10^4,1\le a_i\le 10^5$

Sol

首先当数列的和不为 $n$ 的倍数时,肯定是无解的。

我们考虑当数列的和为 $n$ 的倍数时,每个数需要变成 $\frac{sum}{n}$,那么我们考虑一种构造方式:

由于选 $i$ 为 $1$ 时可以方便的转移,我们考虑将每个位置上的权值都移到位置 $1$,那么我们可以先用 $1$ 里的数将该位置上的数补至 $i$ 的倍数,然后再转移至 $1$,这样需要至多 $2n$ 次操作,然后从 $1$ 移出去需要至多 $n$ 次操作。

时间复杂度 $\mathcal{O}(n)$。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int T, n, sum, a[100005], A[100005], B[100005], C[100005], cnt;
signed main() {
    T = Read();
    while(T--) {
        n = Read(); cnt = 0; sum = 0;
        for(int i = 1; i <= n; i++)  a[i] = Read(), sum += a[i];
        if(sum % n) {puts("-1"); continue;}
        for(int i = 2; i <= n; i++) {
            if(a[i] % i == 0)  A[++cnt] = i, B[cnt] = 1, C[cnt] = a[i] / i;
            else {
                A[++cnt] = 1, B[cnt] = i, C[cnt] = i - (a[i] % i); a[i] += i - (a[i] % i);
                A[++cnt] = i, B[cnt] = 1, C[cnt] = a[i] / i;
            }
        }
        int tmp = sum / n;
        for(int i = 2; i <= n; i++) {
            A[++cnt] = 1, B[cnt] = i, C[cnt] = tmp;
        }
        printf("%lld\n", cnt);
        for(int i = 1; i <= cnt; i++) {
            printf("%lld %lld %lld\n", A[i], B[i], C[i]);
        }
    }
    return 0;
}
View Code

CF1417E

Description

给定一个长度为 $n$ 的数列 $a$,要求让你选取一个 $x$,求出数列 $b$,其中 $b_i=a_i \bigoplus x$,$\bigoplus$ 即异或,使得在数列 $b$ 的逆序对数最小时 $x$ 的最小值。

$n\le 3\times 10^5,a_i\le 10^9$

Sol

由于有异或的出现,容易想到需要按位分析,我们从高到低枚举,考虑枚举到最高的有数字的该位为 $1$ 的数 $i$,那么如果我们该位已经定了,那么所有的数一定会分为两部分,一部分是大于等于 $2^i$ 的数,一部分是小于 $2^i$ 的数,那么由于是按位分析,那么这两部分间数的大小关系是已经定下来了的,已经产生的逆序对数也不会减少,这意味着我们已经算出了这两部分间的贡献,那么单独我们就可以把这些数分为两部分,对于每一个部分继续枚举位数,算出它们的贡献。

时间复杂度 $\mathcal{O}(n\log 10^9)$

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, a[300005], f[35][2], cnt;
struct qj {
    vector<int> num;
    vector<int> hdif;
}s[300005];
signed main() {
    n = Read();
    for(int i = 1; i <= n; i++)  s[1].num.push_back(Read());
    ++cnt;
    for(int k = 30; k >= 0; k--) {
        for(int i = 1; i <= cnt; i++) {
            int sum1 = 0, sum2 = 0;
            s[i].hdif.resize(s[i].num.size());
            for(int j = s[i].num.size() - 1; j >= 0; j--) {
                if((s[i].num[j] & (1 << k)) != 0)  s[i].hdif[j] = sum2, ++sum1;
                else  s[i].hdif[j] = sum1, ++sum2;
            }
            for(int j = 0; j < s[i].num.size(); j++) {
                if((s[i].num[j] & (1 << k)) != 0) f[k][0] += s[i].hdif[j];
                else  f[k][1] += s[i].hdif[j];
            }
        }
        for(int i = 1; i <= cnt; i++) {
            int sum1 = 0, sum2 = 0;
            for(int j = 0; j < s[i].num.size(); j++)
                if((s[i].num[j] & (1 << k)) != 0) ++sum2;
                else  ++sum1;
            if(!sum1 || !sum2)  continue;
            ++cnt;  vector<int> A;
            for(int j = 0; j < s[i].num.size(); j++) {
                if((s[i].num[j] & (1 << k)) != 0)  s[cnt].num.push_back(s[i].num[j]);
                else  A.push_back(s[i].num[j]);
            }
            s[i].num = A;
        }
    }
    int res = 0, sum = 0;
    for(int i = 0; i <= 30; i++) {
        if(f[i][0] <= f[i][1])  sum += f[i][0];
        else  res += (1 << i), sum += f[i][1];
    }
    cout << sum << " " << res << endl;
    return 0;
}
View Code

CF1417F

Description

给定一张图,有 $n$ 个点,每个点有点权 $a_i$,$m$ 条边,要求支持 $q$ 个操作,操作分两种:

  • 给定点 $x$ ,令 $x$ 能够到达的点中点权最大的点为 $u$ ,输出 $a_u$ ,并将 $a_u$ 更新为 $0$。
  • 删掉第 $i$ 条边。

$n\le 2\times 10^5,m\le 3\times 10^5,q\le 5\times10^5$

Sol

$1$ 操作需要正着来, $2$ 操作需要倒着来,粗略一看不好处理。

考虑树链剖分的思想,我们把一条重链上的点在线段树上的编号变为连续的编号,那么我们考虑对于每一个询问 $1$,我们需要将它能够走到的点的编号变为连续的。

我们先将所有操作存下来,倒着加边,用并查集维护,先建出图的一棵生成树,考虑维护编号的连续性,我们将每个点的编号初始化为 $1$,当合并时打一个加法 tag ,代表这个集合的点的编号都要加上一个值,最后从根 dfs 扫一遍就可以得出每个点的编号,注意图可能不连续。

然后清空并查集,按照相同的方式维护,处理出每个点集最小的编号与最大的编号,对于每个询问 $1$ ,它能够到达的点的区间就是该点集编号最小的点到编号最大的点,且它们的编号连续,最后正着拿线段树维护即可。

时间复杂度 $\mathcal{O}(n\log n)$

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int first[800005], nxt[800005], to[800005], tot = 0;
void Add(int x, int y) {
    nxt[++tot] = first[x];
    first[x] = tot;
    to[tot] = y;
}
int ex[500005], ey[500005], vis[500005], rt[500005], tag[500005];
int sz[500005], bh[500005], a[500005], opt[500005], x[500005], vvis[500005], maxn[500005], minn[500005];
int L[500005], R[500005], rev[500005], mx[2000005], Pos[2000005];
void pushup(int o) {
    if(mx[o << 1] > mx[o << 1 | 1])  mx[o] = mx[o << 1], Pos[o] = Pos[o << 1];
    else  mx[o] = mx[o << 1 | 1], Pos[o] = Pos[o << 1 | 1];
}
void build(int o, int l, int r) {
    if(l == r) {mx[o] = a[rev[l]]; Pos[o] = l; return ;}
    int mid = (l + r) >> 1;
    build(o << 1, l, mid);
    build(o << 1 | 1, mid + 1, r);
    pushup(o);
}
void modify(int o, int l, int r, int pos, int val) {
    if(l == r) {mx[o] = val; return ;}
    int mid = (l + r) >> 1;
    if(pos <= mid)  modify(o << 1, l, mid, pos, val);
    else  modify(o << 1 | 1, mid + 1, r, pos, val);
    pushup(o);
}
pair<int, int> my_max(pair<int, int> A, pair<int, int> B) {
    return (A.first > B.first) ? A : B;
}
pair<int, int> query(int o, int l, int r, int nl, int nr) {
    if(nl <= l && r <= nr)  return make_pair(mx[o], Pos[o]);
    int mid = (l + r) >> 1; pair<int, int> ans;
    ans.first = ans.second = 0;
    if(nl <= mid)  ans = my_max(ans, query(o << 1, l, mid, nl, nr));
    if(mid < nr)  ans = my_max(ans, query(o << 1 | 1, mid + 1, r, nl, nr));
    return ans;
}
int findfa(int x) {return rt[x] == x ? x : rt[x] = findfa(rt[x]);}
void dfs(int u) {
    vvis[u] = 1;
    bh[u] += tag[u];
    for(int e = first[u]; e; e = nxt[e]) {
        int v = to[e];
        tag[v] += tag[u];
        dfs(v);
    }
}
signed main() {
    int n = Read(), m = Read(), q = Read();
    for(int i = 1; i <= n; i++)  sz[i] = bh[i] = 1, rt[i] = i, a[i] = Read();
    for(int i = 1; i <= m; i++)  ex[i] = Read(), ey[i] = Read();
    for(int i = 1; i <= q; i++) {
        opt[i] = Read(), x[i] = Read();
        if(opt[i] == 2)  vis[x[i]] = 1;
    }
    for(int i = 1; i <= m; i++) {
        if(!vis[i]) {
            int fx = findfa(ex[i]), fy = findfa(ey[i]);
            if(fx == fy)  continue;
            tag[fx] += sz[fy];
            sz[fy] += sz[fx]; rt[fx] = fy;
            Add(fy, fx);
        }
    }
    for(int i = q; i >= 1; i--) {
        if(opt[i] == 2) {
            int fx = findfa(ex[x[i]]), fy = findfa(ey[x[i]]);
            if(fx == fy)  continue;
            tag[fx] += sz[fy];
            sz[fy] += sz[fx]; rt[fx] = fy;
            Add(fy, fx);
        }
    }
    int sum = 0;
    for(int i = 1; i <= n; i++)
        if(!vvis[findfa(i)])  tag[rt[i]] += sum, sum += sz[rt[i]], dfs(rt[i]);
    for(int i = 1; i <= n; i++)
        maxn[i] = minn[i] = bh[i], rt[i] = i, rev[bh[i]] = i;
    for(int i = 1; i <= m; i++) {
        if(!vis[i]) {
            int fx = findfa(ex[i]), fy = findfa(ey[i]);
            if(fx == fy)  continue;
            maxn[fy] = max(maxn[fy], maxn[fx]);
            minn[fy] = min(minn[fy], minn[fx]);
            rt[fx] = fy;
        }
    }
    for(int i = q; i >= 1; i--) {
        if(opt[i] == 2) {
            int fx = findfa(ex[x[i]]), fy = findfa(ey[x[i]]);
            if(fx == fy)  continue;
            maxn[fy] = max(maxn[fy], maxn[fx]);
            minn[fy] = min(minn[fy], minn[fx]);
            rt[fx] = fy;
        }
        if(opt[i] == 1)
            L[i] = minn[findfa(x[i])], R[i] = maxn[findfa(x[i])];
    }
    build(1, 1, n);
    for(int i = 1; i <= q; i++) {
        if(opt[i] == 1) {
            pair<int, int> tmp = query(1, 1, n, L[i], R[i]);
            printf("%d\n", tmp.first);
            modify(1, 1, n, tmp.second, 0);
        }
    }
    return 0;
}
View Code

 

posted @ 2020-09-28 20:53  verjun  阅读(264)  评论(0编辑  收藏  举报