跳转至

250717 模拟赛

T2 phi

给定 \(n\) 个数字 \(a_i\),请你求出

\[ \sum_{S\subseteq \{1,2,\cdots,n\}}\varphi\left(\prod_{i\in S}a_i\right) \]

\(n\le 2000,\ a_i\le 3000\)

由于欧拉函数是积性函数,因此我们可以将 \(\prod a_i\) 的所有质因子分开处理,再相乘。

注意到 \(a_i\le 3000\),考虑根号分治,大于 \(53\) 的质因子最多只有一个。注意到转移时我们只关心每个质因子是否出现过,因此考虑状压。记 \(p_{1,i}\) 表示 \(a_i\) 中小质数的集合,\(p_{2,i}\) 表示 \(a_i\) 剩下的一个大质数是多少。我们状压前 \(16\) 个质数是否在 \(\prod a_i\) 中出现过,转移时考虑将 \(p_{2}\) 相同的数字放在一起处理。容易做到 \(O(n2^{16})\)

代码
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N = 3010;
const int MOD = 998244353;

#define add(a, b) a = (a + b) % MOD;

int inv[N], w[N], sum[1 << 16], lg[1 << 16];
int pri[N], npri[N], cnt;

inline int calc(int p) { return p == 1 ? 1 : (p - 1) * inv[p] % MOD; }

int n;
int a[N];

int p1[N], p2[N];
int swp[N], tmp[N];

int f[2][1 << 16], g[1 << 16];

// #define FIO

signed main() {

    #ifdef FIO
    freopen("phi.in", "r", stdin);
    freopen("phi.out", "w", stdout);
    #endif

    inv[1] = 1;
    for(int i = 2; i < N; i++) inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;

    for(int i = 1; i < 16; i++) lg[1 << i] = i;

    for(int i = 2; i < N; i++) {
        if(!npri[i]) pri[++cnt] = i;
        for(int j = 1; j <= cnt; j++) {
            if(i * pri[j] >= N) break;
            npri[i * pri[j]] = 1;
            if(i % pri[j] == 0) break;
        }
    }

    for(int i = 1; i <= cnt; i++) w[i] = (pri[i] - 1) * inv[pri[i]] % MOD;

    pri[0] = 1;
    w[0] = 1;

    sum[0] = 1;
    for(int i = 1; i < (1 << 16); i++) {
        sum[i] = sum[i ^ (i & -i)] * w[lg[i & -i] + 1] % MOD;
    }

    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];

    for(int i = 1; i <= n; i++) {
        int x = a[i];
        for(int j = 1; j <= 16; j++) {
            if(x % pri[j] == 0) p1[i] |= (1 << j - 1);
            while(x % pri[j] == 0) x /= pri[j];
        }
        p2[i] = lower_bound(pri, pri + 1 + cnt, x) - pri;
    }

    for(int i = 1; i <= n; i++) swp[i] = i;
    sort(swp + 1, swp + 1 + n, [](int a, int b) { return p2[a] < p2[b]; } );

    for(int i = 1; i <= n; i++) tmp[i] = p1[i];
    for(int i = 1; i <= n; i++) p1[i] = tmp[swp[i]];
    for(int i = 1; i <= n; i++) tmp[i] = p2[i];
    for(int i = 1; i <= n; i++) p2[i] = tmp[swp[i]];
    for(int i = 1; i <= n; i++) tmp[i] = a[i];
    for(int i = 1; i <= n; i++) a[i] = tmp[swp[i]];

    p2[n + 1] = -1;

    int lst = 1;
    f[0][0] = 1;
    for(int i = 1; i <= n; i++) {
        for(int s = (1 << 16) - 1; s >= 0; s--) {
            add(f[lst][s | p1[i]], f[lst][s] * a[i] % MOD);
            add(f[lst][s | p1[i]], f[lst ^ 1][s] * w[p2[i]] % MOD * a[i] % MOD);
        }
        if(p2[i] != p2[i + 1]) {
            for(int s = 0; s < (1 << 16); s++) add(f[lst][s], f[lst ^ 1][s]);
            lst ^= 1;
            for(int s = 0; s < (1 << 16); s++) f[lst][s] = 0;
        }
    }

    int ans = 0;
    for(int s = 0; s < (1 << 16); s++) {
        add(ans, f[lst ^ 1][s] * sum[s] % MOD);
    }

    cout << ans << endl;

    return 0;
}

T3 rec

给你 \(n\times m\) 网格图上的 \(k\) 个矩形,请你选出一些,使得它们的交非空并且包含点数最少。求该最小值。

\(n,m\le 2000,\ k\le 10^6\)

数据结构怎么你们了。

注意到原问题就是让我们对每个格子 \((i,j)\) 求出,包含它的所有矩形的交是多大,然后取 \(\min\) 即可。由于最终的形状一定是矩形,因此只需对每个格子求出,包含它的矩形中,上边界的最小值,下边界的最大值,右边界的最小值和左边界的最大值。

注意到四个方向是完全等价的,因此只需考虑一个方向,假设当前需要解决左边界的最大值。考虑扫描线,我们希望得到所有与当前行有交的矩形在当前行的投影区间,然后将每个区间的右端点挂在左端点处,这样问题就转化为对当前行上的每个点 \(j\) 求出其左边第一个大于它的值。可以使用单调栈求解。

还有一个问题,如果对于每行我们都把所有有效的矩形处理一遍,时间复杂度将达到 \(O(nk+nm)\),不能通过。

注意到我们只关心投影区间左端点对应的右端点的最大值,并且这个最大值只会在扫描线经过矩形的上、下边界时发生变化。由于扫描线需要支持动态随机删除,因此这里对每列都维护一个 set(表示以该点为左端点,所有可能的右端点)即可。

对于当前行,我们求出每一列中 set 的最大值,然后用单调栈维护即可求出答案。时间复杂度 \(O(nm+k\log k)\)

由于出题人只想放线性做法,因此需要卡常:加上快读,把 set 换成可删堆。

代码
#include<ctype.h>
#include<bits/stl_algobase.h>
#include<cstdio>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 2010;
const int M = 1e6 + 10;
const int INF = 0x3f3f3f3f;

char endl = '\n';

namespace io {
    struct istream {
        char ch;
        bool flag;
        inline istream &operator>>(int &x) {
            flag = 0;
            while(!isdigit(ch = getchar_unlocked())) (ch == '-') && (flag = 1);
            x = ch - '0';
            while(isdigit(ch = getchar_unlocked())) x = (x << 3) + (x << 1) + ch - '0';
            flag && (x = -x);
            return *this;
        }
    } cin;
    struct ostream {
        char buf[60];
        int top;
        inline ostream() : top(0) {}
        inline ostream &operator<<(int x) {
            if(x < 0) putchar_unlocked('-'), x = -x;
            do buf[++top] = x % 10 + '0', x /= 10; while(x);
            while(top) putchar_unlocked(buf[top--]);
            return *this;
        }
        inline ostream &operator<<(char c) {
            putchar_unlocked(c);
            return *this;
        }
        inline ostream &operator<<(const char s[]) {
            for(int i = 0; s[i]; i++) putchar_unlocked(s[i]);
            return *this;
        }
    } cout;
}

using io::cin;
using io::cout;

struct Rec {
    int x1, y1, x2, y2;
} a[M];

struct Op {
    int key, p, v, w;
    inline bool operator<(const Op &b) const {
        return key < b.key;
    }
};

template<typename _Comp>
struct myHeap {
    priority_queue<int, vector<int>, _Comp> que, del;
    inline void clear() {
        while(!que.empty()) que.pop();
        while(!del.empty()) del.pop();
    }
    inline bool empty() { return que.size() == del.size(); }
    inline void push(int x) { que.push(x); }
    inline int top() {
        while(!del.empty() && que.top() == del.top()) que.pop(), del.pop();
        return que.top();
    }
    inline void erase(int x) { del.push(x); }
    inline void pop() {
        while(que.top() == del.top()) que.pop(), del.pop();
        que.pop();
    }
};

int n, m, k;

vector<Op> op;
myHeap<less<int> > st1[N];
myHeap<greater<int> > st2[N];

int sta[N], top;
int t[N];

int mxy[N][N], mny[N][N], mxx[N][N], mnx[N][N];

int main() {

    // freopen("rec.in", "r", stdin);
    // freopen("rec.out", "w", stdout);

    cin >> n >> m >> k;
    for(int i = 1; i <= k; i++) cin >> a[i].x1 >> a[i].y1 >> a[i].x2 >> a[i].y2;

    op.clear();
    for(int i = 1; i <= m; i++) st2[i].clear();
    for(int i = 1; i <= k; i++) {
        op.push_back({a[i].x1, a[i].y1, a[i].y2, 1});
        op.push_back({a[i].x2 + 1, a[i].y1, a[i].y2, -1});
    }
    sort(op.begin(), op.end());
    for(int i = 1, j = 0; i <= n; i++) {
        while(j < op.size() && op[j].key == i) {
            if(op[j].w == 1) st2[op[j].p].push(op[j].v);
            else st2[op[j].p].erase(op[j].v);
            ++j;
        }
        top = 0;
        sta[0] = INF;
        for(int j = 1; j <= m; j++) {
            while(top && t[sta[top]] < j) --top;
            if(st2[j].empty()) {
                mxy[i][j] = sta[top];
                continue;
            }
            t[j] = st2[j].top();
            while(top && t[sta[top]] <= t[j]) --top;
            sta[++top] = j;
            mxy[i][j] = j;
        }
    }

    op.clear();
    for(int i = 1; i <= m; i++) st1[i].clear();
    for(int i = 1; i <= k; i++) {
        op.push_back({a[i].x1, a[i].y2, a[i].y1, 1});
        op.push_back({a[i].x2 + 1, a[i].y2, a[i].y1, -1});
    }
    sort(op.begin(), op.end());
    for(int i = 1, j = 0; i <= n; i++) {
        while(j < op.size() && op[j].key == i) {
            if(op[j].w == 1) st1[op[j].p].push(op[j].v);
            else st1[op[j].p].erase(op[j].v);
            ++j;
        }
        top = 0;
        sta[0] = 0;
        for(int j = m; j >= 1; j--) {
            while(top && t[sta[top]] > j) --top;
            if(st1[j].empty()) {
                mny[i][j] = sta[top];
                continue;
            }
            t[j] = st1[j].top();
            while(top && t[sta[top]] >= t[j]) --top;
            sta[++top] = j;
            mny[i][j] = j;
        }
    }

    op.clear();
    for(int i = 1; i <= n; i++) st2[i].clear();
    for(int i = 1; i <= k; i++) {
        op.push_back({a[i].y1, a[i].x1, a[i].x2, 1});
        op.push_back({a[i].y2 + 1, a[i].x1, a[i].x2, -1});
    }
    sort(op.begin(), op.end());
    for(int i = 1, j = 0; i <= m; i++) {
        while(j < op.size() && op[j].key == i) {
            if(op[j].w == 1) st2[op[j].p].push(op[j].v);
            else st2[op[j].p].erase(op[j].v);
            ++j;
        }
        top = 0;
        sta[top] = INF;
        for(int j = 1; j <= n; j++) {
            while(top && t[sta[top]] < j) --top;
            if(st2[j].empty()) {
                mxx[j][i] = sta[top];
                continue;
            }
            t[j] = st2[j].top();
            while(top && t[sta[top]] <= t[j]) --top;
            sta[++top] = j;
            mxx[j][i] = j;
        }
    }

    op.clear();
    for(int i = 1; i <= n; i++) st1[i].clear();
    for(int i = 1; i <= k; i++) {
        op.push_back({a[i].y1, a[i].x2, a[i].x1, 1});
        op.push_back({a[i].y2 + 1, a[i].x2, a[i].x1, -1});
    }
    sort(op.begin(), op.end());
    for(int i = 1, j = 0; i <= m; i++) {
        while(j < op.size() && op[j].key == i) {
            if(op[j].w == 1) st1[op[j].p].push(op[j].v);
            else st1[op[j].p].erase(op[j].v);
            ++j;
        }
        top = 0;
        sta[top] = 0;
        for(int j = n; j >= 1; j--) {
            while(top && t[sta[top]] > j) --top;
            if(st1[j].empty()) {
                mnx[j][i] = sta[top];
                continue;
            }
            t[j] = st1[j].top();
            while(top && t[sta[top]] >= t[j]) --top;
            sta[++top] = j;
            mnx[j][i] = j;
        }
    }

    int ans = INF;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(mxx[i][j] <= mnx[i][j] && mxy[i][j] <= mny[i][j]) {
                ans = min(ans, (mnx[i][j] - mxx[i][j] + 1) * (mny[i][j] - mxy[i][j] + 1));
            }
        }
    }

    cout << ans << '\n';

    return 0;
}

T4 cip

\(n\) 天时间,第 \(i\) 天可以买到质量为 \(a_i\) 的原料,原料每经过一天,质量都会下降 \(1\)。当满足 \(a'\ge b_i\) 时,你可以选择在第 \(i\) 天对原料进行加工,产生 \(a'c_i\) 的价值,并消耗掉原料。

你会在第一天,以及每次加工之后的第二天(最后一天除外)开始时(加工之前)购买当天的原料一件。第 \(n\) 天结束时,你手上不能有原料。

求最大价值。

(保证 \(c_i\) 单调不降,该条件在单 \(\log\) 做法中使用)

\(n\le 2\times 10^5,\ 0\le a_i,c_i\le 5\times 10^4, -n\le b_i\le 5\times 10^4\)

\(f_i\) 表示前 \(i\) 天的答案,钦定第 \(i\) 天进行了加工。不难写出转移:

\[ f_i\gets f_j+\big(a_{j+1}-(i-j-1)\big)c_i\quad \text{for }j<i\wedge a_{j+1}-(i-j-1)\ge b_i \]

为了简化式子,执行:

\[ a_i\gets a_{i+1}+i+1\\ b_i\gets b_i+i \]

这样,转移式就变为

\[ f_i\gets f_j+a_jc_i-ic_i\quad \text{for }j<i\wedge a_j\ge b_i \]

如果没有 \(b_i\) 的约束,那么使用李超线段树即可。考虑用 cdq 处理掉一维限制。由于 cdq 优化 dp 需要保证本轮分治的转移过程中 \(j\) 的 dp 值已经计算完毕,因此不能分治 \(a_j,b_i\) 维度(不能保证 \(j\) 在转移 \(i\)\(j\) 已经是最新,因为 \(b_j\) 可能也在左半区间)。

这里我们将所有 \(a_i\)\(b_i\) 分成两个点,一个转移别人,一个接受转移。我们按 \(a_j\)\(b_i\) 排序后,对下标维度进行分治。现在我们无须考虑下标维度,问题转化为一个一维 dp 问题。由于加点的斜率不具有单调性,因此需要使用李超线段树。

cdq 只能维护偏序

在进行 cdq 分治的过程中,必须保证 \([l,mid]\)\([mid+1,r]\) 的贡献是单向的。尤其是像本题一样,一个 \(f_i\) 具有两个关键字,要注意这两个关键字之间也是会产生贡献的。

代码
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define ld long double
using namespace std;
const int N = 2e5 + 10;
const int V = 5e4 + 10 + N;
const int INF = 0x3f3f3f3f3f3f3f3f;

inline void gmax(int &a, int b) { a = max(a, b); }

struct Op {
    int tp, id, w;
    inline bool operator<(const Op &b) const {
        if(w != b.w) return w > b.w;
        return tp > b.tp;
    }
};

int n;
int a[N], c[N], b[N];

int f[N];

namespace SegT {
    struct Line {
        int k, b;
        inline void clear(int _p1 = 0, int _p2 = 0) { k = _p1, b = _p2; }
    } pool[N];
    int nn;
    int s[4 * (V + 5)];
    int t[4 * (V + 5)], cur;
    inline int lc(int x) { return x << 1; }
    inline int rc(int x) { return x << 1 | 1; }
    inline void clear() {
        nn = 0;
        cur ^= 1;
    }
    inline void flush(int p) {
        if(t[p] != cur) {
            t[p] = cur;
            s[p] = 0;
        }
    }
    inline int calc(int id, int x) {
        if(!id) return -INF;
        return pool[id].b + pool[id].k * x;
    }
    void move_tag(int p, int l, int r, int id) {
        flush(p);
        if(!s[p]) {
            s[p] = id;
            return;
        }
        int mid = (l + r) >> 1;
        if(calc(id, mid) > calc(s[p], mid)) swap(s[p], id);
        if(calc(id, l) > calc(s[p], l)) move_tag(lc(p), l, mid, id);
        if(calc(id, r) > calc(s[p], r)) move_tag(rc(p), mid + 1, r, id);
    }
    int query(int p, int l, int r, int q) {
        flush(p);
        if(l == r) return calc(s[p], q);
        int mid = (l + r) >> 1;
        if(q <= mid) return max(calc(s[p], q), query(lc(p), l, mid, q));
        else return max(calc(s[p], q), query(rc(p), mid + 1, r, q));
    }
    void insert(Line x) {
        pool[++nn] = x;
        move_tag(1, 1, V, nn);
    }
    int query(int x) { return query(1, 1, V, x); }
}

void cdq(int l, int r, vector<Op> &op) {
    if(op.empty()) return;
    if(l == r) return;
    int mid = (l + r) >> 1;
    vector<Op> lop, rop;
    for(Op &o : op) {
        if(o.id <= mid) lop.push_back(o);
        else rop.push_back(o);
    }
    cdq(l, mid, lop);
    SegT::clear();
    for(Op &o : op) {
        if(o.id <= mid && o.tp == 1) {
            SegT::insert({a[o.id], f[o.id]});
        }
        if(o.id > mid && o.tp == 0) {
            gmax(f[o.id], SegT::query(c[o.id]) - o.id * c[o.id]);
        }
    }
    cdq(mid + 1, r, rop);
}

vector<Op> op;

// #define FIO

signed main() {

    #ifdef FIO
    freopen("cip.in", "r", stdin);
    freopen("cip.out", "w", stdout);
    #endif

    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) cin >> c[i];
    for(int i = 1; i <= n; i++) cin >> b[i];

    for(int i = 0; i < n; i++) a[i] = a[i + 1] + i + 1;
    for(int i = 1; i <= n; i++) b[i] = b[i] + i;

    for(int i = 0; i < n; i++) {
        op.push_back({1, i, a[i]});
        op.push_back({0, i + 1, b[i + 1]});
    }
    sort(op.begin(), op.end());

    for(int i = 1; i <= n; i++) f[i] = -INF;

    cdq(0, n, op);

    if(f[n] == -INF) cout << "Impossible\n";
    else cout << f[n] << '\n';

    return 0;
}