CF1946F Nobody is needed

毒瘤卡常,来写题解了。

设计 dp 状态 \(f_{l, r}\) 表示区间 \([l, r]\) 中整除序列的个数,可知 dp 转移为 \(f_{l, r}=\sum_{l \leq k < r} [a_k | a_r] f_{l, k}\),这样朴素 dp 的时间复杂度是 \(\mathcal{O}(qn^2)\) 的,卡常都过不了。而我们发现转移的 dp 状态第一维都是相同的,自然想到滚动第一维 \(l\)

于是我们运用常用技巧,从 \(n\)\(1\) 枚举 \(l\),对 \(l\) 分组进行离线查询,我们可以运用线段树或者树状数组之类的做到 \(\mathcal{O}(\log n)\) 单次询问。考虑从以 \(n\)\(1\) 的顺序枚举 \(l\),我们希望可以以较优的时间复杂度将\(l+1\) 为左端点时的 \(f_{l+1 \cdots n}\) 转移为\(l\) 为左端点时的 \(f_{l \cdots n}\)

此时 dp 转移上无法较显然的进一步优化,我们挖掘题目性质。我们发现由于加入了 \(a_l\) 产生了以 \(a_l\) 为第一个的整除序列使 \(f\) 值改变:假设一个整除序列为 \(a_l, \cdots, a_r\),则 \(f_{r \cdots n} \leftarrow f_{r \cdots n} + 1\)。若我们对 \(f\) 进行差分得到 \(h\),那么该整除序列相当于使 \(h_r \leftarrow h_r + 1\)。而由于 \(a_l | \cdots | a_r\)\(a_r\)\(a_l\) 的倍数,我们发现:对于排列 \(a\)至多只有不超过 \(\dfrac n{a_l}\)\(h_i\) 会改变,而且每个 \(a_l\) 均摊仅会使 \(\mathcal{O}(\log n)\)\(h_i\) 改变!

于是我们再维护一个 \(g_i\) 来表示每次 \(h_i\)增加量(第二次差分),我们发现:

\[g_i = \begin{cases} 0 & a_i \not| a_l\\ 1 & i=l\\ \sum_{a_j | a_i, j < i} g_j & a_i | a_l,i \neq l \end{cases}\]

由于非零的 \(g\) 较少,于是我们可以 \(\mathcal{O}(\log^2 n)\) 枚举 \(g_i\)\(g_j\) 进行转移。处理出 \(g\) 值后,使用线段树进行 \(\mathcal{O}(\log n)\)\(\mathcal{O}(\log n)\) 的修改操作,再维护 \(\sum \sum g_i\) \(\mathcal{O}(\log n)\) 查询,总时间复杂度 \(\mathcal{O}(n \log^2 n+q \log n)\),不卡常不能过(也有可能是因为我程序天生常数大)。

接下来是一点奇怪的优化?卡常?

  1. 用树状数组而非线段树,我不信有人能写线段树过了这题(

  2. 预处理加入 \(a_l\) 时会有哪些 \(h_i\) 会变化,即提前枚举并存储好在 \(a_l\) 右侧的 \(a_l\) 的倍数。

代码:

int T,n,q,a[N],b[N];
vector<pii> query[N];
vector<int> nxt[N];
ll t[N],g[N],ans[N];

inline int lowbit(int x) {return x&-x;}
inline void modify(int p,ll x) {
	for(;p<=n;p+=lowbit(p)) t[p]+=x;
	return ;
}
inline ll ask(int p) {
	ll tmp=0;
	for(;p;p-=lowbit(p)) tmp+=t[p];
	return tmp;
}

int main() {
	read(T);
	while(T--) {
		read(n,q);
		for(int i=1;i<=n;i++) {
			read(a[i]),t[i]=b[i]=0;
			query[i].clear(),nxt[i].clear();
		}
		for(int i=n;i>=1;i--) {
			b[a[i]]=i;
			for(int j=a[i];j<=n;j+=a[i])
				if(b[j]) nxt[i].pb(b[j]);
		}
		for(int i=1,l,r;i<=q;i++) read(l,r),query[l].eb(r,i);
		for(int l=n;l>=1;l--) {
			g[l]=1;
			for(int x:nxt[l]) {
				if(x<l) continue ;
				modify(x,g[x]);
				for(int y:nxt[x]) {
					if(y==x) continue ;
					g[y]+=g[x];
				}
				g[x]=0;
			}
			for(auto [r,id]:query[l]) ans[id]=ask(r);
			// cout<<"Ciallo ~"<<endl;
		}
		for(int i=1;i<=q;i++) write(ans[i]),_S;
		_E;
	}
	return 0;
}
posted @ 2025-07-23 19:23  LinkCatTree  阅读(30)  评论(0)    收藏  举报