Codeforces 1696 / Codeforces Global Round 21
Contest Link
Problem D Permutation Graph
题意
给定排列 \(a=[a_1,a_2,\cdots,a_n]\),\(i\) 和 \(j\) 有一条无向边当且仅当 \(a_i,a_j\) 分别是区间 \([i,j]\) 的最大值和最小值,或者分别是区间 \([i,j]\) 的最小值和最大值。
求 \(1\) 到 \(n\) 的最短路。
\(T \leq 5\times 10^4,1 \leq n \leq 2.5 \times 10^5\)
题解
容易证明,最优解的路径中不存在 \(u \to v\) 使得 \(u > v\),即每一步都是往 \(n\) 的方向走的。具体证明时考虑给出一个类似 \(1 \to 3 \to 2 \to 4\) 的子路径结构,然后推出矛盾。
至此,我们可以设 \(f_i\) 表示 \(1\) 到 \(i\) 的最短路,然后使用 DP 转移。考虑哪些点可以被转移到。考虑维护两个单调栈,如果 \(a_i\) 作为区间最大值,那么可以作为区间最小值的位置是容易用递增栈维护的,但同时我们要考虑到,不能取站内的全局最小值,因为 \(a_i\) 作为区间最大值时,\(i\) 前面不能有比它更大的数。因此,我们需要维护一个递减栈,来找到 \(i\) 之前第一个比它大的位置。这样,计算 \(f_i\) 时,对栈中所有满足 \(x>i\) 的下标 \(x\) 的 \(f_x\) 求 min 再加上 \(1\) 就是答案。\(a_i\) 作为区间最小值同理。
将单调栈看成一个序列,我们对它进行的操作有:在结尾加入/删除,后缀求 min。可以用线段树维护,时间复杂度 \(O(n \log n)\)。
#include <bits/stdc++.h>
const int N=2.5e5+10,INF=0x3f3f3f3f;
int a[N],n,s1[N],s2[N],top1,top2;
int f[N];
struct SGT{
int mx[N<<2];
inline int lc(int x){
return x<<1;
}
inline int rc(int x){
return x<<1|1;
}
inline void update(int x){
mx[x]=std::min(mx[lc(x)],mx[rc(x)]);
return;
}
void build(int k,int l,int r){
if(l==r) return mx[k]=INF,void();
int mid=(l+r)>>1;
build(lc(k),l,mid),build(rc(k),mid+1,r),update(k);
return;
}
void change(int k,int l,int r,int x,int v){
if(l==r) return mx[k]=v,void();
int mid=(l+r)>>1;
if(x<=mid) change(lc(k),l,mid,x,v);
else change(rc(k),mid+1,r,x,v);
update(k);
return;
}
int query(int k,int l,int r,int L,int R){
if(L>R) return INF;
if(L<=l&&r<=R) return mx[k];
int mid=(l+r)>>1,res=INF;
if(L<=mid) res=std::min(res,query(lc(k),l,mid,L,R));
if(mid<R) res=std::min(res,query(rc(k),mid+1,r,L,R));
return res;
}
}t1,t2;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int main(void){
int T=read();
while(T--){
n=read();
for(int i=1;i<=n;++i) a[i]=read();
top1=top2=0;
t1.build(1,1,n),t2.build(1,1,n);
for (int i=1;i<=n;++i){
while(top1&&a[s1[top1]]>a[i]) --top1; // di zeng
while(top2&&a[s2[top2]]<a[i]) --top2; // di jian
int l1=std::upper_bound(s1+1,s1+1+top1,s2[top2])-s1;
int l2=std::upper_bound(s2+1,s2+1+top2,s1[top1])-s2;
if(i>1) f[i]=std::min(t1.query(1,1,n,l1,top1),t2.query(1,1,n,l2,top2))+1;
else f[i]=0;
s1[++top1]=i,s2[++top2]=i;
t1.change(1,1,n,top1,f[i]),t2.change(1,1,n,top2,f[i]);
}
printf("%d\n",f[n]);
}
return 0;
}
Problem E Placing Jinas
题意
平面上有 \(n\) 行格子,第 \(i(0 \leq i< n)\) 行有 \(a_i\) 个,一行的格子从 \(0\) 开始编号。保证 \(a_i \geq a_{i+1}\)。
现在第 \(0\) 行的第 \(0\) 个格子上有一个数 \(1\),其它格子的数都是 \(0\)。你可以进行以下操作任意次:
- 选择一个数字大于 \(0\) 的格子 \((i,j)\),将 \((i,j)\) 上的数减去 \(1\),将 \((i,j+1),(i+1,j)\) 上的数加上 \(1\),如果不存在则忽略
求使所有格子的数都变成 \(0\) 的最小操作数,对 \(10^9+7\) 取模。
\(1 \leq n \leq 2 \times 10^5,0 \leq a_i \leq 2 \times 10^5\)
题解
考虑很像杨辉三角,于是考虑组合意义。设 \(f_{i,j}\) 表示 \((i,j)\) 有一个 \(1\),需要变成 \(0\) 的最小步数,那么有 \(f_{i,j} = f_{i+1,j} + f_{i,j+1} + 1\),因为 \(1\) 变成 \(0\) 之后就分了两个 \(0\) 出去。它的组合意义是,从 \((i,j)\) 出发,往下走或往右走或不走,总的行走方案数。
因此,答案 \(f_{0,0}\) 就是从 \((0,0)\) 走到每一个格子的方案数。
因为 \(a_i\) 单调不增,那么从 \((0,0)\) 走到 \((x,y)\) 的方案数就是 \(\dbinom{x+y}{x}\)。
因此,答案等于
右边是一个上指标求和
# include <bits/stdc++.h>
const int N=400010,INF=0x3f3f3f3f,mod=1e9+7;
int n;
int a[N];
int fac[N],finv[N];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline int qpow(int d,int p){
int ans=1;
while(p){
if(p&1) ans=1ll*ans*d%mod;
p>>=1,d=1ll*d*d%mod;
}
return ans;
}
inline int binom(int n,int m){
int res=(n<m)?0:(1ll*fac[n]*finv[m]%mod*finv[n-m]%mod);
return res;
}
int main(void){
n=read();
fac[0]=1;
for(int i=1;i<=N-10;++i) fac[i]=1ll*fac[i-1]*i%mod;
finv[N-10]=qpow(fac[N-10],mod-2);
for(int i=N-11;i>=0;--i) finv[i]=1ll*finv[i+1]*(i+1)%mod;
int ans=0;
for(int i=0;i<=n;++i) a[i]=read(),ans=(ans+binom(a[i]+i,i+1))%mod;
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号