1. 程式人生 > 其它 >300iq contest 選做

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|\)

,設他們的交集是 \(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]\) 都滿足:

\[\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);
}