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\),于是
于是我们可以按照 \(\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
又一道神仙题。咕咕咕