300iq contest 選做
Contest2 B. Bitwise Xor
題目描述
有一個長度為 \(n\) 的陣列 \(a\) 和一個整數 \(x\),要求滿足如下條件子序列 \(b_1..b_k\) 的數量:
\[\forall 1\leq i<j\leq k\ b_i\oplus b_j\geq x \]\(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|\)
那麼我們就得到了這樣的貪心策略:按錢數排序之後考慮增量一個人,看有無完美匹配即可。
完美匹配可以考慮 \(\tt Hall\) 定理,設已經加入了 \(k\) 個人,也就是對於任意區間 \([L,R]\) 都滿足:
\[\sum_{i=L}^{R} a_i\geq \sum_{i=1}^k [l_i\geq L\and r_i\leq R] \]利用性質 \(l_i\leq l_{i+1},r_i\leq r_{i+1}\),我們考慮列舉"區間的區間",把限制寫成另一種形式,設 \(b_i\) 表示第 \(i\) 個人是否在獨立集中:
\[\sum_{i=L}^Rb_i\leq \sum_{i=l_L}^{r_R}a_i \]顯然可以差分,設 \(sa_i,sb_i\) 分別表示它們的字首和,那麼限制為:
\[c_R=sb_{R}-sa_{r_R}\leq sb_{L-1}-sa_{l_L-1}=d_L \]那麼我們線上段樹上維護 \(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);
}