题解:P17759 「RSOI-3H」数组划分 / partition
截止到 26 年 11 月 6 日,本题最短解。
首先给出一个公式:
这个在莫比乌斯反演的题里也会用到,不过我是区不会。
首先看为什么,设
然后转化一下就是
为了简记,以下的
然后就是你写这题的时候,暴力肯定写了吧,设
那么暴力肯定就是枚举
发现这个解决了
这个暴力很难直接优化,虽然
我们其实可以运用单调栈,这样就能快速求出
具体实现等下说。
同样困难的还有 gcd 的刻画,如果你站在一个不知道莫反的人的视角,你会发现直接求质因数貌似更自然,对吧。
但是,这样可能会重,假设一个
容斥系数当然就是
现在你一定是蒙的,我们来总结一下我们实现了什么,莫比乌斯反演使得我们可以直接对着
得出新的转移式为
这个比原式好看多了,但是其实还要考虑
既然对着单调栈做,那就先看看弹出吧,这里声明一下,对于
弹出
然后出现了问题,这个质因子一定要在
这个怎么办?我们直接再开
当前枚举到一个
说得简单但是实际实现还是有细节的,对于每个
一种想法是把这个开成线段树,但是我们教练测了,大概率被卡常,用一些神秘的科技能卡过。
你发现这个不是修改都是前后缀吗,直接用 vector 搞前缀和就行了,至于位置的话,我们没有中间删除,只有两侧删除(前端删除用一个
概括总结一下删除过程,那一步不会可以反复看,首先枚举
维护
最后弹完了该加入了,这个类似,也是要枚举
这里不要忘了用同样的方法更新答案,
莫比乌斯函数是积性函数(我去不早说),直接筛。
至此我们做完了这道题,时间复杂度
本代码由人类生成,仅供参考:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010,maxn = 200000;
const int mod = 998244353;
int n,k;
int a[N],lim[N];
int vis[N],mul[N],primes[N],idx = 0;
int dp[N];
vector<int> e[N];
int pos;
//求莫比乌斯函数
void get_primes(){
mul[1] = 1;
for(int i = 2;i<=maxn;i++){
if(!vis[i]){
primes[++idx] = i;
mul[i] = -1;
}
for(int j = 1;j<=idx;j++){
if(i * primes[j] > maxn)break;
vis[i * primes[j]] = 1;
if(i % primes[j] == 0){
mul[i * primes[j]] = 0;
break;
}else{
mul[i * primes[j]] = -mul[i];
}
}
}
}
struct node{int l,r,sum;};
vector<int> stmx,stmn;//max/min位置的栈
vector<node> mx[N],mn[N];//对于每个d,合法区间存成栈,可能存在[1,3],[5,6]这种情况,不连续,所以空间63n
int sum[N];
//二分用的
bool cmp(int a,node b){return a < b.l;}
//询问前缀和
int query(const vector<node> &tr,int p){
int pos = upper_bound(tr.begin() , tr.end() , p , cmp) - tr.begin() - 1;
if(pos < 0)return 0;
int res = 0;
if(pos != 0)res = tr[pos - 1].sum;
res = (res + sum[min(p , tr[pos].r)] - sum[tr[pos].l - 1] + mod) % mod;
return res;
}
//询问一段贡献和
int ask(int typ,int d,int l,int r){
if(max(l , pos) > r)return 0;
if(typ == 1)return (query(mx[d] , r) - query(mx[d] , max(pos - 1 , l - 1)) + mod) % mod;
else return (query(mn[d] , r) - query(mn[d] , max(pos - 1 , l - 1)) + mod) % mod;
}
signed main(){
get_primes();
//预处理d
for(int i = 2;i<=maxn;i++){
if(mul[i] == 0)continue;
for(int j = i;j<=maxn;j += i){
e[j].push_back(i);
}
}
cin>>n>>k;
for(int i = 1;i<=n;i++)cin>>a[i];
//处理lim,其实就是前缀max
for(int i = 1;i<=k;i++){
int u,v;
cin>>u>>v;
lim[v] = max(lim[v] , u);
}
for(int i = 1;i<=n;i++)lim[i] = max(lim[i] , lim[i - 1]);
dp[0] = 1;
pos = 1;
int ans = 0;
for(int i = 1;i<=n;i++){
//对dp做前缀和
sum[i] = (sum[i - 1] + dp[i - 1]) % mod;
//处理k的限制,即移动pos
while(pos <= lim[i]){
int p1 = *lower_bound(stmx.begin() , stmx.end() , pos);
int p2 = *lower_bound(stmn.begin() , stmn.end() , pos);
if(__gcd(a[p1] , a[p2]) != 1)ans = (ans - dp[pos - 1] + mod) % mod;
pos++;
}
//删除max
while(stmx.size() && a[i] >= a[stmx.back()]){
int p = stmx.back();
stmx.pop_back();
for(auto d : e[a[p]]){
node tmp = mx[d].back();
mx[d].pop_back();
int res = ask(0 , d , tmp.l , tmp.r);
ans = ((ans + mul[d] * res) % mod + mod) % mod;
}
}
//插入max
int h = 0;
if(stmx.size())h = stmx.back();
stmx.push_back(i);
for(auto d : e[a[i]]){
int res = 0;
if(mx[d].size())res = mx[d].back().sum;
mx[d].push_back({h + 1 , i , (res + sum[i] - sum[h] + mod) % mod});
ans = ((ans - mul[d] * ask(0 , d , h + 1 , i)) % mod + mod) % mod;
}
//删除min
while(stmn.size() && a[i] <= a[stmn.back()]){
int p = stmn.back();
stmn.pop_back();
for(auto d : e[a[p]]){
node tmp = mn[d].back();
mn[d].pop_back();
int res = ask(1 , d , tmp.l , tmp.r);
ans = ((ans + mul[d] * res) % mod + mod) % mod;
}
}
//插入min
h = 0;
if(stmn.size())h = stmn.back();
stmn.push_back(i);
for(auto d : e[a[i]]){
int res = 0;
if(mn[d].size())res = mn[d].back().sum;
mn[d].push_back({h + 1 , i , (res + sum[i] - sum[h] + mod) % mod});
ans = ((ans - mul[d] * ask(1 , d , h + 1 , i)) % mod + mod) % mod;
}
//ans就是当前的dp值
dp[i] = ans;
}
cout<<dp[n]<<"\n";
return 0;
}
快升黑吧太困难了。
如有笔误请指出,感谢。