300iq contest 选做
Contest2 B. Bitwise Xor
题目描述
有一个长度为 \(n\) 的数组 \(a\) 和一个整数 \(x\),要求满足如下条件子序列 \(b_1..b_k\) 的数量:
\(n\leq 300000,x\leq 2^{60}-1\)
解法
我们考虑对于一堆固定的数,考虑它们最高位 \(w\),按这一位上是 \(0/1\) 分成两部分。那么显然只有两部分之间会在这一位上产生 \(1\) 的贡献,内部是不会产生贡献的。如果我们继续递归下去,这说明我们只需要考虑从小到大排序之后相邻两个数之间产生的限制。
上面的结论还有另一种形式:如果 \(a\leq b\leq c,\min(a\oplus b,b\oplus c)\leq a\oplus c\)
那么直接设 \(f[i]\) 表示以 \(a_i'\) 结尾的合法子序列个数,转移条件是 \(a_i'\oplus a_j'\geq x\),用 \(\tt trie\) 树优化转移即可,时间复杂度 \(O(n\log x)\)
总结
去掉无效限制是很重要的,两两问题通过排序变成相邻问题也是重要的思维方法。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 300005;
const int N = 60*M;
const int MOD = 998244353;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,cnt,ans,a[M],f[M],ch[N][2],siz[N];
int ask(int x)
{
int res=0,p=1;
for(int i=60;i>=0;i--)
{
int c=x>>i&1;
if(m>>i&1) p=ch[p][c^1];
else res+=siz[ch[p][c^1]],p=ch[p][c];
}
return res+siz[p];
}
void ins(int x,int y)
{
for(int i=60,p=1;i>=0;i--)
{
int c=x>>i&1;
if(!ch[p][c]) ch[p][c]=++cnt;
p=ch[p][c];siz[p]=(siz[p]+y)%MOD;
}
}
signed main()
{
n=read();m=read();cnt=1;
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
f[i]=ask(a[i])+1;
ans=(ans+f[i])%MOD;
ins(a[i],f[i]);
}
printf("%lld\n",ans);
}
Contest1 D. Dates
题目描述
有 \(n\) 个人 \(m\) 天,其中第 \(i\) 天可以接待 \(a_i\) 个人,一个人如果在 \([l_i,r_i]\) 被接待会支付 \(p_i\) 元,每个人最多被接待一次,问最后赚到的最大钱数。
\(n,m\leq 300000\),保证 \(l_i\leq l_{i+1},r_i\leq r_{i+1}\)
解法
首先可以构造出原问题的拟阵,记 \(U\) 为所有人的集合,\(\mathcal I\) 为合法接待方案的集合,\(\mathcal M=(U,\mathcal I)\)
简单证明一下交换性,考虑两个独立集 \(|A|<|B|\),设他们的交集是 \(C\),考虑反证法,假设接待完 \(A\) 之后不能接待 \(B\) 中的任何一个人。而接待完 \(C\) 之后是可以接待 \(A'=A\setminus C\) 或者 \(B'=B\setminus C\) 的,那么要满足假设必须要 \(|A'|\geq |B'|\),也就是 \(A'\) 至少要有 \(B'\) 这么大才能使他时候不能接待了,这说明 \(|A|\geq |B|\),导出了矛盾。
那么我们就得到了这样的贪心策略:按钱数排序之后考虑增量一个人,看有无完美匹配即可。
完美匹配可以考虑 \(\tt Hall\) 定理,设已经加入了 \(k\) 个人,也就是对于任意区间 \([L,R]\) 都满足:
利用性质 \(l_i\leq l_{i+1},r_i\leq r_{i+1}\),我们考虑枚举"区间的区间",把限制写成另一种形式,设 \(b_i\) 表示第 \(i\) 个人是否在独立集中:
显然可以差分,设 \(sa_i,sb_i\) 分别表示它们的前缀和,那么限制为:
那么我们在线段树上维护 \(c,d\) 即可,判断加入等价于判断 \(\max_{i=x}^n c_i\leq \min_{i=1}^x d_i\),时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 300005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,a[M],b[M],c[M],d[M],id[M];
int cmp(int x,int y) {return b[x]>b[y];}
struct seg
{
int op,s[M<<2],fl[M<<2];
int merge(int x,int y)
{
return op==0?max(x,y):min(x,y);
}
void build(int i,int l,int r)
{
fl[i]=0;
if(l==r)
{
s[i]=(op==0)?c[l]:d[l];
return ;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
s[i]=merge(s[i<<1],s[i<<1|1]);
}
void down(int i)
{
if(!fl[i]) return ;
s[i<<1]+=fl[i];fl[i<<1]+=fl[i];
s[i<<1|1]+=fl[i];fl[i<<1|1]+=fl[i];
fl[i]=0;
}
void add(int i,int l,int r,int L,int R)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
s[i]++;fl[i]++;
return ;
}
int mid=(l+r)>>1;down(i);
add(i<<1,l,mid,L,R);
add(i<<1|1,mid+1,r,L,R);
s[i]=merge(s[i<<1],s[i<<1|1]);
}
int ask(int i,int l,int r,int L,int R)
{
if(L<=l && r<=R) return s[i];
int mid=(l+r)>>1;down(i);
if(mid<L) return ask(i<<1|1,mid+1,r,L,R);
if(R<=mid) return ask(i<<1,l,mid,L,R);
return merge(ask(i<<1,l,mid,L,R),
ask(i<<1|1,mid+1,r,L,R));
}
}T1,T2;
signed main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
a[i]=read()+a[i-1];
for(int i=1;i<=n;i++)
{
int l=read(),r=read();b[i]=read();
c[i]=-a[r];d[i]=-a[l-1];id[i]=i;
}
sort(id+1,id+1+n,cmp);T1.op=0;T2.op=1;
T1.build(1,1,n);T2.build(1,1,n);
for(int i=1;i<=n;i++)
if(T1.ask(1,1,n,id[i],n)<T2.ask(1,1,n,1,id[i]))
{
T1.add(1,1,n,id[i],n);
if(id[i]<n) T2.add(1,1,n,id[i]+1,n);
ans+=b[id[i]];
}
printf("%lld\n",ans);
}