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();
}
posted @ 2022-06-07 20:19  C202044zxy  阅读(120)  评论(5编辑  收藏  举报