题解 LGT273523【红裤衩】

posted on 2022-09-17 12:16:06 | under 题解 | source
题目来源:International Zhautykov Olympiad 2022, Computer Science, Day 1, Problem 1.

QOJ 链接:https://qoj.ac/problem/3575

problem

一个长为 \(n\) 的整数数列 \(a\),一个数 \(a_i\) 能被删除当且仅当

\[a_i=\frac{a_{i-1}+a_{i+1}}{2} \]

求最多能删除多少个 \(a_i\)\(a_i\) 删除后后面的元素向前补位。多测,\(1\leq n\leq 10^5,1\leq a_i\leq 10^9\)

solution

换题:对于 \(a\) 的差分数组 \(d\),每次能将相邻的两个相同的 \(d_i=d_{i+1}\) 合并成 \(d_i+d_{i+1}\),求合并后 \(d\) 的最小长度。

证明:令 \(d_i=a_{i+1}-a_i\)

\[\begin{aligned} a_i&=\frac{a_{i-1}+a_{i+1}}{2}\\ 2a_i&=a_{i-1}+a_{i+1}\\ a_i-a_{i-1}&=a_{i+1}-a_i\\ d_i&=d_{i+1}\\ \end{aligned}\]

有一些显然的结论:负数只能和负数合并,正数只能和正数合并,连续的 \(0\) 直接合并成一个 \(0\)。因此对符号分一次段。

以连续的正数为例。首先计算 \(dit_i=\log(\operatorname{lowbit}(d_i)),bas_i=d_i\div 2^{dit_i}\)。这样,一个 \(d_i\) 可以表示为 \(bas_i\times 2^{dit_i}\)。与另一个 \(bas_i\times 2^{dit_i}\) 合并后,就变成了 \(bas_i\times 2^{dit_i+1}\)。对于不同的 \(bas\),一定不能合并。看到 \(2^j\),貌似可以倍增!

再对 \(bas_i\) 分一次段,对于连续的 \(bas\),令 \(g_{i,j}\) 表示,以 \(i\) 为右端点合并出 \(bas\times 2^j\)的最小的左端点减一。于是就有了状态转移方程:

\[g_{i,j}=\begin{cases} i-1&(j=dit_i),\\ g_{g_{i,j-1},j-1}&(j>dit_i\land bas_{g_{i,j-1}}=bas_i\land g_{i,j-1} \text{合法}\land g_{g_{i,j-1},j-1}\text{合法}). \end{cases}\]

终于可以 DP 了,稍微把问题取个反,令 \(f_i\) 表示前 \(i\) 个数合并后剩下的最少的数字。

\[f_i=\min_{j}(f_{g_{i,j}}+1). \]

考虑有多少个 \(j\)?由于 \(j\) 在指数上,因此 \(j\) 只有 \(O(\log V)\) 种取值,总复杂度 \(O(n\log V)\)

code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int n;
LL a[300010],b[300010],bas[300010];
int g[300010][35],f[300010],dit[300010];
int getdiff(){
	for(int i=1;i<n;i++) b[i]=a[i+1]-a[i];
	int cnt=0;
	a[++cnt]=b[1];
	for(int i=2;i<n;i++) if(b[i]||a[cnt]) a[++cnt]=b[i];
	return cnt;
}
void getg(){
	//g(i,j) 表示以 i 为右端点合并出 2^j 的左端点 -1 
	//-1 为不合法 
	memset(g,-1,sizeof g);
	for(int i=1;i<=n;i++){
		g[i][dit[i]]=i-1;
		if(bas[i]) for(int j=dit[i]+1;j<=34;j++){
			if(~g[i][j-1]&&~g[g[i][j-1]][j-1]&&bas[g[i][j-1]]==bas[i]){
				g[i][j]=g[g[i][j-1]][j-1];
			}
		}
	}
} 
int getf(){
	memset(f,0x3f,sizeof f),f[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=34;j++){
			if(~g[i][j]) f[i]=min(f[i],f[g[i][j]]+1);
		}
	}
	return f[n];
}
int mian(){
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	if(n<=2) return printf("%d\n",n),0;
	n=getdiff();
	for(int i=1;i<=n;i++){
		if(a[i]){
			int s=a[i]<0?-1:1;
			for(a[i]*=s;a[i]%2==0;a[i]>>=1) dit[i]++;
			bas[i]=a[i]*s;
		}
//		printf("(%d,%d)\n",bas[i],dit[i]);
	}
	getg();
	printf("%d\n",getf()+1);
	return 0;
}
void reset(){
	memset(bas,0,sizeof bas);
	memset(dit,0,sizeof dit);
} 
int main(){
	for(scanf("%*d");~scanf("%d",&n);reset(),mian());
	return 0;
}
posted @ 2022-11-15 17:40  caijianhong  阅读(38)  评论(0编辑  收藏  举报