• 【考试总结】20220607


    \(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\) 序列的前缀和可以得到如下算式:

    \[\sum_{i=1}^{n-1}w_i\sum_{s=0}^S\binom{i+s-1}{i-1}\binom{S-s+n-i-1}{n-i-1}|s-S_i| \]

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

    \[2\sum_{s=0}^{S_i}(s_i-s)\binom{i+s-1}{i-1}\binom{S-s+n-i-1}{n-i-1}+\sum_{s=0}^{S}(s-s_i)\binom{i+s-1}{i-1}\binom{S-s+n-i-1}{n-i-1} \]

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

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

    \[\sum_{s=0}^{S}(s-s_i)\binom{i+s-1}{i-1}\binom{S-s+n-i-1}{n-i-1}=i\binom{n+S-1}{S-1}-S_i\binom{S+n-1}{n-1} \]

    即减号前是从 \((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;
    }
    

  • 相关阅读:
    一个接口的性能问题定位和分析过程
    HTTP请求全过程(很全面)
    Linux中查看物理CPU个数、核数、逻辑CPU个数
    linux查看文件大小
    ping不通判断系统是否开机
    ping不通判断系统是否开机
    linux压缩解压文件命令
    python连接redis集群,添加数据
    初学python
    企业级BI为什么这么难做?
  • 原文地址:https://www.cnblogs.com/yspm/p/LNOI2022.html
Copyright © 2020-2023  润新知