【2023.11.08】NOIP2023模拟试题-30

发布时间 2023-11-09 09:43:41作者: DZhearMins

前言

数论迎我归,数学送我葬

组合数学不容易,又有 DP 当

T3 刚爆零,T4 又遭殃

OI 路上怅前望,且行且彷徨

T1 最大公约数

T1 应该想一想就会,接下来我们讨论是怎么减去他的复杂度的。

题目的关键在于,如果根据给出的 \(a\) 推出 \(\gcd\) 的话,就会有 \(9\times 10^{10}\) 条推导关系。

而如果根据给出的 \(\gcd\) 反向找 \(a\) 的话,就只有 $10^6 \log 10^6 $ (均摊 \(\log n\) ) 条推导关系。这就是为什么反向推导会更简单的原因。

#include<bits/stdc++.h>
using namespace std;
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define ll long long
#define ly __int128
#define lp ll
#define lson(x) ((x)<<1)
#define rson(x) (lson(x)|1)
#define Lson(x) tr[lson(x)]
#define Rson(x) tr[rson(x)]
#define mod %P
#define N 300005
int n,kk,cur,amax;
struct Pi{int x,y;bool operator < (const int b){return x<b;}}a[N];
bool operator < (const int x,Pi y){return x<y.x;}
bool Pi_cmp_by_x(Pi x,Pi y){return (x.x==y.x)?x.y<y.y:x.x<y.x;}
int main(){
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%d %d",&n,&kk);
	fi(1,n){
		scanf("%d",&a[i].x);
		a[i].y=i;
		amax=max(amax,a[i].x);
	}
	sort(a+1,a+n+1,Pi_cmp_by_x);
	for(cur=amax;cur>=1;--cur){//分别枚举每一个 gcd
		int f1=n+1,f2=0;Pi*j=a;
		for(int i=cur;i<=amax;i+=cur){//在 a 中查找有没有 gcd 的倍数
			j=lower_bound(j,a+n+1,i);
			if(j==a+n+1)break;
			if(j->x==i){
				f1=min(f1,j->y),f2=max(f2,j->y);
				j=upper_bound(j+1,a+n+1,i)-1;
				f1=min(f1,j->y),f2=max(f2,j->y);//记录满足条件的最小和最大下标
			}
		}
		if(f1+kk<=f2){//如果存在
			printf("%d",cur);
			return 0;
		}
	}
	printf("1\n");
	return 0;
}

T2 子序列

我们可以把题目转化成长度为 \(n\) 的删除序列有多少种,两种不同当且仅当有至少一个删除操作删除的字符不同。

然后我们发现左边无论怎么删都跟我右边没有关系,所以考虑分治。

先考虑

:

我们钦定只考虑 \([l,r]\) 区间内的数的删除方式有 \(f_{l,r}\) 种。

↓一个长度为11的01串

长度为11的01串

我们随便来一组分治,然后假定 \(L,R\) 区间已经搞定被删除掉了,即将要删除 \(this\) 所在的字符:

长度为11的分治01串

剩下的串是 1011,要删除第一个 1 ,合规。

我们再考虑这样的分治

长度为11的分治01串2

我们发现将要删除一个 0,但删这个 0 和删红色 0 最终得到的删除序列是一样的,这个时候我们钦定每次只删连续区间里最后一个字符来避免重复。

if (r==n||s[i]!=s[r+1])
	ans=(ans+1ll*C[r-l][i-l]*dfs(l,i-1)%mod*dfs(i+1,r))%mod;

这里的 if 语句就是用来干这个的(在最长的序列里不会重复所以所有字符都可以删除)

我们发现分治到 \(l<r\) 的情况可以直接算为 \(1\) ,而 \(l=r\) 的情况有可能是 \(0\) (如果和后面的第一个没删除的字符相同),但也有可能是 \(1\) (如果和后面的第一个没删除的字符不同)

然后我们考虑

以下的红色数字是 \(L,R\) 两个区间的分别的任意一组删除序列

长度为3或4的01串删除序列

考虑和归并排序一样的合并方法,容易知道一共有 \(C_{|L|+|R|}^{|L|}\) 种方式来按顺序合并两个序列。中间的 1 是最后删。举个例子,合并过后的一种删除序列长这样:

长度为7的01串删除序列

到这里这道题就做完了,以下是完整代码:

(因为有的 \(f\) 算出来可能是 \(0\) , 所以 \(f\) 要初始化成 \(-1\))

#include<bits/stdc++.h>
using namespace std;
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define ll long long
#define ly __int128
#define lp ll
#define lson(x) ((x)<<1)
#define rson(x) (lson(x)|1)
#define Lson(x) tr[lson(x)]
#define Rson(x) tr[rson(x)]
#define P 1000000007
#define mod %P
#define N 405
ll f[N][N],C[N][N];
int n,s[N];
ll getf(int l,int r){
	if(l>r)return 1;
	if(f[l][r]!=-1)return f[l][r];
	ll ret=0;
	fi(l,r)
		if(n==r||s[i]!=s[r+1])ret=(ret+getf(l,i-1)*getf(i+1,r)%P*C[r-l][r-i]%P)%P;
	return f[l][r]=ret%P;
}
int main(){
	memset(f,-1,sizeof f);
	freopen("sub.in","r",stdin);
	freopen("sub.out","w",stdout);
	scanf("%d",&n);
	char ch=getchar();while(ch!='0'&&ch!='1')ch=getchar();
	fi(1,n){
		s[i]=ch-'0';
		ch=getchar();
	}
	fi(0,n){
		C[i][0]=1;
		ff(j,1,i)C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
	}
	printf("%lld\n",getf(1,n)%P);
	return 0;
}