好耶

题单

我单推这些题(

P2824 [HEOI2016/TJOI2016]排序

两种做法应该都是很好的方法。

二分

时间复杂度 \(O(n\log^2 n)\)。

二分答案后赋权值为 \(0/1\),有很优美的性质。

二分答案 \(mid\),我们把原排列中大于等于 \(mid\) 的数都标记为 \(1\),小于 \(mid\) 的都标记为 \(0\)。然后对于每个操作我们就将 \(01\) 序列排个序。最后如果第 \(p\) 个位子仍是 \(1\) 的话就是可行的。

太懒了并没有写

DS

时间复杂度 \(O(n\log n)\)。

考虑 ODT 的维护,可以把区间排序操作类比为区间染色。

然后对于区间分裂合并直接开权值线段树分裂合并即可。

时间复杂度证明即线段树分裂合并复杂度(

很好写 这是好的(

Code

const int N=4e5+5,logN=20;
int n,m,rt[N],nw=1,a[N];

struct range{
	int l,r,v;
	range(int L,int R=-1,int V=0):l(L),r(R),v(V){}
	bool operator < (range x) const{return l<x.l;}
};
set<range> qaq;
#define IT set<range>::iterator

struct SGT{
	#define mid ((l+r)>>1)
	#define xl (ls[x])
	#define xr (rs[x])
	int sum[N*logN],ls[N*logN],rs[N*logN],st[N],tp,cnt;
	inline int newnode(){
		if(tp) return st[tp--];
		return ++cnt;
	}
	inline void pushup(int x){sum[x]=sum[xl]+sum[xr];}
	inline void del(int x){sum[x]=xl=xr=0;st[++tp]=x;}
	inline int insert(int x,int l,int r,int k,int v){
		if(!x) x=newnode();
		if(l==r){sum[x]+=v;return x;}
		if(k<=mid) xl=insert(xl,l,mid,k,v);
		else xr=insert(xr,mid+1,r,k,v);
		pushup(x);return x;
	}
	inline int merge(int x,int y,int l,int r){
		if(!x||!y) return x|y;
		if(l==r){sum[x]+=sum[y];del(y);return x;}
		xl=merge(xl,ls[y],l,mid);xr=merge(xr,rs[y],mid+1,r);
		pushup(x);return x;
	}
	inline void split(int x,int &y,int l,int r,int k){
		if(!x) return ;y=newnode();
		if(k>sum[xl]) split(xr,rs[y],mid+1,r,k-sum[xl]);
		else xr^=rs[y]^=xr^=rs[y];
		if(k<sum[xl]) split(xl,ls[y],l,mid,k);
		pushup(x);pushup(y);return ;
	}
	inline int kth(int x,int l,int r,int k){
		if(l==r) return l;
		if(sum[xl]>=k) return kth(xl,l,mid,k);
		return kth(xr,mid+1,r,k-sum[xl]);
	}
	inline int query(int x,int l,int r,int L,int R){
		if(L<=l&&r<=R) return sum[x];
		ll res=0;
		if(mid>=L) res+=query(xl,l,mid,L,R);
		if(mid<R) res+=query(xr,mid+1,r,L,R);
		return res;
	}
}T;

// ---------- SGT ---------- //

inline IT split(int pos){
	IT tmp=qaq.lower_bound(range(pos));
	if(tmp!=qaq.end()&&tmp->l==pos) return tmp;
	--tmp;int l=tmp->l,r=tmp->r,v=tmp->v;
	if(!v) T.split(rt[l],rt[pos],1,n,pos-l);
	else T.split(rt[l],rt[pos],1,n,r-pos+1),rt[l]^=rt[pos]^=rt[l]^=rt[pos];
	qaq.erase(tmp);qaq.insert(range(l,pos-1,v));
	return qaq.insert(range(pos,r,v)).first;
}

inline void assign(int l,int r,int op){
	IT R=split(r+1),L=split(l),nw=L;++nw;
	while(nw!=R) T.merge(rt[l],rt[nw->l],1,n),++nw;
	qaq.erase(L,R);qaq.insert(range(l,r,op));
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	rd(n);rd(m);
	for(re i=1;i<=n;++i){
		rd(a[i]);rt[i]=T.insert(rt[i],1,n,a[i],1);qaq.insert(range(i,i,0));
	}
	qaq.insert(range(n+1,n+1,0));
	for(re i=1;i<=m;++i){
		int op,l,r;rd(op);rd(l);rd(r);
		assign(l,r,op);
	}
	rd(m);
	for(IT nw=qaq.begin();nw!=qaq.end();++nw){
		if(nw->r-nw->l+1<m) m-=nw->r-nw->l+1;
		else{
			wr(T.kth(rt[nw->l],1,n,nw->v?nw->r-nw->l+2-m:m));puts("");break;
		}
	}
	return 0;
}

// ---------- Main ---------- //

CF1559D Mocha and Diana

很好的贪心题。

令 \(A\) 图连通块个数不小于 \(B\) 图。

我们证明 \(B\) 图最后连通块个数为 \(1\)。

若 \(A\) 连通块数量大于 \(1\),若此时无法操作,考虑到 \(A\) 图中连通块 \(a\), \(b\)。

对于 \(a\),\(b\) 中的点,他们在 \(B\) 图中一定连通。

那么考虑到 \(A\) 中所有连通块,可发现 \(B\) 全连通,即连通块数量为 \(1\)。

由此贪心分析可知,随意加可行边即可。

因此 D1 就可以直接暴力枚举点对加边即可,时间复杂度 \(O(n^2\alpha(n))\)。

优化贪心。

考虑一个中心点 \(s\)。

我们先让所有点与 \(s\) 尝试连边。

然后连完后令 \(A\) 图中与 \(s\) 不连通的点集为 \(L\),\(B\) 图中与 \(s\) 不连通的点集为 \(R\)。

显然 \(L\cap R=\varnothing\)。

考虑 \(l\in L\) 和 \(r\in R\)。

由定义有 \(A\) 图中 \(l\) 与 \(s\) 不连通,\(r\) 与 \(s\) 连通,\(B\) 图相反。

那么任意 \(l\) 与 \(r\) 都可连边。

然后只要随意配对完 \(L\) 或 \(R\) 就行了,此时一幅图变成一个连通块。

时间复杂度 \(O(n\alpha(n))\)。

Code

const int N=1e5+5;
int n,m1,m2,fa1[N],fa2[N],ans,qaq[N][2],l[N],r[N],cnt1,cnt2;

inline int find1(int x){return fa1[x]==x?x:fa1[x]=find1(fa1[x]);}
inline void merge1(int x,int y){fa1[find1(x)]=find1(y);}

inline int find2(int x){return fa2[x]==x?x:fa2[x]=find2(fa2[x]);}
inline void merge2(int x,int y){fa2[find2(x)]=find2(y);}

// ----------  ---------- //

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
//	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	rd(n);rd(m1);rd(m2);
	for(re i=1;i<=n;++i) fa1[i]=fa2[i]=i;
	for(re i=1;i<=m1;++i){
		int x,y;rd(x);rd(y);merge1(x,y);
	}
	for(re i=1;i<=m2;++i){
		int x,y;rd(x);rd(y);merge2(x,y);
	}
	for(re i=2;i<=n;++i)
		if(find1(i)!=find1(1)&&find2(i)!=find2(1)) qaq[++ans][0]=i,qaq[ans][1]=1,merge1(i,1),merge2(i,1);
	for(re i=2;i<=n;++i)
		if(find1(i)!=find1(1)) l[++cnt1]=i,merge1(i,1);
		else if(find2(i)!=find2(1)) r[++cnt2]=i,merge2(i,1);
	wr(ans+min(cnt1,cnt2));puts("");
	for(re i=1;i<=ans;++i) wr(qaq[i][0]),putchar(' '),wr(qaq[i][1]),puts("");
	for(re i=1;i<=min(cnt1,cnt2);++i) wr(l[i]),putchar(' '),wr(r[i]),puts("");
	return 0;
}

// ---------- Main ---------- //

P7824 「RdOI R3」毒水

很好的构造题。

观察到有 \(2^9<n<2^{10}\)。

大概一看就是考虑二进制分组了。

\(1\sim n\) 标号不太方便,我们改成 \(0\sim n-1\)

先看 \(maxk=30\) 的限制。

我们先选 \(10\) 只鼠编号为 \(0\sim 9\),让它们分别喝对应的水,编号为 \(i\) 的鼠喝的水的编号转化为二进制 \(2^i\) 这一项系数为 \(1\)。

考虑到变异鼠,我们每个鼠复制一下成三个鼠,这样就能辨别出变异鼠了。

然后考虑去掉变异鼠每个鼠存活情况,编号为 \(i\) 的鼠存活当且仅当毒水编号转化为二进制后 \(2^i\) 这一项系数为 \(0\)。

然后算出来即可。

然后考虑 \(maxk=15\)。

显然不是 \(\dfrac{maxk}{2}\) 的关系,因为这没啥实际意义。

很容易想到 \(\left\lceil \log_2n \right\rceil+\left\lceil \log_2\left\lceil \log_2n \right\rceil \right\rceil=14\),盲猜是 \(\left\lceil \log_2n \right\rceil+\left\lceil \log_2\left\lceil \log_2n \right\rceil \right\rceil+1=15\)。

\(\left\lceil \log_2\left\lceil \log_2n \right\rceil \right\rceil\) 这个东西容易想到是对上面我们鼠的编号再二进制分组。

考虑一些水的集合 \(S\) 和一些鼠的集合 \(M\),保证对于 \(\forall s\in S\) 水 \(s\) 被 \(M\) 中偶数个鼠喝了,且对于 \(\forall m\in M\) 鼠 \(m\) 喝的水都在 \(S\) 中。

有结论若 \(M\) 中死了偶数个则无变异鼠,若 \(M\) 中死了奇数个则有变异鼠。由定义和恰有一瓶毒水一只变异鼠易证。

那么考虑对 \(0\sim 9\) 这些鼠再进行类似地二进制分组,编号为 \(10+i\) 的鼠喝的水为编号转化为二进制后 \(2^i\) 系数为 \(1\) 的鼠喝的水的异或。(异或有很好的性质能保证每瓶水被喝偶数次。)

为防止这 \(10\sim 13\) 四只鼠里有内鬼,再拿一只鼠喝他们喝的水的异或即可。

然后先考虑 \(10\sim 14\) 这里有无内鬼,若有则表明 \(0\sim 9\) 里无内鬼,直接用上面 \(maxk=30\) 的方法算即可。

若 \(10\sim 14\) 无内鬼,则考虑 \(10\sim 13\) 和它们各自支配的鼠,分别考虑是否有内鬼,若 \(10+i\) 和支配的鼠里有内鬼,则表明内鬼鼠编号转化为二进制后 \(2^i\) 系数为 \(1\)。把内鬼算出来然后直接把它存活状态取反即可。然后还像上面一样算出毒水即可。

记得 \(n=1/2\) 拿出来特判。

记得 cout 不知道为啥之前不用 cout 基本全 T 了。

Code

const int N=1005,M=20;
int n,q,sum1,sum2,s[M],qaq;bool op;
bitset<N> a[M],ans;

// ----------  ---------- //

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
//	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>q;ans.set();
	if(n==1){cout<<2<<endl<<1<<endl;return 0;}
	if(n==2){cout<<"1 1 1"<<endl<<2<<endl;cin>>q;cout<<2-q<<endl;return 0;}
	sum1=log2(n-1)+1;sum2=log2(sum1-1)+1;
	for(re i=0;i<sum1;++i){
		for(re j=0;j<n;++j) a[i][j]=(j>>i)&1;cout<<"1 "<<a[i].count()<<' ';
		for(re j=0;j<n;++j) if((j>>i)&1) cout<<j+1<<' ';cout<<endl;
	}
	for(re i=0;i<sum2;++i){
		for(re j=0;j<sum1;++j) if((j>>i)&1) a[sum1+i]^=a[j];cout<<"1 "<<a[sum1+i].count()<<' ';
		for(re j=0;j<n;++j) if(a[sum1+i][j]) cout<<j+1<<' ';cout<<endl;a[sum1+sum2]^=a[sum1+i];
	}
	cout<<"1 "<<a[sum1+sum2].count()<<' ';
	for(re j=0;j<n;++j) if(a[sum1+sum2][j]) cout<<j+1<<' ';cout<<endl<<2<<endl;
	for(re i=0;i<=sum1+sum2;++i) cin>>s[i],s[i]^=1,op^=i>=sum1?s[i]:0;
	if(!op){
		for(re i=0;i<sum2;++i){
			q=s[sum1+i];for(re j=0;j<sum1;++j) q^=((j>>i)&1)?s[j]:0;qaq+=q<<i;
		}
		s[qaq]^=1;
	}
	for(re i=0;i<sum1;++i) if(s[i]) ans&=a[i];cout<<ans._Find_first()+1<<endl;
	return 0;
}

// ---------- Main ---------- //

P7825 「RdOI R3」VSQ

先咕

AT2389 [AGC016E] Poor Turkeys

时光倒流 和那道 JOI 的断层思想差不多。

考虑倒推,即从最后一个人开始向前进行。

我们考虑 \((i,j)\) 最后都没被扔掉,则在前面某人为 \((x,i)\) 时,我们扔掉的一定是 \(x\)。

那么此时我们就需要保证再往前 \(x\) 不能被扔掉。

若前面出现 \((x,y)\) 且都不能被扔掉,那么 \((i,j)\) 一定不是一组解。

注意到这里每次拓展实际是一个的拓展,而非一对的拓展。

我们可以将枚举 \((i,j)\) 换成枚举 \(i\),记录下如果要留下 \(i\) 前面一定不能被扔掉的数集 \(S_i\)。

此时若 \(S_i \cap S_j=\varnothing\),则 \((i,j)\) 满足题意。模型意义下即表示 \(i\) 和 \(j\) 前面不能被扔掉的东西不重复,因为在 \(S_i\) 中所有除 \(i\) 的东西都会被扔掉,而东西不能重复扔掉。

考虑到 \(S_i\) 和 \(S_j\) 实际上只要对位完成与运算,用 bitset 存储即可。

时间复杂度 \(O(nm+\dfrac{n^3}{\omega})\)。

Code

const int N=405,M=1e5+5;
int n,m,a[M],b[M],ans;
bool tag[N];
bitset<N> s[N];

// ----------  ---------- //

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
//	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	rd(n);rd(m);
	for(re i=1;i<=m;++i) rd(a[i]),rd(b[i]);
	for(re i=1;i<=n;++i) s[i][i]=1;
	for(re i=1;i<=n;++i)
		for(re j=m;j>0;--j){
			if(s[i][a[j]]&&s[i][b[j]]){tag[i]=1;break;}
			if(s[i][a[j]]) s[i][b[j]]=1;
			if(s[i][b[j]]) s[i][a[j]]=1;
		}
	for(re i=1;i<=n;++i)
		if(!tag[i]){
			for(re j=i+1;j<=n;++j)
				if(!tag[j]){
					if((s[i]&s[j]).count()==0) ++ans;
				}
		}
	wr(ans);puts("");
	return 0;
}

// ---------- Main ---------- //

CF446B DZY Loves Modification

贪心合并 证伪真的挺难(

一开始有个很简单的贪心想法,就是每次取行列中总和最大的那个。

然后你冲了一发,发现你挂了。

WA on #4。

然后其实你发现行列是对互相有后效影响的。

比如当你最大值又有行又有列,你是选哪个呢?

那么考虑到行或者列自己是不影响的。

那么考虑行列分开贪心,贪心策略同上。

令 \(l_i\) 为行选了 \(i\) 个最大答案,\(r_i\) 为列。

显然有 \(ans=\max_{i=0}^k\{l_i+r_i-i\times(k-i)\times p\}\),这时你把行列拆开就可以直接算出行列之间相互影响,保证了贪心的正确性。

记得开 long long。

贪心随便拿个东西存一下就行了,我整了个 multiset,时间复杂度 \(O(k\log\max(n,m))\)。

Code

const int N=1e3+5,K=1e6+5;
int n,m,k,p,a[N][N],l[K],r[K],sl[N],sr[N],ans=-1e18;
multiset<int> L,R;
#define IT multiset<int>::iterator

// ----------  ---------- //

signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
//	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	rd(n);rd(m);rd(k);rd(p);
	for(re i=1;i<=n;++i)
		for(re j=1;j<=m;++j) rd(a[i][j]),sl[i]+=a[i][j],sr[j]+=a[i][j];
	for(re i=1;i<=n;++i) L.insert(sl[i]);
	for(re i=1;i<=m;++i) R.insert(sr[i]);
	for(re i=1;i<=k;++i){
		IT it=L.end();--it;int res=*it;l[i]=l[i-1]+res;L.erase(it);L.insert(res-p*m);
		it=R.end();--it;res=*it;r[i]=r[i-1]+res;R.erase(it);R.insert(res-p*n);
	}
	for(re i=0;i<=k;++i) ans=max(ans,l[i]+r[k-i]-i*(k-i)*p);
	wr(ans);puts("");
	return 0;
}

// ---------- Main ---------- //
posted @ 2021-08-16 12:18  Demoe  阅读(142)  评论(1)    收藏  举报