好耶

题单

我单推这些题(

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  阅读(106)  评论(1编辑  收藏  举报