【题解】qoj7566 Interval Addition

qoj7566】题解

一:【题意】

每次选择一个区间,将区间的数统一加上实数z,求使得序列全为0的最少操作次数

二:【解法】

做差分,题目转化为一个数+z.一个数-z
如果我们使u+=z,v-=z,则在u,v之间连边
通过一系列操作,我们最终构造了许多棵树
如果每个树的点权和为0,则一定存在使序列全为0的操作方案
此时各联通块的边数和即为操作次数

我们为什么不倒着考虑,直接构造和为0的联通块,此时生成树的边数之和即为操作次数
使得操作次数尽量小,则联通块个数需要尽量多,所以问题转化为了求划分联通块最大个数
由数据范围想到了状压dp
dp[S]:表示集合S中的元素划分联通块的最大个数
子集枚举T,dp[S]=max(1+dp[T])
此时理论时间复杂度O(3^n)

我们去掉所有0,然后采取记忆化搜索跑状压dp
虽然理论时间复杂度仍为O(3^n),但是如果去掉0,和为0的联通快个数会少很多(并且很难卡掉)
所以状态数不会很多,实测会跑得很快

三:【代码】

#include<bits/stdc++.h>
using namespace std;
const int N=24;
int a[N];
int dp[1<<N];
int tmp[1<<N];
int Dp(int S){
	if(S==0) return 0;
	if(dp[S]) return dp[S];
	for(int T=(S-1)&S;;T=(T-1)&S){
		if(tmp[S^T]) continue;
		dp[S]=max(dp[S],Dp(T)+1);
		if(T==0) break;
	}
	return dp[S];
} 
int main(){
	int n;cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	a[n]=-a[n-1];for(int i=n-1;i>=1;i--) a[i]-=a[i-1];n++;
	
	vector<int> q;
	for(int i=0;i<n;i++){
		if(a[i]!=0) q.push_back(a[i]);
	}
	n=q.size();
	for(int i=0;i<n;i++) a[i]=q[i];
	
	
	for(int st=0;st<(1<<n);st++){
		for(int i=0;st>>i;i++){
			if(st>>i&1) tmp[st]+=a[i];
		}
	}
	
	cout<<n-Dp((1<<n)-1)<<"\n";
	return 0;
}
posted @ 2025-12-18 09:06  Ming3398  阅读(25)  评论(0)    收藏  举报