CF1601 题解
偶然看这一场的题目,忽然很有感觉,于是写了一下
A
题面
考虑每一位可以单独分开考虑
考虑单独的一位,每次要选 \(m\) 个位置,可能产生贡献的位置就是这位为 1 的数,设数量为 \(x\),则 \(m|x\)
最后显然合法的 \(m\) 满足 \(m| \gcd_{i=0}^{29} M_i\)
记得特判全为 \(0\) 的情况
Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
int k=1,x=0;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') k=-1; ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
inline int cmax(reg int x,reg int y){return x>y?x:y;}
const int N=2e5+10,INF=1e18;
int n,a[N];
inline int gcd(reg int x,reg int y){return y==0?x:gcd(y,x%y);}
inline bool check(){for (reg int i=1;i<=n;i++) if (a[i]) return false; return true;}
signed main(){reg int _=read(); while (_--){
n=read();for (reg int i=1;i<=n;i++) a[i]=read();
if (check()){for (reg int i=1;i<=n;i++) printf("%lld ",i);puts("");continue;}
reg int d=-1;
for (reg int t=0;t<=30;t++){
reg int cnt=0;
for (reg int i=1;i<=n;i++) if (a[i]>>t&1) cnt++;
if (d<0) d=cnt; else d=gcd(d,cnt);
}
for (reg int i=1;i<=d;i++) if (d%i==0) printf("%lld ",i); puts("");
}
return 0;
}
B
题面
一个直接的想法是建图跑最短路
考虑没有"下滑 \(b_i\)" 的限制应该怎么做,可以直接线段树优化建图。
考虑加入这个限制,对 \([0,n]\) 建立一个虚点,每个 \(i\) 和对应的虚点区间 \([i-a_i,i]\) 连边,然后再由虚点的 \(i\) 连向 \(i-b_i\)
使用 01bfs 优化复杂度。
Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
int k=1,x=0;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') k=-1; ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
inline int cmax(reg int x,reg int y){return x>y?x:y;}
const int N=(4e5+10)*5,INF=1e18,mod=998244353;
int n,a[N],b[N],idx,id[N],vis[N],pre[N],dis[N];
#define mp make_pair
vector<pair<int,int> > G[N];
inline void add(reg int u,reg int v,reg int w){G[u].push_back(mp(v,w));}
inline void build(reg int p,reg int l,reg int r){
if (l==r) return (void)(id[p]=l+n+1);id[p]=++idx; reg int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r); add(id[p],id[p<<1],0),add(id[p],id[p<<1|1],0);
}
inline void modify(reg int p,reg int l,reg int r,reg int L,reg int R,reg int node){
if (L<=l&&r<=R) return add(node,id[p],1); reg int mid=l+r>>1;
if (L<=mid) modify(p<<1,l,mid,L,R,node); if (R>mid) modify(p<<1|1,mid+1,r,L,R,node);
}deque<int> q;
signed main(){
n=read(); idx=2*n+1;
for (reg int i=1;i<=n;i++) a[i]=read();for (reg int i=1;i<=n;i++) b[i]=read();
for (reg int i=0;i<=n;i++) add(i+n+1,i+b[i],0); build(1,0,n);
for (reg int i=1;i<=n;i++) modify(1,0,n,i-a[i],i,i);
memset(dis,0x3f,sizeof(dis)); dis[n]=0; q.push_back(n);
while(!q.empty()){
reg int u=q.front(); q.pop_front();
if (vis[u]) continue; vis[u]=1;
for (auto it:G[u]) if (dis[it.first]>dis[u]+it.second){ reg int v=it.first;
dis[v]=dis[u]+it.second;
if (!vis[v]){if (it.second) q.push_back(v); else q.push_front(v); pre[v]=u;}
}
}
if (dis[0]>=INF) return !printf("-1");
printf("%lld\n",dis[0]);
vector<int> path; reg int now=0;
while(now!=n){
if (n+1<=now&&now<=2*n+1) path.push_back(now-n-1);
now=pre[now];
}
reverse(path.begin(), path.end());
for (auto x:path) printf("%lld ",x);
return 0;
}
C
首先注意到一个显然的结论,b 数组插入后一定是单调不降的
所以考虑将 b 排序,然后可以分治解决
定义函数 solve(l,r,L,R)
表示 \(b_l\) 到 \(b_r\) 将插入到 \(a_L\) 到 \(a_R\) 之前
考虑枚举 \(b_{mid}\) 所在的最优位置,这个容易通过前缀和后缀操作进行维护
最后重新构建整个数组,计算一下逆序对就好了,时间复杂度是 \(\Theta(n \log n)\)级别的
Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
reg int k=1,x=0;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
const int N=2e6+10,INF=1e18;
int n,m,a[N],b[N],p[N],pre[N],suf[N],d[N],tot,d_[N],tot_;
inline void solve(reg int l,reg int r,reg int L,reg int R){
if (l>r) return; reg int mid=l+r>>1;
pre[L]=0;for (reg int i=L+1;i<=R;i++) pre[i]=pre[i-1]+(a[i-1]>b[mid]);
suf[R]=0;for (reg int i=R-1;i>=L;i--) suf[i]=suf[i+1]+(b[mid]>a[i]);
p[mid]=L; for (reg int i=L+1;i<=R;i++) if (pre[p[mid]]+suf[p[mid]]>pre[i]+suf[i]) p[mid]=i;
solve(l,mid-1,L,p[mid]),solve(mid+1,r,p[mid],R);
}
vector<int> v[N];
struct BIT{
int c[N];
inline void clear(){for (reg int i=0;i<=tot_;i++) c[i]=0;}
inline void mdf(reg int x,reg int k){for (;x;x-=x&-x) c[x]+=k;}
inline int qry(reg int x){reg int res=0;for (;x<=tot_;x+=x&-x) res+=c[x];return res;}
}t;
inline int calc(){ tot_=tot; reg int ans=0;
for (reg int i=1;i<=tot;i++) d_[i]=d[i]; sort(d_+1,d_+tot_+1);
tot_=unique(d_+1,d_+tot_+1)-d_-1;
for (reg int i=1;i<=tot;i++) d[i]=lower_bound(d_+1,d_+tot_+1,d[i])-d_;
for (reg int i=1;i<=tot;i++){
ans+=t.qry(d[i]+1); t.mdf(d[i],1);
} t.clear();
return ans;
}
signed main(){
reg int _=read(); for (reg int qwq=1;qwq<=_;qwq++){
n=read(),m=read(),tot=0;
for (reg int i=1;i<=n;i++) a[i]=read();
for (reg int i=1;i<=n+1;i++) v[i].clear();
for (reg int i=1;i<=m;i++) b[i]=read(); sort(b+1,b+m+1); solve(1,m,1,n+1);
for (reg int i=1;i<=m;i++) v[p[i]].push_back(b[i]);
for (reg int i=1;i<=n+1;i++){
for (auto it:v[i]) d[++tot]=it; if (i<=n) d[++tot]=a[i];
}
printf("%lld\n",calc());
}
return 0;
}
D
题面
巧妙的贪心
考虑将输入按照 \(\max\{s,a\}\) 为第一关键字,\(s\) 为第二关键字排序
考虑这个贪心的正确性:
- \(\max\{s_{i+1},a_{i+1}=s_{i+1}\}\):即 \(i+1\) 的限制更松,那么无论此时 \(i\) 把山的高度更新到多高都对 \(i+1\) 的选取不造成影响
- \(\max\{s_{i+1},a_{i+1}=a_{i+1}\}\):即 \(i+1\) 更新后的条件更难满足,那么若 \(i+1\) 选了,\(i\) 一定不能选,那么让 \(i\) 先尝试能不能选一定不会更劣
- 最后考虑 \(\max\{s_i,a_i\}=\max\{s_{i+1},a_{i+1}\}\) 那么一定是可以攀登的先攀登,条件更严格的先上
Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
reg int k=1,x=0;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
inline int cmax(reg int x,reg int y){return x>y?x:y;}
const int N=2e6+10,INF=1e18;
int n,m,ans;
struct Node{
int s,a;
inline bool operator<(const Node &rhs)const{return cmax(s,a)<cmax(rhs.s,rhs.a)||cmax(s,a)==cmax(rhs.s,rhs.a)&&s<rhs.s;}
}p[N];
signed main(){
n=read(),m=read(); for (reg int i=1;i<=n;i++) p[i].s=read(),p[i].a=read(); sort(p+1,p+n+1);
for (reg int i=1;i<=n;i++) if (p[i].s>=m) ans++,m=cmax(m,p[i].a); printf("%lld",ans);
return 0;
}
E
首先一个贪心:对于一个询问的答案为
也就是一个前缀最小的前缀和
发现最后答案的段数与 \(r\) 没有太大的关系,考虑将询问按照 \(l \bmod k\) 分类,此时的 \(r\) 应更改为 \(l+\lfloor {r-l\over k} \rfloor k\)
之后考虑怎么处理不同起点前缀最小值,这是很经典问题,可以从后忘前单调栈处理。将 \(\bmod k\) 与 \(l\) 的位置提出为关键点,关键点之前再加入区间最小值 \(\min_{i=l+kx+1}^{l+k(x+1)-1} a_i\)
最后二分出 \(r\) 在单调栈哪个位置之后,前面的部分可以用前缀和处理得到
实现时要小心
Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
reg int k=1,x=0;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
inline int cmax(reg int x,reg int y){return x>y?x:y;}
const int N=2e6+10,INF=1e18;
int n,m,k,ans[N],st[N][20],lg[N],a[N];
struct Node{int l,r,id;inline bool operator<(const Node &rhs){return r<rhs.r;}}p[N];
vector<Node> v[N];
inline int chkmin(reg int x,reg int y){return a[x]<a[y]?x:y;}
inline int query(reg int l,reg int r){reg int len=lg[r-l+1];return chkmin(st[l][len],st[r-(1<<len)+1][len]);}
int cnt,b[N],s[N],top,sum[N];
inline int ask(reg int l,reg int r){if (l>r) return 0; return sum[r]-sum[l-1];}
inline int calc(reg int l,reg int r,reg int x){
reg int posl=l/k*k+x,posr=r/k*k+x; if (posl<l) posl+=k; if (posr>r) posr-=k;
if (posl>posr) return 0; return (posr-posl)/k+1;
}
signed main(){
n=read(),m=read(),k=read(); lg[0]=-1;
for (reg int i=1;i<=n;i++) st[i][0]=i,a[i]=read(),lg[i]=lg[i>>1]+1;
for (reg int i=1;i<=m;i++) p[i].l=read(),p[i].r=p[i].l+(read()-p[i].l)/k*k,p[i].id=i,v[p[i].l].push_back(p[i]);
for (reg int j=1;(1<<j)<=n;j++) for (reg int i=1;i+(1<<j)-1<=n;i++) st[i][j]=chkmin(st[i][j-1],st[i+(1<<j-1)][j-1]);
for (reg int d=1;d<=k;d++){ top=cnt=0;
for (reg int i=d;i<=n;i+=k){ b[++cnt]=i; reg int tmp;
if (i+1<=cmin(i+k,n)-1) tmp=query(i+1,cmin(i+k,n)-1),b[++cnt]=tmp;
}
for (reg int i=cnt;i>=1;i--){
while (top&&a[b[i]]<=a[s[top]]) top--; s[++top]=b[i];
sum[top]=sum[top-1]+calc(s[top],s[top-1]-1,d%k)*a[s[top]];
if (b[i]%k==d%k) for (auto it:v[b[i]]){
reg int l=1,r=top,pos;
while (l<=r){reg int mid=l+r>>1; s[mid]<=it.r?pos=mid,r=mid-1:l=mid+1;}
ans[it.id]=sum[top]-sum[pos]+a[s[pos]]*calc(s[pos],it.r,d%k);
}
}
}for (reg int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
F
题面
发现如果 \(n \le 10^6\) 是简单的,考虑折半搜索
首先转化一下答案,定义 \(b_i\) 为 \(i\) 的字典序,答案为 \(\sum_{i=1}^n b_i-i\)
我们将一个数 \(x\) 拆成 \(\overline{pq}\)
那么一个数的贡献就是 \(b_{\overline{p000000}}-\overline{p000000}+b_{q}-q\)
发现与 \(q\) 有关的计算是容易的,可以暴力地预处理
之后再枚举 \(p\),对于所有可能的 \(p\),对于 \(\overline{p000000}-\overline{p999999}\) 都合法且暴力的 \(p\) 用前缀和维护一下
复杂度为 \(\Theta(\sqrt n \log \sqrt n)\)
Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
reg int k=1,x=0;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();}
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
inline int cmax(reg int x,reg int y){return x>y?x:y;}
const int N=2e6+10,Mod=1e9+7,mod=998244353,B=1000000;
int n,idx,ans,sum[10];
vector<int> v[10];
inline int MOD(reg int x){return (x%mod+mod)%mod;}
inline void dfs1(reg int x,reg int len){
++idx,v[len].push_back(MOD(idx-x)); (sum[len]+=v[len].back())%=Mod;
if (len==6) return; for (reg int i=0;i<10;i++) dfs1(x*10+i,len+1);
}
inline void dfs2(reg int x){ if (x>n) return;
if (x*B+(B-1)<=n&&x*B*10>n){ reg int now=idx;
for (reg int i=0,mul=1;i<=6;i++,mul=mul*10){ reg int sz=v[i].size();
reg int d=MOD(now-x*mul),pos=lower_bound(v[i].begin(),v[i].end(),mod-d)-v[i].begin();
ans=(ans+d*sz+sum[i]-mod*(sz-pos))%Mod; idx+=sz;
} return;
}idx++;ans=(ans+MOD(idx-x))%Mod;
for (reg int i=0;i<10;i++) dfs2(x*10+i);
}
signed main(){
n=read(); dfs1(0,0); for (reg int i=0;i<=6;i++) sort(v[i].begin(),v[i].end());
idx=0; for (reg int i=1;i<=9;i++) dfs2(i); printf("%lld",(ans+Mod)%Mod);
return 0;
}