CF1601 简要题解

题面在这里看

A. Array Elimination

对每一位考虑,如果有 \(m\) 个数当前位置为 \(1\) \((m>0)\),那么 \(k\) 必须是 \(m\) 的因数,于是直接求出所有位置的 \(1\) 个数的 \(gcd\) 即可。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,a[N],T;
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
		int g=0;
		for(int i=0;i<30;++i){
			int ct=0;
			for(int j=1;j<=n;++j)
				if(a[j]&(1<<i)) ct++;
			if(ct) g=__gcd(g,ct);
		}
		if(!g){
			for(int i=1;i<=n;++i) printf("%d ",i);puts("");
			continue;
		}
		int i;
		for(i=1;i*i<=g;++i) if(g%i==0) printf("%d ",i);
		for(i--;i>=1;--i) if(g%i==0&&(g/i)!=i) printf("%d ",g/i);
		puts("");
	}
	return 0;
}

B. Frog Traveler

考虑每次跳跃+下滑后从 \(i\) 位置到达了 \(j\) 位置,那么我们一定会选择走 \(j-a_j<i-a_i\)\(j\) 位置。于是按 \(a_i+i\) 从小到大排序,用线段树维护,求出 \(dp_i\) 后就在线段树上所有 \(j+b_j=i\)\(j\) 上记录 \(dp_i\),那么转移只需要在线段树上找 \([i-a_i,i]\) 区间的最小值进行转移即可。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int n,a[N],b[N],f[N],id[N],to[N];
vector<int> ve[N];
namespace SGT{
	#define lc (p<<1)
	#define rc (p<<1|1)
	#define mid ((l+r)>>1)
	int mn[N<<2],pos[N<<2];
	inline void init(int p=1,int l=0,int r=n){
		mn[p]=0x3f3f3f3f;pos[p]=0;
		if(l==r) return ;
		init(lc,l,mid);init(rc,mid+1,r);
	}
	inline void update(int p,int x,int v,int l=0,int r=n){
		if(l==r){mn[p]=v;pos[p]=l;return ;}
		x<=mid?update(lc,x,v,l,mid):update(rc,x,v,mid+1,r);
		mn[p]=min(mn[lc],mn[rc]);
		pos[p]=mn[p]==mn[lc]?pos[lc]:pos[rc];
	}
	inline pair<int,int> query(int p,int ql,int qr,int l=0,int r=n){
		if(ql<=l&&r<=qr) return make_pair(mn[p],pos[p]);
		pair<int,int> ans=make_pair(0x3f3f3f3f,0);
		if(ql<=mid) ans=min(ans,query(lc,ql,qr,l,mid));
		if(qr>mid) ans=min(ans,query(rc,ql,qr,mid+1,r));
		return ans;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),id[i]=i;
	for(int i=1;i<=n;++i) scanf("%d",&b[i]),ve[i+b[i]].push_back(i);
	sort(id+1,id+n+1,[&](const int &x,const int &y){return x-a[x]<y-a[y];});
	f[0]=0;
	SGT::init();
	SGT::update(1,0,0);
	for(int i=1;i<=n;++i){
		int u=id[i];auto p=SGT::query(1,u-a[u],u);
		f[u]=p.first+1;to[u]=p.second;
		for(int v:ve[u]) SGT::update(1,v,f[u]);
	}
	if(f[n]>=0x3f3f3f3f){
		puts("-1");
		return 0;
	}
	printf("%d\n",f[n]);
	int now=n;
	while(now!=0){
		now=to[now];
		printf("%d ",now);now+=b[now];
	}
	return 0;
}

C. Optimal Insertion

\(b\) 排序后进行插入,通过交换法可以容易证明 \(b\) 插入的位置也是升序的。用线段树维护当前插入每个位置会增加多少逆序对,那么从 \(b_i\)\(b_{i+1}\) 就只需要找出所有满足 \(a_j\in[b_i,b_{i+1}]\)\(j\),在线段树上对 \([0,j-1]\) 做前缀减,对 \([j,n]\) 做后缀加,再查询全局 \(\min\) 即可。这个过程中最优决策点不会左移,因此 \(b\) 插入的位置也是升序的,内部不会造成影响。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int T,n,m,a[N],b[N],id[N];
namespace SGT{
	#define lc (p<<1)
	#define rc (p<<1|1)
	#define mid ((l+r)>>1)
	int mn[N<<2],tag[N<<2];
	inline void pushup(int p){mn[p]=min(mn[lc],mn[rc]);}
	inline void pushdown(int p){
		if(!tag[p]) return ;
		mn[lc]+=tag[p];tag[lc]+=tag[p];
		mn[rc]+=tag[p];tag[rc]+=tag[p];tag[p]=0;
	}
	inline void build(int p=1,int l=0,int r=n){
		tag[p]=0;
		if(l==r){mn[p]=l;return ;}
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(p);
	}
	inline void update(int p,int ql,int qr,int v,int l=0,int r=n){
		if(ql<=l&&r<=qr){mn[p]+=v;tag[p]+=v;return ;}
		pushdown(p);
		if(ql<=mid) update(lc,ql,qr,v,l,mid);
		if(qr>mid) update(rc,ql,qr,v,mid+1,r);
		pushup(p);
	}
}
namespace BIT{
	int c[N];
	inline void init(){memset(c+1,0,sizeof(int)*(n));}
	inline int lowbit(int x){return x&(-x);}
	inline void upd(int x){for(;x>0;x-=lowbit(x)) c[x]++;}
	inline int query(int x){
		int ret=0;
		for(;x<=n;x+=lowbit(x)) ret+=c[x];
		return ret;
	}
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		BIT::init();SGT::build();
		for(int i=1;i<=n;++i) scanf("%d",&a[i]),id[i]=i;
		for(int i=1;i<=m;++i) scanf("%d",&b[i]);
		sort(b+1,b+m+1);
		sort(id+1,id+n+1,[&](const int &x,const int &y){return a[x]<a[y];});
		int now=1;
		long long ans=0;
		for(int l=1,r=0;l<=n;l=r+1){
			r=l;
			while(r<n&&a[id[r+1]]==a[id[r]]) ++r;
			for(int i=l;i<=r;++i) ans+=BIT::query(id[i]);
			for(int i=l;i<=r;++i) BIT::upd(id[i]);
		}
		for(int i=1,las=0;i<=m;++i){
			if(i!=1&&b[i]==b[i-1]){ans+=las;continue;}
			while(now<=n&&a[id[now]]<b[i]) SGT::update(1,0,id[now]-1,1),SGT::update(1,id[now],n,-1),now++;
			int ql=now;
			while(ql<=n&&a[id[ql]]==b[i]) ++ql;
			for(int j=now;j<ql;++j) SGT::update(1,id[j],n,-1);
			las=SGT::mn[1];ans+=las; 
			for(int j=now;j<ql;++j) SGT::update(1,0,id[j]-1,1);
			now=ql;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

D. Difficult Mountain

神仙结论题:将所有人按 \(\max(a,s)\) 为第一关键字,\(s\) 为第二关键字后排序,然后依次考虑每一个人,能选就选。

证明:对 \(a\le s\)\(a>s\) 的人分类讨论,不妨设正在比较 \(i,j\)

  • \(a_i\le s_i,a_j\le s_j\),他们都不会影响山的高度,于是 \(s\) 的人先攀登一定更优。
  • \(a_i>s_i,a_j>s_j\),不妨设 \(a_i<a_j\),若 \(j\) 先上山则 \(i\) 一定不可能上山了,因此不如先尝试 \(i\)
  • \(a_i\le s_i,a_j>s_j,s_i<a_j\),和上一个一样,\(j\) 上山后 \(i\) 一定上不了,不如让 \(i\) 先尝试。
  • \(a_i\le s_i,a_j>s_j,s_i>a_j\),先让 \(j\) 上山不会影响 \(i\),因此先尝试 \(j\) 一定不劣。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,ans,d;
struct node{
	int s,d;
}a[N];
int main(){
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;++i)
		scanf("%d%d",&a[i].s,&a[i].d);
	sort(a+1,a+n+1,[&](const node &x,const node &y){
		int t1=max(x.s,x.d),t2=max(y.s,y.d);
		if(t1^t2) return t1<t2;
		if(x.s^y.s) return x.s<y.s;
		return x.d<y.d;
	});
	ans=0;
	for(int i=1;i<=n;++i) if(a[i].s>=d) ans++,d=max(d,a[i].d);
	printf("%d\n",ans);
	return 0;
}

E - Phys Ed Online

首先可以用单调队列求出 \(mn_i=\min_{j\in[i,i+k]}a_j\),于是

\[ans(l,r)=a_l+\sum_{j=1}^{l+kj\le r}\min_{0\le w< j}mn_{l+wk} \]

于是我们可以按照 \(\mod k\) 的余数分类,将问题转化为 \(k=1\) 的情况。

从右到左考虑每个左端点,对当前点的答案做出贡献的一定只有一个单减的序列,使用单调栈维护这一单减序列,预处理倍增数组 \(f_{i,j},g_{i,j}\) 表示从 \(i\) 出发的单调栈的第 \(2^j\) 项,以及从 \(i\) 跳到 \(f_{i,j}\) 途中所有数的贡献,然后就可以 \(\mathcal O(n\log n+q\log n)\) 倍增求出答案了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=6e5+10;
int mn[N],to[N][20],n,q,k,a[N],pw[N],b[N],l,r;
ll s[N][20];
int main(){
	scanf("%d%d%d",&n,&q,&k);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	l=1;r=0;
	pw[0]=k;
	for(int i=1;i<20&&pw[i-1]<=n;++i) pw[i]=pw[i-1]<<1;
	for(int i=n;i>=1;--i){
		while(l<=r&&a[b[r]]>a[i]) --r;
		b[++r]=i;
		while(l<=r&&b[l]>i+k) ++l;
		mn[i]=a[b[l]];
	}
	a[n+k]=-1;
	for(int i=n+k;i>n-k;--i) for(int j=0;j<20;++j) to[i][j]=n+k; 
	for(int i=n-k;i>=1;--i){
		to[i][0]=i+k;
		if(mn[to[i][0]]>=mn[i]){
			int x=to[i][0];
			for(int j=19;j>=0;--j)
				if(mn[to[x][j]]>=mn[i]) x=to[x][j];
			to[i][0]=to[x][0];
		}
		s[i][0]=1ll*mn[i]*(to[i][0]-i)/k;
		for(int j=1;j<20;++j){
			to[i][j]=to[to[i][j-1]][j-1];
			s[i][j]=s[i][j-1]+s[to[i][j-1]][j-1];
		}
	}
	while(q--){
		int l,r;scanf("%d%d",&l,&r);
		r-=(r-l)%k;
		ll ans=a[l]; 
		for(int i=19;i>=0;--i)
			if(to[l][i]<=r) ans+=s[l][i],l=to[l][i];
		printf("%lld\n",ans+1ll*mn[l]*((r-l)/k));
	}
	return 0;
}

F. Two Sorts

又一道神仙题。咕咕咕

posted @ 2021-10-27 23:10  cjTQX  阅读(65)  评论(0编辑  收藏  举报