1. 程式人生 > 其它 >“卓見杯”2020年河南省CCPC 虛擬賽補題總結

“卓見杯”2020年河南省CCPC 虛擬賽補題總結

班委競選

知識點:結構體排序

廣告投放

知識點:dp,數論分塊

思路:

定義\(f(i,j)\)為考慮前\(i\)個節目,觀眾為\(j\)的最大收益
轉移方程為\(f(i,j/d[i])=max(f(i-1,j/d[i]),f(i-1,j)+j*p[i])\)
此時時間複雜度為\(O(nm)\)
利用數論分塊優化\(dp\)
\(⌊⌊n/i⌋ /j⌋ = ⌊n/(i · j)⌋\)
$⌊n/i⌋ $的取值只有 \(O(√n)\) 種(數論分塊、整數分塊)
所以先把所有的取值取出來,在進行\(dp\),時間複雜度\(O(n√m)\)
第一維可以利用滾動陣列,改成\(0/1\),或者開2個\(dp\)陣列來轉移

View Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
#define int long long
int n, m;
int p[N], d[N];
int f[N][2];
int g[N];
int val[N];
int cnt;
int vis[N];
signed main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> p[i];
    for (int i = 1; i <= n; i++) cin >> d[i];

    val[++cnt] = m;
    vis[m] = 1;
    for (int i = m; i >= 1; i--) {
        if (vis[i]) {
            for (int j = 1; j <= i; j++) {
                vis[i / j] = 1;
            }
            val[++cnt] = i;
        }
    }
    sort(val + 1, val + cnt + 1);

    cnt = unique(val + 1, val + 1 + cnt) - val - 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= cnt; j++) {
            f[val[j] / d[i]][0] = max(f[val[j] / d[i]][0], f[val[j]][1] + val[j] * p[i]);
        }
        for (int j = 1; j <= cnt; j++) {
            f[val[j]][1] = f[val[j]][0];
        }
    }

    int res = 0;
    for (int i = 0; i <= m; i++) {
        res = max(res, f[i][0]);
    }
    cout << res << endl;
}

我得重新集結部隊

知識點:大模擬

發通知

知識點:差分,離散化

思路:

利用\(map\)或者手動離散化,再進行差分,求一遍字首和,看滿足題意的時間點中的異或和最大值

View Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
struct node {
    int l, r;
    int w;
} a[N];
int n, k;
int res;
map<int, int> x;
map<int, int> y;
bool cmp(node a, node b) {
    if (a.l == b.l) return a.r < b.r;
    return a.l < b.l;
}
int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].l >> a[i].r >> a[i].w;
    }
    sort(a + 1, a + n + 1, cmp);

    for (int i = 1; i <= n; i++) {
        int l = a[i].l, r = a[i].r;
        int w = a[i].w;
        x[l] += 1, x[r + 1] -= 1;
        y[l] ^= w, y[r + 1] ^= w;
    }
    bool is_first = 1;
    int now = 0;
    for (auto it : x) {
        if (is_first) {
            is_first = 0;
            now = it.second;
            continue;
        } else {
            now += it.second;
            x[it.first] = now;
        }
    }

    is_first = 1;
    now = 0;

    for (auto it : y) {
        if (is_first) {
            is_first = 0;
            now = it.second;
            continue;
        } else {
            now = now ^ it.second;
            y[it.first] = now;
        }
    }
    int res = -1;
    for (auto it : x) {
        if (it.second >= k) {
            res = max(res, y[it.first]);
        }
    }
    cout << res << endl;
}

旅遊勝地

知識點:2-SAT,二分

思路:

二分距離,然後看當前這種距離下的建圖,跑2-SAT,然後就看\(x\)\(¬x\)是否在一個強連通分量裡
建邊時,就看當前這對點選\(a\)\(b\)值,是否滿足題意,例如,\(|a_x-b_y|>mid\) ,那麼就是\(¬x∨¬y\),也就是x向y+n連邊,y向x+n連邊,其餘情況類似
還有樹形\(dp\)的做法,不過沒有理解,用的\(2-SAT\)的做法

View Code
#include <bits/stdc++.h>
using namespace std;
const int N = 8e5 + 10;
int n, m;
int a[N], b[N];
int h[N], e[N], ne[N], idx;
int dfn[N], low[N], timetamp;
int sta[N], top;
int in_sta[N], id[N], scc_cnt;
int u[N], v[N];
void add_edge(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
void init() {
    memset(h,-1,sizeof(h));
    idx=0;
    top = 0, scc_cnt = 0, timetamp = 0;
    for (int i = 0; i <= 2 * n + 2; i++) {
        dfn[i] = low[i] = sta[i] = in_sta[i] = id[i] = 0;
    }
}
 
void tarjan(int u) {
    dfn[u] = low[u] = ++timetamp;
    sta[++top] = u;
    in_sta[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        } else if (in_sta[j]) {
            low[u] = min(low[u], dfn[j]);
        }
    }
 
    if (dfn[u] == low[u]) {
        scc_cnt++;
        int y;
        do {
            y = sta[top--];
            in_sta[y] = 0;
            id[y] = scc_cnt;
        } while (y != u);
    }
}
bool check(int s) {
    init();
 
    for (int i = 1; i <= m; i++) {
        int x = u[i], y = v[i];
        if (abs(a[x] - a[y]) > s) {
            add_edge(x, y + n);
            add_edge(y, x + n);
        }
        if (abs(a[x] - b[y]) > s) {
            add_edge(x, y);
            add_edge(y + n, x + n);
        }
        if (abs(b[x] - a[y]) > s) {
            add_edge(x + n, y + n);
            add_edge(y, x);
        }
        if (abs(b[x] - b[y]) > s) {
            add_edge(x + n, y);
            add_edge(y + n, x);
        }
    }
 
    for (int i = 1; i <= 2 * n; i++) {
        if (!dfn[i]) {
            tarjan(i);
        }
    }
 
    for (int i = 1; i <= n; i++) {
        if (id[i] == id[i + n]) {
            return 0;
        }
    }
    return 1;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    for (int i = 1; i <= n; i++) {
        scanf("%d", &b[i]);
    }
 
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &u[i], &v[i]);
    }
 
    int l = 0, r = 2e9;
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    printf("%d\n", l);
}

太陽轟炸

知識點:二項分佈,概率,組合數

思路:

炸到虛空的概率為\(p=min(1,\frac{(R_1+r)^2}{(R_2)^2})\),算出至少要炸\(cnt\)
那麼期望就是\(\sum_{i=cnt}^{n} C_n^i p^i (1-p)^{n-i}\qquad\)

View Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int mod = 1e9 + 7;
const int N = 6e6 + 10;
int fact[N], infact[N];
int qmi(int a, int k) {
    int res = 1;
    while (k) {
        if (k & 1) res = res * a % mod;
        k >>= 1;
        a = a * a % mod;
    }

    return res;
}
void init() {
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i++) {
        fact[i] = i * fact[i - 1] % mod;
    }
    infact[N - 1] = qmi(fact[N - 1], mod - 2) % mod;
    for (int i = N - 2; i >= 1; i--) {
        infact[i] = infact[i + 1] * (i + 1) % mod;
    }
}
int C(int n, int m) {
    if (m > n) return 0;
    return fact[n] * infact[n - m] % mod * infact[m] % mod;
}
signed main() {
    init();
    int n, R1, R2, r, atk, h;
    cin >> n >> R1 >> R2 >> r >> atk >> h;
    int nR = R1 + r;
    int a = nR * nR;  //交面積
    int b = R2 * R2;  //太陽面積
    int A = b - a;
    int B = b;

    int cnt = (h + atk - 1) / atk;
    if (cnt > n) {
        cout << 0 << endl;
        return 0;
    }
    if (R1 + r >= R2) {
        puts("1");
        return 0;
    }
    a = a % mod;
    b = b % mod;
    A = (b - a + mod) % mod;
    int res = 0;
    int y = qmi(2, n) % mod;
    y = qmi(2, mod - 2) % mod;
    int down = qmi(b, n) % mod;
    down = qmi(down, mod - 2) % mod;

    for (int i = cnt; i <= n; i++) {
        int x = C(n, i) % mod;
        int up = qmi(a, i) % mod * qmi(A, n - i) % mod;
        res = (res % mod + x * up % mod * down % mod) % mod;
        res = res % mod;
    }

    res = res % mod;
    cout << res << endl;
}

二進位制與、平方和

知識點:線段樹

思路:

線段樹維護每個數二進位制下的每一位,由於是\(AND\)操作,所以對於每一位上,若是\(1\),則一直不用改,可以發現需要修改的情況只有當前位置是\(1\)\(x\)的當前位置為\(0\),這樣操作後,就會變成\(0\),但是這個題維護每一位的話,不方便在pushdown的時候,在修改每一位的值的情況下同時算出區間平方和,所以區間修改,懶標記不好維護(可能有懶標記的做法,我沒想到),這樣的話,可以遞迴維護每一位和平方和,在修改操作時,對於修改二進位制下的每一位的值時,就看每一位上的\(0/1\)來修改,平方和在修改完兒子結點後,回溯回來pushup更新父親結點的區間平方和的值,其餘操作就和基礎線段樹一樣
時間複雜度詳細分析看題解

View Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 3e5 + 10;
const int mod = 998244353;
 
struct node {
    int l, r;
    int a[25];
    int sum;
} tr[N * 4];
int n, m;
int a[N];

void push_up(int u, int l, int r) {
    for (int i = 0; i < 25; i++) {
        tr[u].a[i] = tr[l].a[i] + tr[r].a[i];
    }
    tr[u].sum = (tr[l].sum + tr[r].sum) % mod;
}
void push_up(int u) { push_up(u, u << 1, u << 1 | 1); }
void build(int u, int l, int r) {
    tr[u].l = l, tr[u].r = r;
    if (l == r) {
        for (int i = 0; i < 25; i++) {
            if ((a[l] >> i) & 1) {
                tr[u].a[i] = 1;
            }
        }
        tr[u].sum = a[l] * a[l] % mod;
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    push_up(u);
}
void modify(int u, int l, int r, int x) {
    if (tr[u].l == tr[u].r) {
        int res = 0;
        for (int i = 0; i < 25; i++) {
            if ((tr[u].a[i]) && (((x >> i) & 1) == 0)) {
                tr[u].a[i] = 0;
            }
        }
        for (int i = 0; i < 25; i++) {
            if (tr[u].a[i]) {
                res = res | 1 << i;
            }
        }
        tr[u].sum = res * res % mod;
        return;
    }
 
    if (l <= tr[u].l && tr[u].r <= r) {
        bool f = 1;
        for (int i = 0; i < 25; i++) {
            if ((tr[u].a[i]) && ((x >> i) & 1) == 0) {
                f = 0;
                break;
            }
        }
        if (f) return;
    }
 
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u << 1, l, r, x);
    if (r > mid) modify(u << 1 | 1, l, r, x);
    push_up(u);
}
 
int query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) {
        return tr[u].sum;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    int res = 0;
    if (l <= mid) res = (res + query(u << 1, l, r)) % mod;
    if (r > mid) res = (res + query(u << 1 | 1, l, r)) % mod;
    return res;
}
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    cin >> m;
    while (m--) {
        int op, l, r, x;
        cin >> op;
        if (op == 2) {
            cin >> l >> r;
            cout << query(1, l, r) << endl;
        } else {
            cin >> l >> r >> x;
            modify(1, l, r, x);
        }
    }
    return 0;
}

子串翻轉回文串

知識點:字串雜湊

思路:

若字串本身就是迴文串就不用判,對於不是迴文串的,如果翻轉某個區間就是迴文串,說明左右兩端有部分相同的字元,對於相同的字元,肯定是不用翻轉的,是無效操作,所以去找第一次左右對稱位置不同字元的位置,對於找到的字串,翻轉情況只有兩種情況,要麼是固定左端點,列舉右端點去翻轉,或者固定右端點,列舉左端點去翻轉,翻轉後看字串是否迴文,就用字串雜湊來判斷

View Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned long long
#define endl "\n"
const int N = 5e5 + 10, P = 131;
const int mod = 1e9 + 7;
int n;
 
int h[N], p[N], uh[N];
char s[N];
 
int get1(int l, int r)
{
    return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;
}
 
int get2(int l, int r)
{
    return (uh[l] - uh[r + 1] * p[r - l + 1] % mod + mod) % mod;
}
 
bool check(int l, int r, int len)
{
    int x = h[len], y = uh[1];
    int dex = (h[len] - get1(l, r) * p[len - r] % mod + get2(l, r) * p[len - r] % mod + mod) % mod;
    int dey = (uh[1] - get2(l, r) * p[l - 1] % mod + get1(l, r) * p[l - 1] % mod + mod) % mod;
    return dex == dey;
}
signed main()
{
    int T;
    cin >> T;
    while (T--)
    {
        scanf("%s", s + 1);
        bool flag = 0;
        int len = strlen(s + 1);
        for (int i = 1; i <= len / 2; i++)
        {
            if (s[i] != s[len - i + 1])
            {
                flag = 0;
                break;
            }
        }
        if (flag)
        {
            cout << "Yes" << endl;
        }
        else
        {
            p[0] = 1;
            h[0] = 0;
            for (int i = 1; i <= len; i++)
            {
                h[i] = (h[i - 1] * P % mod + s[i]) % mod;
                p[i] = p[i - 1] * P % mod;
            }
            uh[len + 1] = 0;
            for (int i = len; i >= 1; i--)
            {
                uh[i] = (uh[i + 1] * P % mod + s[i]) % mod;
            }
 
            int pos = 0;
            for (int i = 1; i <= len; i++)
            {
                if (s[i] != s[len - i + 1])
                {
                    pos = i;
                    break;
                }
            }
 
            for (int i = pos; i <= len - pos + 1; i++)
            {
                int l = pos, r = i;
                int ll = i, rr = len - pos + 1;
                if (check(l, r, len) || check(ll, rr, len))
                {
                    flag = 1;
                    break;
                }
            }
            if (flag)
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        }
    }
}