题解 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\),\(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\)的最小的左端点减一。于是就有了状态转移方程:
终于可以 DP 了,稍微把问题取个反,令 \(f_i\) 表示前 \(i\) 个数合并后剩下的最少的数字。
考虑有多少个 \(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;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-T273523.html