Manthan, Codefest 19 (open for everyone, rated, Div. 1 + Div. 2) F.Bits And Pieces(高维前缀和sosdp)

题目

长度为n(3<=n<=1e6)的数组a[],0<=ai<=2e6

求最大的ai|(aj&ak)的值,满足三元组i<j<k

思路来源

CodeForces -1208F Bits And Pieces(位运算,贪心,SoS DP)_1208c codeforces-CSDN博客

题解

考虑可以枚举一维ai,

问题转化为,在ai没有的那些二进制位里,从高位到低位选,

假设当前贪心选取的二进制位为bit,bit对应的任意超集里,是否存在两个位置j,k比i大

由于只需要考虑两个位置,故维护pair dp[i]为i对应的二进制状态的最大的两个位置

由于对于bit要考虑其超集,故令超集对其子集更新,

全下放的复杂度不能接受,考虑每次只舍一个二进制位,倒序下放

复杂度O(n+m*2^m),其中m为二进制位20

O(m*2^m)预处理,然后枚举一维ai,

从高位到低位贪心选位,然后考虑这个额外选的二进制状态里的最大两个位置j,k,

如果均大于就或上这一位,否则就保留着之前的状态,继续往低位选

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10,M=2e6+10;
#define fi first
#define se second
typedef pair<int,int> P;
int n,v,ans,a[N];
P dp[M];
//dp[i}:表示i的超集(即子集中包含i)的最大位置和次大位置
void merge(P &a,int x){
    if(x>a.fi){
        a.se=a.fi;
        a.fi=x;
    }
    else if(x>a.se && x!=a.fi){
        a.se=x;
    }
}
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        scanf("%d",&a[i]);
        merge(dp[a[i]],i);
    }
    for(int i=20;i>=0;--i){
        for(int j=M-1;j>=0;--j){
            if(j>>i&1){//去掉1位的子集 每个向相邻的子集下放答案
                merge(dp[j^(1<<i)],dp[j].fi);
                merge(dp[j^(1<<i)],dp[j].se);
            }
        }
    }
    for(int i=0;i<n-2;++i){//i,n-2,n-1
        int s=0;
        for(int j=20;j>=0;--j){
            if(!(a[i]>>j&1) && dp[s|(1<<j)].fi>i && dp[s|(1<<j)].se>i){
                s|=(1<<j);
            }
        }
        ans=max(ans,a[i]|s);
    }
    printf("%d\n",ans);
    return 0;
}

题解2(2024.6.25补充)

有个群友问他的均摊做法为啥TLE,然后看了下代码,常数太大了,魔改了下就过了

由于每个状态最多被摊两次,直接下放均摊所有子状态即可,

每次只删一个1下放,这样多访问一层也最多会访问m个而已,

大概总访问状态数是2*m*2^m级别的,严格的次数不太会证

query[x]记录x这个状态第一次被更新是被哪个位置更新的

// LUOGU_RID: 163019403
#pragma GCC optimize(2)
#include <algorithm>
#include <cstdio>
#include <vector>
#include <set>
namespace Input{
	const int BUFFLEN = 1 << 20;
	char buf[BUFFLEN | 2], *p1 = buf, *p2 = buf;
	inline char getc(){
		if(p1 == p2)
			p1 = buf,
			p2 = buf + fread(buf, 1, BUFFLEN, stdin);
		return p1 == p2? EOF: *p1++;
	}
	inline bool isdgt(const char& op){return op >= '0' && op <= '9';}
	inline bool isalpha(const char& op){return op >= 'a' && op <= 'z';}
	inline bool iscapitcal(const char& op){return op >= 'A' && op <= 'Z';}
	inline bool isletter(const char& op){return isalpha(op) || iscapitcal(op);}
	inline bool ischr(const char& op){return isletter(op);}
	long long read(){
		static long long res; static char op, f;
		for(f = 0, op = getc(); !isdgt(op); op = getc()) f |= (op == '-');
		for(res = 0; isdgt(op); op = getc()) res = res * 10 + (op ^ '0');
		return f? -res: res;
	}
	int readstr(char *s){
		static int len; static char op;
		do op = getc(); while(!ischr(op));
		for(len = 0; ischr(op); op = getc()) s[len++] = op;
		return len;
	}
}
using namespace std;
using Input::read;
typedef long long ll;
const int N = 1 << 21;
int query[N];
bool tax1[N], tax2[N];
void add2(const int& x){
	if(tax2[x]) return;
	tax2[x] = 1;
	for(int t = x; t; t ^= t & -t)
		add2(x ^ (t & -t));
}
void add1(const int& x,int y){
	if(tax1[x]){
        if(query[x]==y)return;
		if(!tax2[x]) add2(x);
		return;
	}
	else if(query[x]<y){
		query[x]=y;
        tax1[x]=1;
		for(int t = x; t; t ^= t & -t)
			add1(x ^ (t & -t),y);
	}
}
void addval(const int& x,int y){
	add1(x,y);
}
int a[N], n;
signed main(int argc, char **argv){
//	freopen("order.in", "r", stdin);
//	freopen("F_tax.out", "w", stdout);
	n = read();
	for(int i = 1; i <= n; ++i)
		a[i] = read();
	addval(a[n],n); // puts("a");
	addval(a[n - 1],n-1); // puts("b");
	int ans = 0;
	for(int i = n - 2; i; --i){
		int tmp = 0;
		for(int j = 20; ~j; --j)
		if(!(a[i] & (1 << j))){
			if(tax2[tmp | (1 << j)]) tmp |= (1 << j);
		}
		ans = std::max(ans, a[i] | tmp);
		addval(a[i],i);
	}
	printf("%d\n", ans);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值