• LNOI 2022 题目选做


    题目描述

    点此看题

    解法

    考虑计算每个分界线的贡献,设 \(s_i=\sum_{j=1}^i a_j,m=s_n\),那么答案可以写成:

    \[\sum_{i=1}^{n-1}w_i\sum_{j=0}^m|s_i-j|\cdot{i+j-1\choose i-1}\cdot {n-i+m-j-1\choose n-i-1} \]

    首先把绝对值拆开,就有(我们把外层去掉,只推导内层的式子):

    \[2\sum_{j=0}^{s_i}(s_i-j)\cdot{i+j-1\choose i-1}\cdot {n-i+m-j-1\choose n-i-1}+\sum_{j=0}^{m}(j-s_i)\cdot{i+j-1\choose i-1}\cdot {n-i+m-j-1\choose n-i-1} \]

    前面和后面的区别仅仅只有 \(j\) 枚举的上界,我们首先推导后面的式子,把 \((j-s_i)\) 拆开:

    \[s_i\sum_{j=0}^m{i+j-1\choose i-1}\cdot{n-i+m-j-1\choose n-i-1}=s_i\cdot {n+m-1\choose n-1} \]

    这是因为有组合意义:从 \(n+m-1\) 个数中选取 \(n-1\) 个,强制第 \(i\) 个数的位置是 \(j\)

    \[\begin{aligned} &\sum_{j=0}^{m}j{i+j-1\choose i-1}\cdot {n-i+m-j-1\choose n-i-1}\\ =&\sum_{j=0}^mi{i+j-1\choose j-1}\cdot {n-i+m-j-1\choose n-i-1}\\ =&\sum_{j=0}^mi{i+j-1\choose i}\cdot {n-i+m-j-1\choose n-i-1}\\ =&i\cdot {n-m-1\choose n} \end{aligned} \]

    那么后面的部分我们就成功解决了,它们竟然可以化简成单项组合数的形式!


    现在我们来解决前面的部分,我只讲解怎么处理下面的式子,另一个可以自己推导:

    \[\sum_{j=0}^{s_i}{i+j-1\choose i-1}\cdot {n-i+m-j-1\choose n-i-1} \]

    我们考虑拿两个指针来维护 \(i\)\(s_i\) 的变化,因为它们都是单增的,所以我们只需要右移指针,就可以以 \(O(n+S)\) 的时间处理所有位置的和式。

    考虑增大 \(s_i\) 是容易的,只需要增加一个单项式即可。增大 \(i\) 却会导致很多的变化,此时我们再来考察组合意义。

    \({n+m-1\choose n-1}\) 的选取方案是 \(p_1< p_2....< p_{n-1}\),那么限定上界就相当于强制 \(p_i\leq i+s_i\),当我们增大 \(i\) 的时候,限制变成了 \(p_{i+1}\leq i+s_i+1\),由于 \(p_i<p_{i+1}\),可以推出 \(p_i\leq i+s_i\),所以新的限制是原来限制的充分条件

    这说明限制是严格增强的,我们只需要减去一些方案即可,具体地,我们减去 \(p_i\leq i+s_i\and p_{i+1}>i+s_i+1\) 的方案。这其实也是一个单项式,即 \({i+s_i-1\choose i-1}\cdot {n+m-1-(i+s_i+1)\choose n-i-2}={i+s_i-1\choose i-1}\cdot {n+m-2-i-s_i\choose n-i-2}\)

    另一个式子的处理方案也是类似的,两个东西可以放在一起写,略微调整系数即可。

    时间复杂度 \(O(n+m)\)

    总结

    形式规律,但是有枚举上界的组合数积和式,可以尝试递推快速计算。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int N = 500005;
    const int M = 3000005;
    const int MOD = 998244353;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,m,ans,a[N],w[N],fac[M],inv[M];
    void init(int n)
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    }
    int C(int n,int m)
    {
    	if(n<m || m<0) return 0;
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    void add(int &x,int y) {x=(x+y)%MOD;}
    struct node
    {
    	int n,m,p,q,r;
    	node(int A,int B)
    	{
    		n=A;m=B;p=q=0;
    		r=C(n+m-1,m);
    	}
    	int calc(int A,int B)
    	{
    		while(q<B)
    		{
    			q++;
    			add(r,C(p+q,q)*C(n-p+m-q-1,m-q));
    		}
    		while(p<A)
    		{
    			p++;
    			add(r,-C(p+q,p)*C(n-p+m-q-1,n-p));
    		}
    		return r;
    	}
    };
    void work()
    {
    	n=read();m=ans=0;
    	for(int i=1;i<=n;i++) m+=(a[i]=read());
    	for(int i=1;i<n;i++) w[i]=read();
    	node f(n-1,m),g(n,m-1);
    	for(int i=1;i<n;i++)
    	{
    		int x=0;a[i]+=a[i-1];
    		add(x,i*C(n+m-1,n));
    		add(x,-a[i]*C(n+m-1,m));
    		add(x,2*a[i]*f.calc(i-1,a[i]));
    		if(a[i]) add(x,-2*i*g.calc(i,a[i]-1));
    		add(ans,x*w[i]);
    	}
    	printf("%lld\n",(ans+MOD)%MOD);
    }
    signed main()
    {
    	T=read();init(3000000);
    	while(T--) work();
    }
    

    题目描述

    点此看题

    解法

    完全的一道诈骗题,被骗得裤衩子都没了

    其实本题的限制是很弱的,可以做这样的题意转化:考虑从子串 \([l,r]\) 出发,每次移动到 \([l-1,r-2]\),可以跳跃到这个子串的其他出现位置,要求仅仅是左端点不能越过 \(1\),答案就是 \(r-l+1\)

    不考虑往回跳,答案有很明显的下界 \(\frac{n}{2}\);如果考虑往回跳,我们枚举第一次起跳的子串 \([l,r]\)

    由于碰到 \(l\) 就可以往回跳,所以合法的充要条件是这个子串在原串至少出现过两次。而出发的串最多通过 \(\lfloor\frac{n-r}{2}\rfloor\) 次移动到子串 \([l,r]\),所以答案就是 \(r-l+1+\lfloor\frac{n-r}{2}\rfloor\)

    那么做法也就呼之欲出了,我们建立后缀自动机,然后维护节点的出现次数与最小的 \(\tt endpos\) 就可以求出答案,时间复杂度 \(O(|S|)\)

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 1000005;
    const int inf = 0x3f3f3f3f;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,ans,cnt,last,siz[M],pos[M];
    vector<int> g[M];char s[M];
    struct node {int fa,len,ch[26];}a[M];
    void add(int c)
    {
        int p=last,np=last=++cnt;
        a[np].len=a[p].len+1;siz[np]=1;
        for(;p && !a[p].ch[c];p=a[p].fa)
    		a[p].ch[c]=np;
        if(!p) a[np].fa=1;
        else
        {
            int q=a[p].ch[c];
            if(a[q].len==a[p].len+1) a[np].fa=q;
            else
            {
                int nq=++cnt;a[nq]=a[q];
    			a[nq].len=a[p].len+1;
                a[q].fa=a[np].fa=nq;
                for(;p && a[p].ch[c]==q;p=a[p].fa)
    				a[p].ch[c]=nq;
            }
        }
    }
    void dfs(int u)
    {
    	for(int v:g[u])
    	{
    		dfs(v);siz[u]+=siz[v];
    		pos[u]=min(pos[u],pos[v]);
    	}
    	if(siz[u]>1)
    		ans=max(ans,a[u].len+(n-pos[u])/2);
    }
    void work()
    {
    	scanf("%s",s+1);n=strlen(s+1);
    	for(int i=1;i<=cnt;i++)
    	{
    		a[i].fa=a[i].len=0;g[i].clear();
    		pos[i]=inf;siz[i]=0;
    		memset(a[i].ch,0,sizeof a[i].ch);
    	}
    	cnt=last=1;ans=n/2;
    	for(int i=1;i<=n;i++)
    		add(s[i]-'a'),pos[last]=i;
    	for(int i=2;i<=cnt;i++)
    		g[a[i].fa].push_back(i);
    	dfs(1);
    	printf("%d\n",ans);
    }
    signed main()
    {
    	T=read();
    	memset(pos,0x3f,sizeof pos);
    	while(T--) work();
    }
    
  • 相关阅读:
    读书笔记 effective c++ Item 32 确保public继承建立“is-a”模型
    读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低
    读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)
    程序猿开发语言投票
    读书笔记 effective c++ Item 29 为异常安全的代码而努力
    读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)
    C++ 11和C++98相比有哪些新特性
    读书笔记 effective c++ Item 27 尽量少使用转型(casting)
    如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
    如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16353264.html
Copyright © 2020-2023  润新知