Codeforces Round #772 (Div. 2) 题解 (A-F)

1635A - Min Or Sum

题意:给定一个序列 a a a,每次可以将 a i a_i ai a j a_j aj 替换成 x x x y y y,要求 a i ∣ a j = x ∣ y a_i\mid a_j = x\mid y aiaj=xy,问若干次操作后能达到的最小的序列和。

注意到将 ( a i , a j ) → ( a i ∣ a j , 0 ) (a_i, a_j)\to (a_i\mid a_j, 0) (ai,aj)(aiaj,0) 是最优的做法,我们可以通过这样的操作,让尽可能多的数字等于 0 0 0,然后保留一个 a 1 ∣ a 2 ∣ ⋯ ∣ a n a_1\mid a_2\mid \cdots \mid a_n a1a2an

所以 a 1 ∣ a 2 ∣ ⋯ ∣ a n a_1\mid a_2\mid \cdots \mid a_n a1a2an 即为答案。

1635B - Avoid Local Maximums

题意:给定一个序列 a a a,每次可以将一个 a i a_i ai 改成任意正整数 x x x,问最少多少次这样的操作后,整个序列不存在 local maximum,即不存在 i i i 满足 a i − 1 < a i > a i + 1 a_{i-1} < a_i>a_{i+1} ai1<ai>ai+1 n = 2 × 1 0 5 n = 2\times 10^5 n=2×105

div2 B 题,考虑有没有什么贪心做法。

我们注意到,一次操作最多能够消除两个 local maximums,而且这两个 local maximum 一定相邻。

所以考虑如下贪心算法,从左往右扫,如果遇到一个 local maximum a i a_i ai,则将 a i + 1 ← max ⁡ ( a i , a i + 2 ) a_{i + 1}\leftarrow \max(a_{i}, a_{i + 2}) ai+1max(ai,ai+2)。不难发现这样最多就可以同时干掉两个 local maximum,因此其是最优的。

const int maxn = 2e5 + 5;
int a[maxn], n;

int main() {
    int T; read(T);
    while (T--) {
        read(n);
        FOR(i, 1, n) read(a[i]);
        int ans = 0;
        FOR(i, 2, n - 1)
            if (a[i] > a[i + 1] && a[i] > a[i - 1])
                a[i + 1] = max(a[i], a[i + 2]), ++ans;
        print(ans);
        FOR(i, 1, n) print(a[i], ' ');
        putchar('\n');
    }
    return output(), 0;
}

1635C - Differential Sorting

题意:给定一个序列 a a a,每次操作可以选择 1 ≤ x < y < z ≤ n 1\le x < y < z\le n 1x<y<zn,然后 a x ← a y − a z a_x\leftarrow a_y - a_z axayaz,构造一个方案使得整个序列不降。 n = 2 × 1 0 5 n = 2\times 10^5 n=2×105

我们注意到,选择的三个下标一定要满足 x < y < z x < y < z x<y<z,所以发现第一个性质: a n − 1 > a n a_{n-1}>a_n an1>an,则无解,因为我们没办法修正 a n − 1 a_n - 1 an1。接下来,我们注意到,若 a n ≥ 0 a_n\ge0 an0,则我们可以将前面所有数字修正为 a n − 1 − a n a_{n-1} -a_n an1an。否则无解,除非序列一开始就不降。

证明:假设 a n < 0 a_n<0 an<0 且序列初始不满足不降,并且我们用了 m m m 步完成排序。假设我们最后一步操作是 ( x m , y m , z m ) (x_m, y_m, z_m) (xm,ym,zm),不难发现为了保证不降,所有元素都是负值,故 a z m a_{z_m} azm 一定 < 0 < 0 <0,但是 a x m = a y m − a z m > a y m a_{x_m} = a_{y_m} - a_{z_m}>a_{y_m} axm=aymazm>aym,假设不成立。

const int maxn = 2e5 + 5;
int a[maxn], n;

int main() {
    int T; read(T);
    while (T--) {
        read(n);
        FOR(i, 1, n) read(a[i]);
        if (is_sorted(a + 1, a + n + 1)) {
            print(0);
            continue;
        }
        if (a[n - 1] > a[n] || a[n] < 0) {
            print(-1);
            continue;
        }
        print(n - 2);
        FOR(i, 1, n - 2) print(i, n - 1, n);
    }
    return output(), 0;
}

1635D - Infinite Set

题意:给定一个序列 a 1 ⋯ n a_{1\cdots n} a1n,令集合 S S S 如下构造:

  • ∀ i \forall i i a i ∈ S a_i\in S aiS
  • ∀ x ∈ S \forall x\in S xS 2 x + 1 ∈ S 2x +1\in S 2x+1S
  • ∀ x ∈ S \forall x\in S xS 4 x ∈ S 4x\in S 4xS

问集合 S S S 中严格小于 2 p 2^p 2p 的元素个数,答案对 1 0 9 + 7 10^9+7 109+7 取模, n , p ≤ 2 × 1 0 5 n,p\le 2\times 10^5 n,p2×105

发现 p p p 很大,且 2 x + 1 2x + 1 2x+1 4 x 4x 4x 这样的操作让人联想到位运算。所以先在二进制下考虑这个问题的 n = 1 n = 1 n=1 版本。

发现,每次做插入 2 x + 1 2x+1 2x+1 和插入 4 x 4x 4x 这样构成了一个决策树,得到的答案绝对不会相交。而 2 x + 1 2x+1 2x+1 相当于将 x x x 左移一位再将最后一位设成 1 1 1 4 x 4x 4x 相当于将 x x x 左移两位。所以我们实际上就是将 x x x 往左移一定的位数,保证最高位 $ < p$ 即可。

统计一下这样的方案数:不难发现设 f i f_i fi 为左移 i i i 位能得到的数字个数,有
{ f 1 = 1 f 2 = 2 f i = f i − 1 + f i − 2 + 1 i ≥ 3 \begin{cases} f_1 = 1\\ f_2 = 2\\ f_i = f_{i-1} + f_{i-2} + 1&i\ge 3 \end{cases} f1=1f2=2fi=fi1+fi2+1i3
于是 n = 1 n=1 n=1 的情况就考虑完了。对于原来那种“从数集”扩展的模式,最麻烦的就是 a i a_i ai 能变换到 a j a_j aj,这样子方案数就会算重。所以考虑去重。发现从决策树的节点往根跳的过程很容易(因为最后一步是确定的),所以对于每个 a i a_i ai,都这样往回跳跳,如果跳到了某个 a j a_j aj,就直接不用考虑 a i a_i ai 即可。

于是本题得到了解决。

const int maxn = 2e5 + 5;
int n, p, a[maxn];
modint f[maxn];
std::set<int> S;

int main() {
    read(n, p);
    f[p] = 1;
    DEC(i, p - 1, 1) f[i] = f[i + 1] + f[i + 2] + 1;
    FOR(i, 1, n) read(a[i]), S.insert(a[i]);
    modint ans = 0;
    DEC(i, n, 1) {
        bool flg = 1;
        int x = a[i];
        while (x) {
            if (x & 1) x >>= 1;
            else if (x % 4) break;
            else x >>= 2;
            if (S.count(x)) {
                flg = 0;
                break;
            }
        }
        if (flg) {
            int highbit = 0;
            DEC(j, 30, 0) if ((1 << j) & a[i]) {
                highbit = j;
                break;
            }
            ans += f[highbit + 1];
        }
    }
    print(ans);
    return output(), 0;
}

1635E - Cars

一维数轴上,初始在 n n n 个不同位置有 n n n 辆车,车的朝向确定,而速度可以为任意常数。定义两种车子之间的关系:

  • 称两辆车风马牛不相及当且仅当不论他们的速度如何,他们都永远无法相遇。
  • 称两辆车双向奔赴命中注定当且仅当不论他们的速度如何,他们都能在一个点相遇。

现在给定 m m m 条这样的关系,请还原出 n n n 辆车的坐标和朝向。 n , m ≤ 2 × 1 0 5 n,m\le 2\times 10^5 n,m2×105

首先不难发现,“风马牛不相及”指的就是两辆车是往两边散开的,“双向奔赴命中注定”指的就是两辆车是往中间双向奔赴的。有关系的车的朝向都是不一样的,这个可以用二分图染色染一下。

然后,对于风马牛不相及的车的关系,可以发现 x L < x R x_L < x_R xL<xR,相应的对于双向奔赴的,有 x R < x L x_R < x_L xR<xL,于是我们可以考虑给原来二分图的边定向:若 x u < x v x_u < x_v xu<xv,则连边 u → v u\to v uv,反之亦然。

这样下来,发现这些约束关系合法当且仅当图是张 DAG,并且需要求方案的话输出拓扑序就可以了。所以总结一下算法流程:二分图染色(判断无解),给边定向,拓扑排序(判断无解)。

const int maxn = 2e5 + 5;
vector<int> G0[maxn], G[maxn];
int n, m, vis[maxn], col[maxn], ind[maxn], x[maxn];
bool flg = 1;

struct Relation {
    int op, u, v;
} a[maxn];

void dfs(int u, int cur) {
    col[u] = cur, vis[u] = 1;
    for (int &v : G0[u]) {
        if (vis[v] && col[v] == cur) flg = 0;
        if (vis[v]) continue;
        dfs(v, cur == 1 ? 2 : 1);
    }
}

int main() {
    read(n, m);
    FOR(i, 1, m) read(a[i].op, a[i].u, a[i].v), G0[a[i].u].push_back(a[i].v), G0[a[i].v].push_back(a[i].u);
    FOR(i, 1, n) if (!vis[i]) dfs(i, 1);
    if (!flg) {
        print("NO");
    } else {
        FOR(i, 1, m) {
            const int &u = a[i].u, &v = a[i].v;
            if (a[i].op == 1) {
                if (col[u] == 1) G[u].push_back(v), ++ind[v];
                else G[v].push_back(u), ++ind[u];
            } else {
                if (col[u] == 2) G[u].push_back(v), ++ind[v];
                else G[v].push_back(u), ++ind[u];
            }
        }
        queue<int> q;
        FOR(i, 1, n) if (!ind[i]) q.push(i);
        int cntx = 0;
        while (!q.empty()) {
            int u = q.front();
            x[u] = ++cntx;
            q.pop();
            for (const int &v : G[u]) {
                if (!--ind[v]) q.push(v);
            }
        }
        if (cntx != n) {
            print("NO");
        } else {
            print("YES");
            FOR(i, 1, n) {
                putchar(col[i] == 1 ? 'L' : 'R'); putchar(' ');
                print(x[i]);
            }
        }
    }
    return output(), 0;
}

1635F - Closest Pair

n n n 个二元组 ( x i , w i ) (x_i, w_i) (xi,wi) ∣ x i ∣ , w i ≤ 1 0 9 |x_i|,w_i\le 10^9 xi,wi109 n ≤ 3 × 1 0 5 n\le 3\times 10^5 n3×105 x i x_i xi 升序。 q ≤ 3 × 1 0 5 q\le 3\times 10^5 q3×105 次询问,给出 [ l , r ] [l,r] [l,r],求

min ⁡ l ≤ i < j ≤ r ∣ x i − x j ∣ ( w i + w j ) \min_{l\le i < j\le r}|x_i - x_j|(w_i + w_j) li<jrminxixj(wi+wj)

数据结构结论题。

考虑哪些 ( i , j ) (i,j) (i,j) 是不优的。

  • 若存在 p p p q q q,使得 x p < x q ∧ w p > w q x_p < x_q\land w_p > w_q xp<xqwp>wq,则 q q q 作为 i i i 一定比 p p p 优。
  • 若存在 p p p q q q,使得 x p < x q ∧ w p < w q x_p < x_q\land w_p < w_q xp<xqwp<wq,则 p p p 作为 j j j 一定比 q q q 优。

于是,令 L i = max ⁡ { j : j < i ∧ w j ≤ w i } L_i = \max\{j:j < i\land w_j\le w_i\} Li=max{j:j<iwjwi} R i = min ⁡ { j : j > i ∧ w j ≤ w i } R_i = \min\{j:j > i\land w_j\le w_i\} Ri=min{j:j>iwjwi},答案就只能取形如 [ i , R i ] [i, R_i] [i,Ri] [ L i , i ] [L_i, i] [Li,i] 的区间。这样的区间一共有 2 n 2n 2n 个。

先用单调栈求出所有的 L i L_i Li R i R_i Ri,再计算出这 2 n 2n 2n 个区间 [ l i , r i ] [l_i, r_i] [li,ri] 的价值 c i c_i ci,在每个 r i r_i ri 上挂上标记 ( l i , c i ) (l_i, c_i) (li,ci),在每个询问 ( i , q l , q r ) (i, ql, qr) (i,ql,qr) q r qr qr 上挂上标记 ( q l , i ) (ql, i) (ql,i)。然后扫描线扫右端点,线段树或者树状数组维护一下 RMQ 和单点修改,这题就做完了。

using ll = long long;
using pll = pair<ll, ll>;
const int maxn = 3e5 + 5;
int n, m, L[maxn], R[maxn], stk[maxn], top;
ll t[maxn << 2], ans[maxn];
pll a[maxn];
vector<pll> op[maxn], q[maxn];

il ll calc(int i, int j) {return myabs(a[i].first - a[j].first) * (a[i].second + a[j].second);}

#define L (k << 1)
#define R (L | 1)
#define M ((i + j) >> 1)

void modify(int i, int j, int k, int x, ll v) {
    if (i == j) {
        chkmin(t[k], v);
        return;
    }
    if (x <= M) modify(i, M, L, x, v);
    else modify(M + 1, j, R, x, v);
    t[k] = min(t[L], t[R]);
    return;
}

ll query(int i, int j, int k, int x, int y) {
    if (x <= i && y >= j) return t[k];
    ll ret = 2e18;
    if (x <= M) chkmin(ret, query(i, M, L, x, y));
    if (y > M) chkmin(ret, query(M + 1, j, R, x, y));
    return ret;
}

#undef L
#undef R
#undef M

int main() {
    read(n, m);
    FOR(i, 1, n) read(a[i].first, a[i].second);
    FOR(i, 1, m) {
        int l, r; read(l, r);
        q[r].push_back({l, i});
    }
    FOR(i, 1, n) {
        while (top > 0 && a[stk[top]].second > a[i].second) --top;
        L[i] = stk[top], stk[++top] = i;
    }
    top = 0;
    DEC(i, n, 1) {
        while (top > 0 && a[stk[top]].second > a[i].second) --top;
        R[i] = stk[top], stk[++top] = i;
    }
    FOR(i, 1, n) {
        if (L[i]) op[i].push_back({L[i], calc(L[i], i)});
        if (R[i]) op[R[i]].push_back({i, calc(i, R[i])});
    }

    memset(t, 0x3f, sizeof t);
    FOR(i, 1, n) {
        for (auto &p : op[i]) modify(1, n, 1, p.first, p.second);
        for (auto &p : q[i]) ans[p.second] = query(1, n, 1, p.first, i);
    }
    FOR(i, 1, m) print(ans[i]);
    return output(), 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值