【考试总结】2022-06-07
吃
将 \(a_i=1\) 的 \(b_i\) 先进行累加得到 \(S\),剩下的 \(b_i\) 最多加一个,因为此时 \(a_i\ge 2\),加两个不如加较大的一个再做乘法
设 \(K=\prod_{i\in\{a_i\ge 2\}} a_i\) 那么枚举对哪个元素执行加法,答案就是 \(\max\left\{K\frac{S+b_i}{a_i}\right\}\)
Code Display
const int N=5e5+10;
int n,mxpos;
pair<int,int> a[N];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i].fir);
for(int i=1;i<=n;++i) scanf("%lld",&a[i].sec);
int S=1;
for(int i=1;i<=n;++i) if(a[i].fir==1) S+=a[i].sec;
long double mx=S;
for(int i=1;i<=n;++i) if(a[i].fir!=1){
long double cur=1.0*(S+a[i].sec)/a[i].fir;
if(cur>mx) mx=cur,mxpos=i;
}
int ans=S;
for(int i=1;i<=n;++i) if(mxpos==i) ans+=a[i].sec;
ans%=mod;
for(int i=1;i<=n;++i) if(a[i].fir!=1&&mxpos!=i) ans=ans*a[i].fir%mod;
printf("%lld\n",ans);
return 0;
}
题
发现逆序对数为奇数的序列只有 \(\{1,3,2\},\{2,1,3\},\{3,2,1\}\) ,那么每个元素可以匹配进其中的一个集合中,将集合的所有匹配长度的数量压到状态中进行转移即可
注意如果数量大于 \(n\) 那么一定不合法,同时如果后面的数字数量不足以将当前这些集合填满也要进行剪枝
使用 __gnu_pbds::cc_hash_table<int,int>
并将状态变成 \(20\) 进制进行存储即可
Code Display
__gnu_pbds::cc_hash_table<int,int> mp[60];
int n,pw[20];
inline vector<int> decode(int S){
vector<int> sta;
for(int i=1;i<=6;++i) sta.emplace_back(S%20),S/=20;
return sta;
}
char s[100];
inline bool can(int pos,int a){return s[pos]-'0'==a||s[pos]=='0';}
bool mark=0;
int fac[30];
signed main(){
//freopen("problem.in","r",stdin); freopen("problem.out","w",stdout);
pw[0]=fac[0]=1;
rep(i,1,6) pw[i]=pw[i-1]*20;
rep(i,1,19) fac[i]=fac[i-1]*i%mod;
int T; scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
scanf("%s",s+1);
rep(i,1,3*n+1) mp[i].clear();
mp[1][0]=1;
for(int i=1;i<=3*n;++i){
for(auto Sta:mp[i]){
int S=Sta.first,v=Sta.second%mod;
if(!v) continue;
vector<int> sta=decode(S);
int ned=(sta[0]+sta[2]+sta[4])*2+sta[1]+sta[3]+sta[5];
if(3*n-i+1<ned) continue;
if(can(i,1)){
if(sta[0]<n) mp[i+1][S+pw[0]]+=v;
if(sta[3]) mp[i+1][S-pw[3]]+=sta[3]*v;
if(sta[4]) mp[i+1][S-pw[4]+pw[5]]+=sta[4]*v;
}
if(can(i,2)){
if(sta[1]) mp[i+1][S-pw[1]]+=sta[1]*v;
if(sta[2]) mp[i+1][S-pw[2]+pw[3]]+=sta[2]*v;
if(sta[4]<n) mp[i+1][S+pw[4]]+=v;
}
if(can(i,3)){
if(sta[0]) mp[i+1][S-pw[0]+pw[1]]+=sta[0]*v;
if(sta[2]<n) mp[i+1][S+pw[2]]+=v;
if(sta[5]) mp[i+1][S-pw[5]]+=sta[5]*v;
}
}
}
printf("%lld\n",fac[n]*mp[3*n+1][0]%mod);
}
return 0;
}
盒
设 \(\displaystyle S_i=\sum_{j=1}^i a_j\) 并简记 \(S=S_n\)
移动次数本质是前缀和的差的绝对值,枚举 \(b\) 序列的前缀和可以得到如下算式:
单独考察每个 \(i\) ,将绝对值展开可以得到:
考虑加号后半部分,将括号展开后考虑并用吸收恒等式将 \(j\) 吸入组合数中
注意到从 \((0,0)\) 到 \((n,m)\) 的路径条数有另一个计算方式,即枚举在第 \(i\) 行/列经过的另一维度的下标,那么套用之可以化简得到:
即减号前是从 \((0,0)\) 走到 \((n,S-1)\) ,减号后走到的是 \((n-1,S)\)
直接套入每个 \(i\) 的 \(w_i\) 的系数表达式的加号前一部分发现这就是钦定经过某行/列时另一维度标号小于某值的方案数
此时使用组合意义可以将 \(p\leftarrow p+1,q\leftarrow q+1\),也就是在指针 \(p,q\) 变化的过程中添加从第 \(q+1\) 行走或者减去从第 \(p\) 行走的方案即可
在实际问题的解决中 \(i,s_i\) 都是不降的,均摊复杂度到了 \(\Theta(n+m)\)
Code Display
const int N=3e6+10;
int fac[N],ifac[N],n;
inline int binom(int n,int k){return n<k?0:mul(mul(fac[n],ifac[k]),ifac[n-k]);}
struct Calculator{
int p,q,n,m,res;
inline void init(int N,int M){
n=N; m=M;
p=q=0;
res=binom(n+m-1,n-1);
}
inline void move(int x,int y){
while(q<y){
++q;
ckadd(res,mul(binom(p+q,p),binom(n+m-p-q-1,m-q)));
}
while(p<x){
++p;
ckdel(res,mul(binom(p+q,p),binom(n+m-p-q-1,n-p)));
}
return ;
}
}calc1,calc2;
signed main(){
n=3e6; fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
ifac[n]=ksm(fac[n],mod-2);
for(int i=n;i>=1;--i) ifac[i-1]=mul(ifac[i],i);
int T; scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
vector<int>a(n+1),w(n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
int ans=0;
calc1.init(n-1,a[n]);
calc2.init(n,a[n]-1);
for(int i=1;i<n;++i){
scanf("%lld",&w[i]);
int delt=0;
delt+=i*binom(n+a[n]-1,a[n]-1);
delt-=a[i]*binom(n+a[n]-1,n-1);
if(a[i]){
calc1.move(i-1,a[i]);
calc2.move(i,a[i]-1);
delt+=2*(a[i]*calc1.res-i*calc2.res);
}
delt=(delt%mod+mod)%mod;
ckadd(ans,mul(delt,w[i]));
}
printf("%lld\n",ans);
}
return 0;
}
串
答案的下界就是让 \(T_i=S[i,2i]\),数量自然是 \(\left\lfloor\frac{n}2\right\rfloor\) 个
但是对于一个在 \(S\) 中出现超过一次时的子串 \(S[l,r]\) 可以不论长度得将数量加到 \(r-l+1\) ,这个过程可以倒过来考察: 从较右的出现 \([c,d]\) 开始缩头尾,如果头到了 \(a\) 那么再挪到 \(c\) 继续缩
那么缩完了还可以再扩展到 \(n\) ,所以找到最靠左的一次结尾位置 \(r\) 即可得到结果 \(len+\dfrac{n-r}2\)
使用 \(\rm SAM\) 进行上述过程的维护即可,注意特判没有子串出现两次的情况
Code Display
const int N=1e6+10;
vector<int> G[N];
char s[N];
int son[N][26],len[N],fa[N],pos[N],siz[N],tot=1,las=1,n;
inline void extend(int x){
int tmp=las,np=las=++tot;
pos[np]=len[np]=len[tmp]+1; siz[np]=1;
while(tmp&&!son[tmp][x]) son[tmp][x]=np,tmp=fa[tmp];
if(!tmp) return fa[np]=1,void();
int q=son[tmp][x];
if(len[q]==len[tmp]+1) return fa[np]=q,void();
int clone=++tot; len[clone]=len[tmp]+1;
fa[clone]=fa[q]; fa[q]=fa[np]=clone;
rep(i,0,25) son[clone][i]=son[q][i];
while(son[tmp][x]==q) son[tmp][x]=clone,tmp=fa[tmp];
return ;
}
int main(){
int T; scanf("%d",&T);
while(T--){
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i) extend(s[i]-'a');
for(int i=2;i<=tot;++i) G[fa[i]].emplace_back(i);
int ans=n/2;
function<void(int)>dfs=[&](int x){
for(auto t:G[x]){
dfs(t);
siz[x]+=siz[t];
if(!pos[x]||pos[x]>pos[t]) pos[x]=pos[t];
}
if(siz[x]>1) ckmax(ans,(n-pos[x])/2+len[x]);
return ;
};
dfs(1);
printf("%lld\n",ans);
rep(i,1,tot){
G[i].clear();
rep(j,0,25) son[i][j]=0;
siz[i]=fa[i]=len[i]=pos[i]=0;
}
tot=las=1;
}
return 0;
}