【考试总结】2022-06-07

ai=1bi 先进行累加得到 S,剩下的 bi 最多加一个,因为此时 ai2,加两个不如加较大的一个再做乘法

K=i{ai2}ai 那么枚举对哪个元素执行加法,答案就是 max{KS+biai}

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

Si=j=1iaj 并简记 S=Sn

移动次数本质是前缀和的差的绝对值,枚举 b 序列的前缀和可以得到如下算式:

i=1n1wis=0S(i+s1i1)(Ss+ni1ni1)|sSi|

单独考察每个 i ,将绝对值展开可以得到:

2s=0Si(sis)(i+s1i1)(Ss+ni1ni1)+s=0S(ssi)(i+s1i1)(Ss+ni1ni1)

考虑加号后半部分,将括号展开后考虑并用吸收恒等式将 j 吸入组合数中

注意到从 (0,0)(n,m) 的路径条数有另一个计算方式,即枚举在第 i 行/列经过的另一维度的下标,那么套用之可以化简得到:

s=0S(ssi)(i+s1i1)(Ss+ni1ni1)=i(n+S1S1)Si(S+n1n1)

即减号前是从 (0,0) 走到 (n,S1) ,减号后走到的是 (n1,S)

直接套入每个 iwi 的系数表达式的加号前一部分发现这就是钦定经过某行/列时另一维度标号小于某值的方案数

此时使用组合意义可以将 pp+1,qq+1,也就是在指针 p,q 变化的过程中添加从第 q+1 行走或者减去从第 p 行走的方案即可

在实际问题的解决中 i,si 都是不降的,均摊复杂度到了 Θ(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;
}

答案的下界就是让 Ti=S[i,2i],数量自然是 n2

但是对于一个在 S 中出现超过一次时的子串 S[l,r] 可以不论长度得将数量加到 rl+1 ,这个过程可以倒过来考察: 从较右的出现 [c,d] 开始缩头尾,如果头到了 a 那么再挪到 c 继续缩

那么缩完了还可以再扩展到 n ,所以找到最靠左的一次结尾位置 r 即可得到结果 len+nr2

使用 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;
}

posted @   没学完四大礼包不改名  阅读(403)  评论(23编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示