Loading

Codeforces 1696 / Codeforces Global Round 21

Codeforces Global Round 21

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}\)

因此,答案等于

\[\sum \limits_{i=0}^n \sum \limits_{j=0}^{a_i-1} \binom {i+j}{i} \]

右边是一个上指标求和

\[\sum \limits_{i=0}^{n} \dbinom{a_i+i}{i+1} \]

# 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;
}
posted @ 2023-02-21 20:57  Meatherm  阅读(34)  评论(0)    收藏  举报