专栏文章

CF1497E2题解

CF1497E2题解参与者 2已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@minqlml0
此快照首次捕获于
2025/12/02 06:44
3 个月前
此快照最后确认于
2025/12/02 06:44
3 个月前
查看原文
题解没有 O(nk)O(nk) 的 dp 做法,于是写一下。尽管预处理是 O(nV)O(n\sqrt V) 的。

首先注意到应该有一定的贪心性质,在分段数相同的时候,你希望最后一个分割点更靠后。
然后考虑完全平方数有什么性质,其因子的次数都是偶数。
两个数相乘为一个完全平方数,本质上和他们所含有的偶数次方因子没有关系。只需把这些除掉就转化成了两个数相等的问题。预处理一个小常数单根号是容易做的,可以进一步优化但没有必要。
再考虑你转化后怎么做,相等的情况可以记一个 toito_i 表示和 ii 相同的上一个,格式化想到一个 fi,jf_{i,j} 表示前 ii 个分 jj 段的最少段数,发现是没有办法转移的,因为你不知道上一个分段点。
不妨用 0/10/1 把答案和转移点都记下来,每次从前转移。
但是你其实并不需要决策转移点,因为考虑前 ii 个和前 i1i-1 个的区别本质就是第 ii 个位置造成的冲突。
记分段点为 xx,即最后一段的开头。
如果 x>toix>to_i,该冲突在这一段不存在,可以直接继承 fi,jf_{i,j}
否则冲突存在,处理方法是改掉一个或者新开一段。
也就是从 fi1,jf_{i-1,j} 或者 fi1,j1f_{i-1,j-1} 转移。
这个转移为什么是对的,考虑你在计算 fi,jf_{i,j} 的时候前面的东西应该被算完了,并且你已经记住了它的最优决策点。
所以每个位置的最优决策点应该是唯一的,所以就省去了枚举决策点的一个 kk
具体的方程可以看代码和注释,是简单的。主要想分享这种预先知道决策点复杂度省一个 kk 的东西。
CPP
#include<bits/stdc++.h>
using namespace std;
int a[200010],f[200010],dp[200010][21][2];
unordered_map<int,int> mp,to;
void solve(){
	int n,k;cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
		int now=a[i];
		for(int j=2;j*j<=now;j++){
			while(now%(j*j)==0) now/=(j*j);
		}
		a[i]=now;
	}
	mp.clear();
	for(int i=1;i<=n;i++){
		if(mp[a[i]]) to[i]=mp[a[i]];
		else to[i]=0;
		mp[a[i]]=i;
	}
	for(int i=0;i<=n;i++)
		for(int j=0;j<=k;j++) dp[i][j][1]=1e9,dp[i][j][0]=0;
	dp[1][0][0]=1,dp[1][0][1]=1;
	dp[1][1][0]=1,dp[1][1][1]=1;
	for(int i=2;i<=n;i++){
		for(int j=0;j<=min(i,k);j++){
			int x=to[i];
			if(dp[i-1][j][0]>x) dp[i][j][0]=dp[i-1][j][0],dp[i][j][1]=dp[i-1][j][1];
			else
			{
				if(dp[i-1][j][1]+1<=dp[i][j][1]){
					dp[i][j][0]=i;
					dp[i][j][1]=dp[i-1][j][1]+1;
				}
				if(j&&(dp[i-1][j-1][1]<dp[i][j][1]||(dp[i-1][j-1][1]==dp[i][j][1]&&dp[i-1][j-1][0]>dp[i][j][0]))){
					dp[i][j][0]=dp[i-1][j-1][0];
					dp[i][j][1]=dp[i-1][j-1][1];
				}
			}
		}
	} 
	int ans=1e9;
	for(int i=0;i<=k;i++){
		ans=min(ans,dp[n][i][1]);
	}
	cout<<ans<<'\n';
	return ;
}
signed main(){
	//freopen("seq.in","r",stdin);
	//freopen("seq.out","w",stdout);
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int T;cin>>T;while(T--) solve();
}
/*
尽量往后划 
f[i][j][0/1]=f[i-1][j][0/1]
to[i]
if(f[i-1][j][0]>to[i]) f[i][j][0/1]=f[i-1][j][0/1]
else 
f[i][j][0]=i,f[i][j][1]=f[i-1][j]+1
f[i][j][0]=f[i-1][j-1][0],f[i][j][1]=f[i-1][j-1][1]
*/

评论

2 条评论,欢迎与作者交流。

正在加载评论...