Loading

2024CCPC 女生专场

D - 优秀的拆分

Description

有一个长度为 \(N\) 的排列 \(P\),将 \(P\) 拆成 \(A\)\(B\),求 \(A\) 的最长上升子序列的长度与 \(B\) 的最长下降子序列的长度相加的最大值。

数据范围:\(1\leqslant N\leqslant 2×10^5\).

Solution

首先一个很重要的结论是,设 LIS/LDS 分别为序列的最长上升/下降子序列长度,答案一定是 LIS+LDS 或者 LIS+LDS-1.

这是因为序列的 LIS 和 LDS 最多重复一个数字,因为要求 严格 上升/下降。

于是只用判断:无论怎么选,上升子序列和下降子序列必定存在一个交点。

使用二元组 \((len,cnt)\) 维护数据,表示最长上升/下降子序列长度为 \(len\),选择子序列方案数为 \(cnt\).

使用 \(f_{0/1,i}\) 表示以 \(i\) 为结尾(\(0\))/开头(\(1\))的最长上升子序列,而 \(g_{0/1,i}\) 表示最长下降子序列。这两个数组均需维护上述二元组。

最后,计算出 \(c_0\) 为全局 LIS 的方案数,\(c_1\) 为全局 LDS 的方案数。那么 \(c_0·c_1\) 表示 LIS 和 LDS 的所有可能匹配。

接着计算 \(h_{0/1,i}\) 表示包含 \(i\) 的最长上升(\(0\))/下降(\(1\))子序列的方案数。于是我们只用比较 \(\sum h_{0,i}\cdot h_{1,i}\)\(c_0·c_1\) 是否相等,如果相等则无论怎么选,上升子序列和下降子序列必定存在一个交点。

这题非常坑爹的一个点是,方案数这一维会爆 long long,不能直接比较,需要自行设定一个模数计算。我用了 \(10^9+7\).

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; bool f=0; char s;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <set>
# include <cmath>
# include <vector>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;

const int MAXN = 2e5+5;
const int mod = 1e9+7;

int n, a[MAXN];
ll h[2][MAXN];
struct node {
    int len; ll cnt;
} c[2][MAXN], f[2][MAXN], g[2][MAXN];

int lowbit(int x) { return x&-x; }

node merge(node x, node y) {
    if(x.len ^ y.len)
        return (x.len<y.len)? y: x;
    return (node){x.len, (x.cnt+y.cnt)%mod};
}

void add0(int x, node k) {
    for(int i=x; i<=n; i+=lowbit(i))
        c[0][i] = merge(c[0][i], k);
}

void add1(int x, node k) {
    for(int i=x; i>0; i-=lowbit(i))
        c[1][i] = merge(c[1][i], k);
}

node ask0(int x) {
    node ret = (node){0, 1};
    for(int i=x; i>0; i-=lowbit(i))
        ret = merge(ret, c[0][i]);
    return ret;
}

node ask1(int x) {
    node ret = (node){0, 1};
    for(int i=x; i<=n; i+=lowbit(i))
        ret = merge(ret, c[1][i]);
    return ret;
}

void reset() {
    for(int i=1; i<=n; ++i)
        c[0][i] = (node){0, 0},
        c[1][i] = (node){0, 0};
}

void noSananoLife() {
    n = read(9);
    for(int i=1; i<=n; ++i) a[i]=read(9);
    reset();
    for(int i=1; i<=n; ++i) {
        node ret0 = ask0(a[i]-1);
        node ret1 = ask1(a[i]+1);
        f[0][i] = (node){ret0.len+1, ret0.cnt};
        g[0][i] = (node){ret1.len+1, ret1.cnt};
        add0(a[i], f[0][i]);
        add1(a[i], g[0][i]);
    }
    reset();
    for(int i=n; i>=1; --i) {
        node ret0 = ask0(a[i]-1);
        node ret1 = ask1(a[i]+1);
        g[1][i] = (node){ret0.len+1, ret0.cnt};
        f[1][i] = (node){ret1.len+1, ret1.cnt};
        add0(a[i], g[1][i]);
        add1(a[i], f[1][i]);
    }
    int LIS=0, LDS=0;
    for(int i=1; i<=n; ++i)
        LIS = max(LIS, f[0][i].len),
        LDS = max(LDS, g[0][i].len);
    ll c0=0, c1=0;
    for(int i=1; i<=n; ++i)
        c0 += (LIS==f[0][i].len)? f[0][i].cnt: 0,
        c1 += (LDS==g[0][i].len)? g[0][i].cnt: 0;
    c0 %= mod, c1 %= mod;
    for(int i=1; i<=n; ++i) {
        if(f[0][i].len+f[1][i].len-1 == LIS)
            h[0][i] = f[0][i].cnt%mod*(f[1][i].cnt%mod)%mod;
        else h[0][i] = 0;
        if(g[0][i].len+g[1][i].len-1 == LDS)
            h[1][i] = g[0][i].cnt%mod*(g[1][i].cnt%mod)%mod;
        else h[1][i] = 0;
    }
    ll all = 0;
    for(int i=1; i<=n; ++i)
        all = (all+h[0][i]*h[1][i]%mod)%mod;
    print(LIS+LDS-(all==c0*c1%mod), '\n');
}

int main() {
    for(int T=read(9); T; --T)
        noSananoLife();
    return 0;
}   



J - 最大公因数的平方和

Solution

这道题最重要的地方就是对 \(\sum_{i=l}^r\sum_{j=L}^R \gcd^2(a_i,b_j)\) 的转化。以后可以对由 \(\gcd(x,y)\) 组成的多项式更敏感一点,都可以利用类欧拉反演进行构造。具体对于这道题,我们构造 \(f(i)\) 满足 \(\sum_{d|n}f(d)=n^2\),于是答案转化为:\(\sum_{d=1}^n\sum_{i=l}^r\sum_{j=L}^R f(d)[d|a_i][d|b_j]\).

注意到 \(a,b\) 都是排列,我们考虑根号分治。

  • 对于 \(d<B\) 的因数。直接暴力枚举因子,用前缀和计算。空间复杂度 \(S(2nB)\),时间复杂度 \(\mathcal O((n+q)B)\).

  • 对于 \(d>B\) 的因数。对询问进行差分,分成 \((r,R),(r,L-1),(l-1,R),(l-1,r-1)\) 四个询问点。枚举 \(d\) 的倍数 \(d_A,d_B\),它对 \(pa_{d_A}\leqslant r,pb_{d_B}\leqslant R\) 的询问有 \(f(d)\) 的贡献。其中 \(pa_i,pb_i\) 分别表示 \(i\) 在序列 \(a,b\) 中的位置。使用二维数点即可解决。

    考虑点数的级别,是 \(\sum_{d=B}^n (n/d)^2\)。这并不好算,考虑到它的上限是积分 \(\int_{B}^n(n/x)^2\text{d}x=n^{3/2}-n\),所以点数级别是 \(\mathcal O(n^{3/2})\).

    插入/查询一个点的复杂度为 \(\mathcal O(\log n)\),所以复杂度是 \(\mathcal O(n^{3/2}\log n+4q\log n)\)。据题解可以利用根号平衡做到插入 \(\mathcal O(1)\),查询 \(\mathcal O(B)\),但是我觉得有点麻烦,反正这么写复杂度也没爆,于是就不管啦 (●'◡'●)

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; bool f=0; char s;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <set>
# include <cmath>
# include <queue>
# include <vector>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int,ll> pii;

const int MAXN = 1e5+5;
const ll mod = (1ll<<32);

ll f[MAXN], ans[MAXN], c[MAXN];
int pa[MAXN], pb[MAXN];
int n, a[MAXN], b[MAXN], q, B, cnt[320][MAXN][2];
struct node {
    int l, r, L, R;
} qr[MAXN];
struct Node {
    int yy, id, op;
    Node() {}
    Node(int Y, int ID, int OP): yy(Y), id(ID), op(OP) {}
};
vector <pii> modi[MAXN];
vector <Node> Qr[MAXN];

void getFunction() {
    f[1] = 1;
    for(int i=2; i<=n; ++i) {
        int Q = sqrt(i); ll sum=0;
        for(int j=1; j<=Q; ++j)
            if(i%j==0) sum += f[j]+f[i/j];
        if(Q*Q==i) sum -= f[Q];
        f[i] = (1ll*i*i-sum)%mod;
    } // 需要注意 sqrt(i)*sqrt(i)=i 的情况
}

void divide() {
    for(int i=1; i<=B; ++i)
        for(int j=1; j<=n; ++j)
            cnt[i][j][0] = cnt[i][j-1][0]+(a[j]%i==0),
            cnt[i][j][1] = cnt[i][j-1][1]+(b[j]%i==0);
}

int lowbit(int x) { return x&-x; }

void add(int x, ll k) {
    for(; x<=n; x+=lowbit(x))
        c[x] = (c[x]+k)%mod;
}

ll ask(int x) {
    ll ret = 0;
    for(; x>0; x-=lowbit(x))
        ret = (ret+c[x])%mod;
    return ret;
}

int main() {
    n = read(9); B = sqrt(n);
    for(int i=1; i<=n; ++i) 
        pa[a[i]=read(9)] = i;
    for(int i=1; i<=n; ++i) 
        pb[b[i]=read(9)] = i;
    q = read(9);
    getFunction();
    divide();
    for(int i=1; i<=q; ++i) {
        qr[i].l=read(9), qr[i].r=read(9);
        qr[i].L=read(9), qr[i].R=read(9);
        for(int d=1; d<=B; ++d) {
            int c1 = cnt[d][qr[i].r][0]-cnt[d][qr[i].l-1][0];
            int c2 = cnt[d][qr[i].R][1]-cnt[d][qr[i].L-1][1];
            ans[i] = (ans[i]+1ll*c1*c2%mod*f[d]%mod)%mod;
        }
    }
    for(int i=1; i<=q; ++i) {
        Qr[qr[i].r].push_back(Node(qr[i].R, i, 1));
        Qr[qr[i].r].push_back(Node(qr[i].L-1, i, -1));
        Qr[qr[i].l-1].push_back(Node(qr[i].R, i, -1));
        Qr[qr[i].l-1].push_back(Node(qr[i].L-1, i, 1));
    }
    for(int d=B+1; d<=n; ++d) 
        for(int da=d; da<=n; da+=d)
            for(int db=d; db<=n; db+=d) {
                modi[pa[da]].push_back(make_pair(pb[db], f[d]));
            }
    for(int x=1; x<=n; ++x) {
        for(auto it: modi[x]) {
            add(it.first, it.second);
        }
        for(auto it: Qr[x]) {
            ll tmp = ask(it.yy);
            ans[it.id] = (ans[it.id]+tmp*it.op+mod)%mod;
        }
    }
    for(int i=1; i<=q; ++i) print(ans[i], '\n');
    return 0;
}



posted @ 2025-03-30 22:20  ShyGray  阅读(86)  评论(0)    收藏  举报