[APIO2015]巴厘岛的雕塑(数位dp)

【题解】

引用ZYF神犇一句话:"显然位运算的极值问题都应该从高位向低位考虑。优先让这一位为0,如果行的话这一位就是0,否则就设为1。" 


设答案为ans,从高位到低位枚举 是否有使ans的这一位为0的方案,注意到每一位是互相独立的 

假设枚举到了倒数第x位,
即ans的最高位到倒数第x+1位的最优01分布已确定,现在正在判断第x位是否有可能填0:
对于每个x,考虑递推法:
设 布尔数组 f[i][j]表示:将前i个数分j段,能否在 得到的优美度的最高位到倒数第x+1位 都与ans一致的情况下,让倒数第x位为0
因为段与段之间的合并为or运算,所以递推方式为:
若 将前k个数(k<i)分j-1段,得到的优美度本身与ans一致,并且第j段的优美度也与ans一致,才可以由f[k][j-1]转移到f[i][j]
如何判断以上两个条件:
1. 得到的优美度的最高位到倒数第k+1位 是否与ans一致:( (S[i]-S[k])>>k | ans ) == ans (ans为0的位,S[i]-S[k]都不为1)
2. 得到的优美度的倒数第k位能否为0:( (S[i]-S[k]) & 1<<(k-1) ) == 0

对于每个x,若f[n][A~B]有至少一个为1,nas的第k位就可以为0

复杂度:O( logY * n^3 )

对于最后一组数据:A==1,B<=n,段数只有上限 
我们要想把f数组的第二个维度省掉的话,用f[i]记录将前i个数分段并得到可行解的最小段数,最后判断其是否小于B,即可 

复杂度:O( logY * n^2 )


注意:第一次WA37是因为“1<<(LL)x-1LL”,这里第一个1没有写成1LL


【代码】

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define INF 100000
typedef long long LL;
LL s[2005];
int f[105][105],g[2005];
LL ans=0,t;
int n,A,B,len=0;
int min(int a,int b)
{
	if(a<b) return a;
	return b;
}
void work1()
{
	int x,i,j;
	for(x=len;x>0;x--)
	{
		for(i=1;i<=n;i++)
			g[i]=INF;//g[0]=0;
		for(i=1;i<=n;i++)
			for(j=0;j<i;j++)
				if(g[j]<B)
				{
					t=s[i]-s[j];
					if( (t>>(LL)x|ans)==ans && (t&1LL<<(LL)x-1LL)==0 ) g[i]=min(g[i],g[j]+1);
				}
		ans<<=1LL;
		if(g[n]>B) ans++;
	}
}
void work2()
{
	int x,i,j,k;
	for(x=len;x>0;x--)
	{
		memset(f,0,sizeof(f));
		f[0][0]=1;
		for(i=1;i<=n;i++)
			for(j=1;j<=i;j++)
				for(k=0;k<i;k++)
					if(f[k][j-1])
					{
						t=s[i]-s[k];
						if( (t>>(LL)x|ans)==ans && (t&1LL<<(LL)x-1LL)==0 ) f[i][j]=1;
					}
		for(i=A;i<=B;i++)
			if(f[n][i]) break;
		ans<<=1;
		if(i>B) ans++;
	}
}
int main()
{
	int i;
	scanf("%d%d%d",&n,&A,&B);
	for(i=1;i<=n;i++)
	{
		scanf("%lld",&s[i]);
		s[i]+=s[i-1];
	}
	for(t=s[n];t>0;t>>=1)
		len++;
	if(A==1) work1();
	else work2();
	printf("%lld",ans);
	return 0;
}


你可能感兴趣的:(dp,apio)