LNOI2022 Solution Set

T1. 吃

先选 \(a_i=1\) 的全部加上,剩下的只会选一个加(如果加两个,一定不如先加大的然后乘起来)。

比较大小可以取 \(\log\) 然后相加。

/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
LL read()
{
	LL x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(LL x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const LL MOD=1e9+7;
inline LL Add(LL u,LL v){return u+v>=MOD?u+v-MOD:u+v;}
inline LL Sub(LL u,LL v){return u-v>=0?u-v:u-v+MOD;}
inline LL Mul(LL u,LL v){return LL(u)*LL(v)%MOD;}
inline LL add(LL &u,LL v){return u=Add(u,v);}
inline LL sub(LL &u,LL v){return u=Sub(u,v);}
inline LL mul(LL &u,LL v){return u=Mul(u,v);}
LL QuickPow(LL x,LL p=MOD-2)
{
	if(p<0)	p+=MOD-1;
	LL ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
LL n;
LL a[500005],b[500005];
DB A[500005];
int main(){
// 	freopen("food.in","r",stdin);
// 	freopen("food.out","w",stdout);
	n=read();
	for(LL i=1;i<=n;++i)	a[i]=read();
	for(LL i=1;i<=n;++i)	b[i]=read();
	LL ret=1;
	for(LL i=1;i<=n;++i)	if(a[i]==1)	ret+=b[i];
	DB ano=0;
	for(LL i=1;i<=n;++i)	if(a[i]^1)	A[i]=log(a[i]),ano+=A[i];
	DB maxn=log(ret);
	for(LL i=1;i<=n;++i)	if(a[i]^1)	maxn+=A[i];
	LL p=0;
	for(LL i=1;i<=n;++i)
	{
		if(a[i]==1)	continue;
		DB c=log(ret+b[i]);
		if(c+ano-A[i]>maxn)	maxn=c+ano-A[i],p=i;
	}
	if(p==0)
	{
		ret%=MOD;
		for(LL i=1;i<=n;++i)	mul(ret,a[i]);
		write(ret);
	}
	else
	{
		ret%=MOD;
		add(ret,b[p]);
		for(LL i=1;i<=n;++i)	if(i^p)	mul(ret,a[i]);
		write(ret);
	}
	return 0;
}

T2. 题

注意到只有 \(\{1,3,2\}, \{2,1,3\}, \{3,2,1\}\) 逆序对是奇数的,并且 \(n\) 很小,我们把这三个逆序对的六个真前缀全部考虑到 DP 定义当中,即定义 \(dp_{i,p_1,p_2,p_3,p_{13},p_{21},p_{32}}\) 表示当前考虑了前 \(i\) 个位置,有 \(p_1\) 个暂时只凑出了 \(\{1\}\),以此类推。

暴力转移即可。注意到有很多状态并不需要(比如某一个 \(p\) 已经上溢了,或者是不可能达成条件了等等),可以用一堆 if 语句处理掉。

/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
const int MOD=1e9+7;
inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
inline int add(int &u,int v){return u=Add(u,v);}
inline int sub(int &u,int v){return u=Sub(u,v);}
inline int mul(int &u,int v){return u=Mul(u,v);}
int QuickPow(int x,int p=MOD-2)
{
	if(p<0)	p+=MOD-1;
	int ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
void Solve();
int main(){
//	freopen("problem3.in","r",stdin);
//	freopen("problem.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T-->0)	Solve();
	return 0;
}
int n;
char s[65];
int dp[2][20][20][20][20][20][20];
/*
1 2 3
13 21 32

132
213
321
*/
void Solve()
{
	scanf("%d",&n);
	int N=3*n;
	scanf("%s",s+1);
	memset(dp,0,sizeof dp);
	dp[0][0][0][0][0][0][0]=1;
	for(int i=1;i<=N;++i)
	{
		int lst=(i-1)&1,u=i&1;
		int ret=N-i+1;
		int up=min(i-1,n),tk=ret;
		for(int p13=0;p13<=up;++p13)
		{
			if(p13*2>i-1)	break;
			if(p13>tk)	break;
			for(int p21=0;p21<=up;++p21)
			{
				if(p13*2+p21*2>i-1)	break;
				if(p13+p21>tk)	break;
				for(int p32=0;p32<=up;++p32)
				{
					if(p13*2+p21*2+p32*2>i-1)	break;
					if(p13+p21+p32>tk)	break;
					for(int p1=0;p1<=up;++p1)
					{
						if(p13*2+p21*2+p32*2+p1>i-1)	break;
						if(p1*2+p13+p21+p32>tk)	break;
						for(int p2=0;p2<=up;++p2)
						{
							if(p13*2+p21*2+p32*2+p1+p2>i-1)	break;
							if(p1*2+p2*2+p13+p21+p32>tk)	break;
							for(int p3=0;p3<=up;++p3)
							{
								if(p1+p2+p3+2*p21+2*p32+2*p13>i-1)	break;
								if(p1*2+p2*2+p3*2+p13+p21+p32>tk)	break;
								int c=dp[lst][p1][p2][p3][p13][p21][p32];
								if(!c)	continue;
								if(s[i]=='1' || s[i]=='0')
								{
									if(p1<n)	add(dp[u][p1+1][p2][p3][p13][p21][p32],c);
									if(p2 && p21<n)	add(dp[u][p1][p2-1][p3][p13][p21+1][p32],Mul(c,p2));
									if(p32)	add(dp[u][p1][p2][p3][p13][p21][p32-1],Mul(c,p32));
								}
								if(s[i]=='2' || s[i]=='0')
								{
									if(p2<n)	add(dp[u][p1][p2+1][p3][p13][p21][p32],c);
									if(p3 && p32<n)	add(dp[u][p1][p2][p3-1][p13][p21][p32+1],Mul(c,p3));
									if(p13)	add(dp[u][p1][p2][p3][p13-1][p21][p32],Mul(c,p13));
								}
								if(s[i]=='3' || s[i]=='0')
								{
									if(p3<n)	add(dp[u][p1][p2][p3+1][p13][p21][p32],c);
									if(p1 && p13<n)	add(dp[u][p1-1][p2][p3][p13+1][p21][p32],Mul(c,p1));
									if(p21)	add(dp[u][p1][p2][p3][p13][p21-1][p32],Mul(c,p21));
								}
								dp[lst][p1][p2][p3][p13][p21][p32]=0;
							}
						}
					}
				}
			}
		}
	}
	int ans=dp[N&1][0][0][0][0][0][0];
	for(int i=1;i<=n;++i)	mul(ans,i);
	printf("%d\n",ans);
}

T3. 盒

显然对每一个 \(i\)\(i \to i+1\)\(i \gets i+1\) 的次数,这个就跟 \(a\) 的前缀和 \(b\) 的前缀差的绝对值有关系。

那就枚举这个东西,拆了之后会形成一种数路径格点限制在第 \(i\) 列时在 \(j\) 行以内,等价于在 \(i+1\) 列之后突破 \(j\) 行。

只是比较麻烦啊,推并不难推,不知道为啥想着就很痛苦阿。

/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9')	f=(c=='-'?-1:f),c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x*f;
}
void write(int x)
{
	if(x<0)	putchar('-'),x=-x;
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int MOD=998244353;
inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
inline int add(int &u,int v){return u=Add(u,v);}
inline int sub(int &u,int v){return u=Sub(u,v);}
inline int mul(int &u,int v){return u=Mul(u,v);}
int QuickPow(int x,int p=MOD-2)
{
	if(p<0)	p+=MOD-1;
	int ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
int fac[3000005],ifac[3000005];
inline int C(int n,int m){return n<m || m<0?0:Mul(fac[n],Mul(ifac[m],ifac[n-m]));}
void Solve();
int main(){
//	freopen("box2.in","r",stdin);
//	freopen("box.out","w",stdout);
	fac[0]=1;
	for(int i=1;i<=3000000;++i)	fac[i]=Mul(fac[i-1],i);
	ifac[3000000]=QuickPow(fac[3000000]);
	for(int i=2999999;~i;--i)	ifac[i]=Mul(ifac[i+1],i+1);
	int T=read();
	while(T-->0)	Solve();
	return 0;
}
struct F{
	int n,m,i,k;
	int ret;
	inline void init(int N,int M){i=k=ret=0,n=N,m=M,ret=C(n+m-1,m);}
	int calc(int I,int K)
	{
		if(I<0 || K<0)	return 0;
		while(i<I)	++i,sub(ret,Mul(C(i+k,i),C(n-i+m-k-1,m-k-1)));
		while(k<K)	++k,add(ret,Mul(C(i+k,i),C(n-i+m-k-1,n-i-1)));
		return ret;
	}
}f,g;
int n,sum[500005],w[500005];
void Solve()
{
	n=read();
	for(int i=1;i<=n;++i)	sum[i]=read()+sum[i-1];
	int S=sum[n];
	for(int i=1;i<n;++i)	w[i]=read();
	int ans=0;
	for(int i=1;i<n;++i)
	{
		int coe=Sub(Mul(i,C(n+S-1,n)),Mul(sum[i],C(n+S-1,S)));
		add(ans,Mul(w[i],coe));
	}
	f.init(n-1,S),g.init(n,S-1);
	for(int i=1;i<n;++i)
	{
		int coe=Sub(Mul(sum[i],f.calc(i-1,sum[i])),Mul(i,g.calc(i,sum[i]-1)));
		add(coe,coe);
		add(ans,Mul(w[i],coe));
	}
	write(ans),puts("");
}

T4. 串

先读懂题目,看懂这个字符串序列到底在干什么。容易发现答案的下界是 \(\left\lfloor \dfrac{|s|}{2} \right\rfloor\),大概是考虑 \(T_1 = S[2], T_2 = S[3\dots 4],T_3 = S[4 \dots 6],\cdots\),这样 \(T_i\) 的右端点在 \(2i\),可以发现答案下界是 \(\left\lfloor \dfrac{|s|}{2} \right\rfloor\)

然后考虑这样一个事情,答案要比下界大,我们应该在某一个时刻往回跳。假设选完 \(T_i\) 之后往回跳了,需要满足 \(T_i\) 这个串在 \(S\) 里出现两次(可以重叠),思考之后发现这是充要条件:

  • 首先如果一个串只出现一遍,怎么可能可以往回跳(充分性);
  • 然后 \(T_i\)\(S\) 里出现两次,记两次出现的位置的左端点为 \(l_1,l_2\)
    • 如果两个串没有重叠:考虑倒推,\(T_i\) 的右端点向左移两位,左端点向左移一位,需要满足 \(l_1 \geq r-l+2\),因为两串不重叠,因此 \(l_1 \geq r-l+2\) 显然;
    • 如果两个串重叠:仍然考虑倒推,但是前面没有足够的空位,注意到我们跳着跳着就跳成了 \(T_i\) 的一个前缀,这样可以往后回跳,直到回到 \(T_0\)

因此这个结论是充要的。我们只关心最后一次回跳,那么记最后一次回跳回到了位置 \([l,r]\),答案就是 \(r-l+1 + \left\lfloor\dfrac{n-r}{2}\right\rfloor\)。显然我们选 \(r\) 最小的就好了。

这个东西可以很简单的用后缀自动机维护。在每个实结点上记下对应的字符编号,虚结点记为无穷大,最后每个点对应的字符串集合第一次出现的位置的右端点为 Parent 树子树内的最小值。出现次数就是子树内实结点个数。判断即可。

/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
/* 
	答案下界 n/2
	注意到如果一个串出现了两次,那么一定可以往回跳。
	然后剩下的直接按构造下界的方式做。
	sam 维护每个串第一次出现的右端点在哪儿。
*/
void Solve();
int main(){
	freopen("string3.in","r",stdin);
	int T;
	scanf("%d",&T);
	while(T-->0)	Solve();
	return 0;
}
struct SAM{
	int fa[1000005],len[1000005],siz[1000005];
	int ch[1000005][26];
	int R[1000005];
	int cnt,lst;
	void init()
	{
		for(int i=1;i<=cnt;++i)	fa[i]=0,memset(ch[i],0,sizeof ch[i]),len[i]=siz[i]=R[i]=0;
		cnt=lst=1;
	}
	void extend(int c,int id)
	{
		int p=lst,cur=++cnt;
		len[cur]=len[p]+1;
		R[cur]=id;
		lst=cur;
		while(!ch[p][c])	ch[p][c]=cur,p=fa[p];
		if(!p)	fa[cur]=1;
		else
		{
			int q=ch[p][c];
			if(len[q]==len[p]+1)	fa[cur]=q;
			else
			{
				int clone=++cnt;
				len[clone]=len[p]+1;
				memcpy(ch[clone],ch[q],sizeof ch[q]);
				fa[clone]=fa[q];
				fa[q]=fa[cur]=clone;
				while(ch[p][c]==q)	ch[p][c]=clone,p=fa[p];
			}
		}
		siz[cur]=1;
	}
}sam;
char s[500005];
int fa[1000005],siz[1000005],len[1000005],R[1000005];
int n,N;
vector<int> G[1000005];
void dfs(int u){for(auto v:G[u])	dfs(v),siz[u]+=siz[v],R[u]=min(R[u],R[v]);}
void Solve()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	sam.init();
	for(int i=1;i<=n;++i)	sam.extend(s[i]-'a',i);
	N=sam.cnt;
	for(int i=1;i<=N;++i)	siz[i]=sam.siz[i],fa[i]=sam.fa[i],len[i]=sam.len[i],R[i]=(sam.R[i]==0?n+1:sam.R[i]);
	for(int i=1;i<=N;++i)	G[i].clear();
	for(int i=2;i<=N;++i)	G[fa[i]].push_back(i);
	dfs(1);
	int ans=0;
	for(int i=1;i<=N;++i)	if(siz[i]>=2)	ans=max(ans,len[i]+(n-R[i])/2);
	printf("%d\n",ans);
}
posted @ 2022-06-08 08:31  SyadouHayami  阅读(59)  评论(0编辑  收藏  举报

My Castle Town.