Solution - CF1497E2

\(\color{9D3DCF}\text{CF1497E2 Square-free division (hard version)}\)

\(\color{000000}\text{1497 E2. Square-Free Division (hard version)}\)

首先发现题目中要求使得两个数之积都不是完全平方数,那么我们可以先预处理 \(a_i\),将 \(a_i\) 中的构成平方的因子先除掉(即将 \(a_i\) 中次数大于 \(1\) 的质因子的次数 \(\bmod\ 2\)),显然去除每个数的平方因子后,两个数相乘为完全平方数当且仅当它们相等。

看到 \(n\leq 2\times 10^5,k\leq 20\),自然想到 \(O(nk)\)\(O(nk^2)\) 的 DP。

\(f_{i,j}\) 表示前 \(i\) 个数中修改 \(j\) 个划分的最小段数,\(g(i,j)\) 表示表示以 \(i\)终点,在 \(i\) 所在的划分段内修改了 \(j\) 次往回最远能扩充到的点。

则有

\[f_{i,j}=\min^j_{x=0}\{f_{g(i,x)-1,j-x}+1\} \]

时间复杂度 \(O(nk^2)\)

那么如何求 \(g\) 呢?

我们发现在 \(g(i,j)\) 中,对于一个确定的 \(j\),当 \(i\) 增大时,\(g(i,j)\) 必然单调不减,所以对于一个 \(j\),我们能够用双指针 \(O(n)\) 求出。

具体实现为开一个桶记录 \(a_i\) 出现的次数,用双指针维护一个满足修改次数小于等于 \(j\) 的区间,即可求出 \(g(i,j)\),时间复杂度 \(O(nk)\)

总时间复杂度是 \(O(nk^2)\),可以通过本题。


后记:

在本题中,尽量不要使用 memset 函数给数组进行赋初值(都是血泪教训)

memset 函数的时间复杂度为 \(O(n)\),其中 \(n\) 为数组的字节大小,具体可以看这篇文章

本题可以通过构造数据使得 memset 函数 TLE。

TLE on #15


#include<cstdio>
#include<cstring>
const int N=2e5+10,K=25,S=1e7+10;
const int inf=0x3f3f3f3f;
int T,n,k;
int ans;
int a[N];
int num[S];
int f[N][K],g[N][K];
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9')ch=='-'?f=0:0,ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?x:-x;
}
inline int min(int a,int b){return a<b?a:b;}
int main(){
	T=read();
	while(T--){
		n=read(),k=read(),ans=inf;
		for(int i=1;i<=n;++i){
			a[i]=read();
			for(int j=2;j*j<=a[i];++j)while(a[i]%(j*j)==0)
				a[i]/=j*j;
		}
		for(int i=0;i<=k;++i){
			for(int j=1;j<=n;++j)num[a[j]]=0;
			for(int l=1,r=1,t=0;r<=n;++r){
				++num[a[r]],t+=(num[a[r]]>1);
				while(t>i&&l<r)
					t-=(num[a[l]]>1),--num[a[l]],++l;
				g[r][i]=l;
			}
		}
		for(int i=1;i<=n;++i)for(int j=0;j<=k;++j){
			f[i][j]=inf;
			for(int x=0;x<=j;++x)
				f[i][j]=min(f[i][j],f[g[i][x]-1][j-x]+1);
		}
		for(int i=0;i<=k;++i)
			ans=min(ans,f[n][i]);
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2023-12-18 18:18  L01001101  阅读(12)  评论(0)    收藏  举报