1. 程式人生 > 其它 >2021.11.3模擬賽 賽後總結

2021.11.3模擬賽 賽後總結

A. 質數統計(prime)

Description

給定 \(n\) 個正整數 \(a_1,a_2\dots a_n\),定義 \(Cnt(p)\) 表示這 \(n\) 個數中能被質數 \(p\) 整除的數的個數,不是質數的 \(Cnt()\)\(0\)

\(m\) 組詢問,每組詢問輸入 \(l,r\),求 \(\sum_{i=l}^rCnt(i)\)

\(1\le n,m \le 10^6,1\le a_i \le 10^7,1\le l \le r \le 10^7\)

Solution

顯然先預處理出 \(cnt\) 陣列,做個字首和,\(O(1)\) 查詢。

可以用線性篩,同時求出每個數的最小質因子 \(mindiv\)

,對於每個 \(a_i\),不斷除以 \(mindiv\),就可以得到每個質因子了,也就求出了 \(cnt\) 陣列。

也可以不處理 \(mindiv\),就仿照質因數分解的過程也可以過

題上說要對 \(10^9+7\) 取模,但事實證明並不用。

Code
#include <bits/stdc++.h>
#define ll long long

using namespace std;

template <typename T>
void read(T &x)
{
    x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
    return;
}

template <typename T>
void write(T x)
{
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}

const int N = 1e6 + 5;
const int MAXN = 1e7 + 5;
const int P = 1e9 + 7;
int n, m, a[N], maxn;
bool flag[MAXN];
int p[N], tot, mindiv[MAXN];

void Euler(int n)
{
    flag[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        if(!flag[i]) p[++tot] = i, mindiv[i] = i;
        for(int j = 1; j <= tot && i * p[j] <= n; j++)
        {
            flag[i * p[j]] = 1;
            mindiv[i * p[j]] = p[j];
            if(i % p[j] == 0) break;
        }
    }
    return;
}

ll cnt[MAXN];

void Divide(int x)
{
    while(x > 1)
    {
        cnt[mindiv[x]]++;
        int d = mindiv[x];
        while(x % d == 0) x /= d;
    }
    return;
}

int main()
{
    read(n), read(m);
    for(int i = 1; i <= n; i++)
        read(a[i]), maxn = max(maxn, a[i]);
    Euler(maxn);
    for(int i = 1; i <= n; i++)
        Divide(a[i]);
    for(int i = 1; i < MAXN; i++)
        cnt[i] += cnt[i - 1];
    while(m--)
    {
        int l, r;
        read(l), read(r);
        write(cnt[r] - cnt[l - 1]);
        putchar('\n');
    }
    return 0;
}

B. 城市規劃(planning)

Description

給定 \(n\) 棟樓的高度 \(h_i\),定義這些樓的不美觀度為 \(max_{1\le i\le n}\{|h_i-h_{i+1}|\}\)

現在你可改變任意 \(k\) 棟樓的高度,使得不美觀度最小。

輸出最小值為多少。

\(1\le k \le n \le 2000,1\le h_i \le 2\times 10^9\)

Solution

觀察到是最大值最小,不難想到二分答案。

對於二分的答案 \(x\),考慮如何判斷是否可行

\(f_i\) 表示將前 \(i\) 棟樓改成合法的最少需要改幾棟樓的高度

初值為 \(i-1\)

,轉移時,列舉 \(j\),若 \(|h_i-h_j| \le x\times (i-j)\),就說明可以通過修改 \((i,j)\) 中間的 \(i-j-1\) 樓使得前 \(i\) 棟樓合法

\(f_i=min(f_i,f_j+i-j-1)\)

最後將 \(n\) 棟樓列舉一遍,判斷是否存在 \(f_i+n-i \le k\)

注意樓比較高,要開 long long

Code
#include <bits/stdc++.h>
#define int long long

using namespace std;

template <typename T>
void read(T &x)
{
    x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
    return;
}

template <typename T>
void write(T x)
{
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}

const int N = 2010;
int n, k, a[N], ans;
int f[N];

bool chk(int x)
{
    for(int i = 1; i <= n; i++)
    {
        f[i] = i - 1;
        for(int j = 1; j < i; j++)
            if(abs(a[i] - a[j]) <= 1ll * x * (i - j))
                f[i] = min(f[i], f[j] + i - j - 1);
    }
    for(int i = 1; i <= n; i++)
        if(f[i] + n - i <= k) return true;
    return false;
}

signed main()
{
    read(n), read(k);
    int l = 0, r = 0;
    for(int i = 1; i <= n; i++)
        read(a[i]), r = max(r, abs(a[i] - a[i - 1]));
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(chk(mid)) r = mid - 1, ans = mid;
        else l = mid + 1;
    }
    write(ans), putchar('\n');
    return 0;
}

C. 花環(garland)

Description

給定 \(n\) 個點的環,劃分為 \(m\) 段,最大化和。

\(1\le n \le 3 \times 10^5,1\le m \le 10^5\)

Solution

首先注意到一個性質:如果有連續一段權值都為正的點,那麼如果選擇了一個,肯定選完一整段(可能分成幾段來選)。

如果這樣的段的個數 \(\le m\),那麼答案即為這些段的和。

否則,要減少一些段,有兩種方法可以減少一段:

  1. 少選一整段全為正的
  2. 選擇一段全為負的,將兩段正的連起來

所以一整段負的也是要麼不選,要麼全選

那麼我們可以將這樣連續的正的和負的縮成一個點,就變成了一個正負相間的環

考慮如何減到剩 \(m\) 段。

先把負的權值改為正的,然後每次選出一段就把和減掉,答案的初值為所有正段的和。

不難發現,如果選出第 \(i\) 段,那麼兩邊的段一定不會再被選,因為如果第 \(i\) 段原來是正的,那麼已經減少了一段,再選兩邊的會使答案更劣,如果第 \(i\) 段原來是負的,那麼選兩邊也會使答案更劣。

但是我們不能只貪心的去選當前最優的,因為有可能再選了其他段後,另一種方案更優,所以要進行反悔貪心。

具體請見 Luogu P3620 資料備份

Code
#include <bits/stdc++.h>

using namespace std;

template <typename T>
void read(T &x)
{
    x = 0; int f = 1; char c = getchar();
    while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
    x *= f;
    return;
}

template <typename T>
void write(T x)
{
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}

const int N = 6e5 + 5;
typedef pair<int, int> P;

int n, m, a[N], ans;
int val[N], cnt;
int pre[N], nxt[N];
bool vis[N];
priority_queue <P, vector<P>, greater<P> > que;

void del(int x)
{
    vis[x] = 1;
    nxt[pre[x]] = nxt[x];
    pre[nxt[x]] = pre[x];
    return;
}

int main()
{
    read(n), read(m);
    for(int i = 1, lst = 0; i <= n; lst = a[i], i++)
    {
        read(a[i]);
        if(a[i] * lst > 0) val[cnt] += a[i];
        else val[++cnt] = a[i];
    }
    if(val[1] * val[cnt] > 0 && cnt > 1) val[1] += val[cnt], cnt--;

    n = cnt, cnt = 0;
    for(int i = 1; i <= n; i++)
        if(val[i] > 0) ans += val[i], cnt++;

    if(cnt <= m)
    {
        write(ans);
        return 0;
    }

    for(int i = 1; i <= n; i++)
    {
        pre[i] = i - 1, nxt[i] = i + 1;
        if(val[i] < 0) val[i] = -val[i];
        que.push(P(val[i], i));
    }
    pre[1] = n, nxt[n] = 1;

    while(cnt > m)
    {
        cnt--;
        while(vis[que.top().second]) que.pop();
        P p = que.top();
        que.pop();
        ans -= p.first;
        int id = p.second;
        val[id] = val[pre[id]] + val[nxt[id]] - val[id];
        del(pre[id]), del(nxt[id]);
        que.push(P(val[id], id));
    }
    write(ans);
    return 0;
}

D. 密碼門(cipher)

Description

給定 \(n\) 個操作符和引數,操作符包括按位與、按位或、按位異或,求一個數通過這些操作後的值,多組詢問,帶修。

修改某個操作的操作符和引數。

\(1\le n,m \le 2\times 10^5,1\le x \le 1000\)

Solution

用線段樹維護二進位制每一位上一開始是 \(0\)\(1\),最後結果是多少。

因為數最大隻有 \(1000\),二進位制也就 \(10\) 位,因此對每一位建個線段樹還是可以的,不過貌似有壓在一起的做法?不太會

pushup 就是將在左子樹裡求出來的結果放在右子樹裡再求

Code
#include <bits/stdc++.h>
#define ls (rt << 1)
#define rs (rt << 1 | 1)

using namespace std;

const int N = 2e5 + 5;
int n, m, a[N];
int op[N], x[N];
int t[10][N << 2][2];   // & | ^

int get_op(char c)
{
    if(c == 'A') return 0;
    if(c == 'O') return 1;
    if(c == 'X') return 2;
}

void pushup(int b, int rt)
{
    t[b][rt][0] = t[b][rs][t[b][ls][0]];
    t[b][rt][1] = t[b][rs][t[b][ls][1]];
}

void build(int b, int l, int r, int rt)
{
    if(l == r)
    {
        if(op[l] == 0) t[b][rt][0] = 0, t[b][rt][1] = a[l];
        if(op[l] == 1) t[b][rt][0] = a[l], t[b][rt][1] = 1;
        if(op[l] == 2) t[b][rt][0] = a[l], t[b][rt][1] = a[l] ^ 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(b, l, mid, ls);
    build(b, mid + 1, r, rs);
    pushup(b, rt);
}

void update(int b, int pos, int op, int val, int l, int r, int rt)
{
    if(l == r)
    {
        if(op == 0) t[b][rt][0] = 0, t[b][rt][1] = val;
        if(op == 1) t[b][rt][0] = val, t[b][rt][1] = 1;
        if(op == 2) t[b][rt][0] = val, t[b][rt][1] = val ^ 1;
        return;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(b, pos, op, val, l, mid, ls);
    else update(b, pos, op, val, mid + 1, r, rs);
    pushup(b, rt);
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        char s[5];
        scanf("%s%d", s, &x[i]);
        op[i] = get_op(s[0]);
    }

    for(int i = 0; i < 10; i++)
    {
        for(int j = 1; j <= n; j++)
            a[j] = (x[j] >> i) & 1;
        build(i, 1, n, 1);
    }

    while(m--)
    {
        int typ, x;
        scanf("%d%d", &typ, &x);
        if(typ == 1)
        {
            int ans = 0;
            for(int i = 0; i < 10; i++)
                ans += t[i][1][(x >> i) & 1] << i;
            printf("%d\n", ans);
        }
        else
        {
            char s[5];
            int y, opt;
            scanf("%s%d", s, &y);
            opt = get_op(s[0]);
            for(int i = 0; i < 10; i++)
                update(i, x, opt, (y >> i) & 1, 1, n, 1);
        }
    }
    return 0;
}
$$A\ drop\ of\ tear\ blurs\ memories\ of\ the\ past.$$