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