[GDOI2017模拟]最大值(贪心+递推)

【题面】

【解题思路】

以下的数均已转为二进制数

AND:贪心,从高位往低位,如果集合里的数有这一位的个数小于2,说明答案里不可能包含这一位,继续做下一位的操作;如果个数大于2,显然取有这一位的数会比其他数更优,把其他数从集合中删除。


XOR:贪心,建一棵字典树,同样是尽量使高位为1


OR:递推,设f[x](x为压缩状态)为是否有数包含x集合,显然对于f[x]=true有f[y](y为x的子集)=true,那么我们就可以从大到小递推,每个状态只要更新比它少一个1的状态就可以了(PS:我的程序里f数组顺便记录了权值,与此处略有不同)

【呆马】

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<iostream>
using namespace std;
const int N=1e5+1,M=2100001;
int T,n,m,c,l,L,t,sum,ans,i,j,k,a[N],ls[M],rs[M],f[M],ex[21];
bool cmp(int x,int y){return x>y;}
void add(int x,int y,int b)
{
	if (!b) return;
	int t=y&ex[b];
	if (!t)
	{
		if (!ls[x]) ls[x]=++m;
		add(ls[x],y,b-1);
	}
	else
	{
		if (!rs[x]) rs[x]=++m;
		add(rs[x],y,b-1);
	}
}

void find(int x,int y,int b)
{
	if (!b) return;
	int t=y&ex[b];
	if (!t)
	{
		if (rs[x])
		{
			sum+=ex[b];
			find(rs[x],y,b-1);
		}
		else if (ls[x]) find(ls[x],y,b-1);
	}
	else
	{
		if (ls[x])
		{
			sum+=ex[b];
			find(ls[x],y,b-1);
		}
		else if (rs[x]) find(rs[x],y,b-1);
	}
}

int main()
{
		ex[1]=1;
		for (i=2;i<=21;i++) ex[i]=ex[i-1]<<1;
		for (scanf("%d\n",&T);T;T--,scanf("\n"))
		{
			scanf("%d%d\n",&n,&c);
			l=1;
			for (i=1;i<=n;i++)
			{
				scanf("%d",&a[i]);
				for (;a[i]>=ex[l+1];l++);
			}
			L=ex[l+1]-1;
			ans=0;
			if (c==1)
			{
				sort(a+1,a+n+1,cmp);
				t=n;
				for (i=l;i;i--)
				{
					m=0;
					for (j=1;j<=t;j++)
						if (a[j]&ex[i]) m++;
					if (m<2) continue;
					for (j=1;j<=t;j++)
						for (;j<=t && !(a[j]&ex[i]);) swap(a[j],a[t--]);
				}
				printf("%d\n",a[1]&a[2]);
			}
			if (c==2)
			{
				m=1,ans=0;
				for (i=1;i<=n;i++) add(1,a[i],l);
				for (i=1;i<=n;i++)
				{
					sum=0;
					find(1,a[i],l);
					ans=max(ans,sum);
				}
				for (i=1;i<=m;i++) ls[i]=rs[i]=0;
				printf("%d\n",ans);
			}
			if (c==3)
			{
				sort(a+1,a+n+1,cmp);
				for (i=1;i<=L;i++) f[i]=0;
				for (i=a[1],j=1;i;i--)
				{
					for (;j<=n && a[j]==i;j++) f[i]=i;
					if (f[i])
						for (k=1;k<=l;k++)
						{
							t=i&ex[k];
							if (t) f[i-t]=i-t;
						}
				}
				for (i=1;i<L;i++)
				{
					for (j=1;j<=l;j++)
						if (!(i&ex[j])) f[i|ex[j]]=max(f[i|ex[j]],f[i]);
				}
				for (i=1;i<=n;i++) ans=max(ans,a[i]+f[L-a[i]]);
				printf("%d\n",ans);
			}
		}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值