“卓见杯”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;
        }
    }
}
posted @ 2022-04-06 14:39  Wraith_G  阅读(466)  评论(0)    收藏  举报
// //