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;
}

浙公网安备 33010602011771号