[APIO2015] T1巴厘岛的雕塑

题目传送门

题目范围如果要求用 l o n g   l o n g long\ long long long,在左移的时候一定要写成 1 L L 1LL 1LL

题意

巴厘岛的一条主干道上共有 N N N座雕塑,依次编号为 1 1 1 N N N。雕塑 i i i的年龄为 Y i Y_i Yi
政府想把这些雕塑分成恰好 X X X组,要求每组不能为空,且每组雕塑的编号必须连续。每个雕塑必须属于某一组。
分组方案需要考虑美观程度。计算方法如下:分别计算每组雕塑的年龄之和,然后将每一组的结果按位取或,就得到了该分组方案的美观值。求最小的美观值

S o l u t i o n Solution Solution

这道题首先想到题目中 A 、 B A、B AB限制分组条件,又看到数据范围分为 A = = 1 A==1 A==1 A ! = 1 A!=1 A!=1两种情况,于是显然限制条件不同需要分情况讨论。

A ! = 1 A!=1 A!=1

这道题看起来是限制条件下的贪心,考虑如何贪心,显然最后答案的和一定是由 01 01 01串组成的,于是考虑如何分组使得 01 01 01串最小,显然这种思路可以转化为:构造一个 a n s ans ans a n s ans ans是否符合 A   B A\ B A B分组的限制条件。则考虑先构造一个一定最不优的 a n s ans ans并从高位到低位逐位将 a n s ans ans上的 0 0 0转换成 1 1 1看是否可行,验证构造的 a n s ans ans即可。
先令 a n s ans ans为当前最不优的情况(使其完全满足可贪心递推的条件),枚举每个 a n s ans ans看它是否符合贪心思想。 b o o l bool bool型数组 f i , k f_{i,k} fi,k表示前 i i i个数,分成 k k k组这种情况是否可行。根据 i i i值从前往后递推,如果此时讨论的连续序列符合当前的贪心思想,则对此时情况标记为可行。判断每个 a n s ans ans并更新,则一定能得到最优解。
代码细节问题: d p dp dp记得赋初值,要从已经赋过值的状态转移(虽然也许这种状态下的情况和实际矛盾,比如 0 0 0个数分 0 0 0组的方案数为 1 1 1)。构造前缀和来直接检验那一段数列是否符合贪心, ( ( s [ k ] − s [ i ] ) ∣ x ) = = x ((s[k]-s[i])|x)==x ((s[k]s[i])x)==x,因为是“或”不是“异或”,所以这样就表示当前这样分组后符合贪心条件,不会使贪心变得不优。
曾经的疑惑:其实 i i i k k k这段序列也不一定单独分一组更优,但是我们必须使分组条件满足组数 ≥ A \geq A A,而在不会使答案更劣的情况下这样必定会使答案更符合题目限制。

A = = 1 A==1 A==1

此时大体思路和上面差不多,但是注意到这个时候 N N N很大,又因为我们没有了最低分组数目的限制,所以可以直接将 d p dp dp圧掉一维,用 f i f_i fi表示前面 i i i个数分成多少组,检验最后分组数 f n f_n fn是否满足 ≤ B \leq B B即可。
代码细节问题:这里 f i f_i fi因为是取 m i n min min所以要记得赋最大值,同上赋初值 f 0 = 0 f_0=0 f0=0

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
int s[N],ans,n,a,b;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
	return cnt*f;
}
bool f[105][105];
int f2[2005];
inline bool check(int x){
	memset(f,0,sizeof(f));
	f[0][0]=1;
	for(int i=0;i<n;++i){//要从0开始 因为是从0赋的值 要不然推不过去 
		for(int j=0;j<b;++j){
			for(int k=i+1;k<=n;k++){
				if(((s[k]-s[i])|x)==x){
					f[k][j+1]|=f[i][j];
					//也不一定 i+1 要单独成一位 但是要满足a这个下限 所以在符合条件的时候要另开一组(k+1)保证更优 
				}
			}
		}
	}
	for(int i=a;i<=b;i++)if(f[n][i]) return true;
	return false;
}
inline bool check2(int x){
	memset(f2,63,sizeof(f2));f2[0]=0;//一定要记得赋最大值 
	for(int i=0;i<n;i++){
		for(int k=i+1;k<=n;k++){
			if(((s[k]-s[i])|x)==x)
				f2[k]=min(f2[k],f2[i]+1);//因为a==1 所以不考虑下限 圧掉一维 直接判在最后是否符合b 
		}
	}
	if(f2[n]<=b) return true;
	return false;
}
signed main(){
	n=read(),a=read(),b=read();
	for(int i=1;i<=n;++i){s[i]=read();s[i]+=s[i-1];}
	if(a!=1){
		ans=(1LL<<50)-1;//相当于ans现在在二进制下是由49个1构成  
		for(int i=49;i>=0;i--){
			ans-=(1LL<<i);//将第i个1变成0  并判断此时贪心是否合法  
			if(!check(ans))ans+=(1LL<<i);//如果此时的ans不满足条件 则还原ans  
		}
		cout<<ans<<endl;
	}
	if(a==1){
		ans=(1LL<<50)-1;
		for(int i=49;i>=0;i--){
			ans-=(1LL<<i);
			if(!check2(ans))ans+=(1LL<<i);
		}
		cout<<ans<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值