2023.5.18测试

xishanmeigao / 2023-08-07 / 原文

T1 金币

(其实是原题 P3951 [NOIP2017 提高组] 小凯的疑惑)

给定 \(a,b\in \mathrm{N^*}\),求最大的不能表示成 \(ax+by\:\:(x,y\in \mathrm{N})\) 的数字

考场上想了很久,幸好搞出来了

因为 \(\gcd(a,b)=1\),所以如果 \(x,y\in\mathrm{Z}\),那么由 \(\mathrm{B\acute{e}zout}\) 定理知 \(ax+by\) 可以表示出任意的整数。

但现在 \(x,y\) 有自然数的限制,所以当 \(x\)\(y\) 小于 \(0\) 时式子表示出来的数不一定能够用两个大于等于 \(0\)\(x,y\) 表示出来。那我们就考虑 \(x,y\) 取到多少表示出来的数一定是不合法的,且让这个数最大

考虑让 \(x>0,y<0\)。容易发现,当 \(x\geq b\) 时,可以将式子变形成 \(a(x-b)+b(y+a)\),也就是说 \(x\) 大于等于 \(b\) 的部分可以抵消到后面 \(y\) 的负值,那此时 \(y+a\) 可能就可以大于等于 \(0\),那这个式子表示出来的数就是合法的。就算此时 \(y+a\) 仍然小于 \(0\),那它肯定也是不比\(y=-1\) 更优的。所以直接让 \(x=b-1,y=-1\) 即可

综上,答案为 \(a(b-1)+b\times(-1)=ab-a-b\)

T2 字符串计数

一个长度为 \(len\) 的字符串,包括至少 \(a\) 个大写字母,\(b\) 个小写字母,\(c\) 个数字,求有多少个符合条件的字符串。答案模 \(10^9+9\)

考场上脑瘫把组合写成排列了,一直搞不出来,最后喜提 \(15\)

设大写字母、小写字母,数字的数量依次为 \(i,j,k\:(i+j+k=len)\),那么此时对答案的贡献为 \(26^i\times26^j\times10^k\times \binom{i+j}{i}\times\binom{len}{i+j}\)

考虑 \(O(n^2)\) 枚举 \(i,j\),可以拿到 \(36\)

现在思考如何优化。观察到大小写字母的底数都是 \(26\),所以考虑把它们合并在一起。设 \(f(n)\) 表示大小写字母共选了 \(n\) 个的方案数 \((a+b\leq n\leq len-k)\),那么最终答案就为 \(\sum\limits_{i=1}^{len-k}{f(i)\times10^{len-i}\times\binom{len}{len-i}}\)

考虑如何计算 \(f(n)\)

\[\begin{aligned}f(n)&=\sum_{i=a}^{n-b}{26^i\times26^{n-i}\times\binom{n}{i}}\\&=26^n\times\sum_{i=a}^{n-b}{\binom{n}{i}}\end{aligned} \]

\(g(n)=\sum\limits_{i=a}^{n-b}{\binom{n}{i}}\),因为 \(n\geq a+b\),所以第一项为 \(g(a+b)=\binom{a+b}{a}\)。考虑递推式子,将 \(g(n)\) 放到杨辉三角上可看出:

\[\begin{aligned}g(n+1)&=\sum_{i=a}^{n+1-b}{\binom{n+1}{i}}\\&=2\times g(n)+\binom{n}{a-1}+\binom{n}{n-b+1}\end{aligned} \]

\(O(n\log MOD)\) 递推出来

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=200010;
const LL MOD=1e9+9;

int len,a,b,c;
LL fac[N],f[N],g[N],ans;

LL ksm(LL x,LL y)
{
	if(y==0)
		return 1;
	if(y==1)
		return x;
	LL tmp=ksm(x,y/2);
	if(y&1)
		return tmp*tmp%MOD*x%MOD;
	return tmp*tmp%MOD;
}

LL C(int a,int b)
{
	if(a<b || a<0 || b<0)
		return 0;
	if(b==0)
		return 1;
	if(a==0)
		return 0;
	return fac[a]*ksm(fac[a-b],MOD-2)%MOD*ksm(fac[b],MOD-2)%MOD;
}

int main()
{
	fac[0]=1;
	for(int i=1; i<=2e5+1; i++)
		fac[i]=(fac[i-1]*i)%MOD;
	
	scanf("%d%d%d%d",&len,&a,&b,&c);
	
	if(a+b+c>len)
	{
		printf("0");
		return 0;
	}
	
	for(int i=a+b; i<=len-c; i++)
	{
		if(i==a+b) //注意这地方当a+b=0时就不好递推了,所以边界自己处理
			g[i]=C(i,a);
		else
			g[i]=(g[i-1]*2%MOD+C(i-1,a-1)+C(i-1,i-b))%MOD;
		f[i]=ksm(26,i)*g[i]%MOD;
		(ans+=f[i]*ksm(10,len-i)%MOD*C(len,len-i)%MOD)%=MOD;
	}
	
	printf("%lld",ans);

	return 0;
}

T3 树

一棵带边权的树,边权为 \([0,9]\) 内的整数。问有多少点对 \((u,v)\),满足将 \(u\rightarrow v\) 简单路径上的边权串成一个整数 \(x\)\(x\)\(m\) 的倍数。

\(2\leq n\leq 10^5\)\(1\leq m\leq 10^9\)\(m\perp 10\)

一眼点分治。考场上迅速码完,calc 函数直接两重循环暴力判断。由于数据太弱,喜提 \(100\)

不过还是要思考如何优化

对于 \((u,v)\) 来说,设 \(a\)\(u\rightarrow rt\) 路径上串成的数,\(b\)\(rt\rightarrow v\) 路径上串成的数,\(dep\)\(v\) 的深度。则

\[a\times10^{dep}+b\equiv 0\pmod m \]

利用 \(m\perp 10\) 除以 \(10^{dep}\)

\[a+\frac{b}{10^{dep}}\equiv 0\pmod m \]

\(a,\dfrac{b}{10^{dep}}\) 分别丢进两个不同的 \(\mathrm{map}\) 即可

对于 \((v,u)\) 来说,情况类似

预处理出 \(10\) 的正整数次幂的逆元,时间复杂度 \(O(n\log^2 n)\)

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=100010,M=5000010;

struct node
{
	LL sa,sb; //sa倒着,sb正着 
	int cnt;
}a[N];
int n,p,L;
int rt,rt_size,size[N];
int head[N],nxt[2*N],ver[2*N],edge[2*N],tot;
bool v[N];
LL m,inv[N],ans;
map <LL,int> aa,bb; 

LL exgcd(LL a,LL b,LL &x,LL &y)
{
	if(b==0)
	{
		x=1;  y=0;
		return a;
	}
	
	LL d=exgcd(b,a%b,x,y);
	LL tmp=x;
	x=y;  y=tmp-(a/b)*y;
	return d;
}

void add(int x,int y,int z)
{
	ver[++tot]=y;  edge[tot]=z;
	nxt[tot]=head[x];  head[x]=tot;
}

void find_root(int x,int fa,int Large)
{
	int max_part=0;
	size[x]=1;
	for(int i=head[x]; i; i=nxt[i])
	{
		int y=ver[i];
		if(y==fa || v[y])
			continue;
		
		find_root(y,x,Large);
		
		size[x]+=size[y];
		max_part=max(max_part,size[y]);
	}
	
	max_part=max(max_part,Large-size[x]);
	if(max_part<rt_size)
	{
		rt=x;
		rt_size=max_part;
	} 
}

void dfs(int x,int fa,LL numa,LL numb,LL pw,int t)
{
	a[++p]=(node){numa,numb*inv[t]%m,t};
	for(int i=head[x]; i; i=nxt[i])
	{
		int y=ver[i],z=edge[i];
		if(y==fa || v[y])
			continue;
			
		LL nta=(1LL*z*pw+numa)%m;
		LL ntb=(numb*10%m+(LL)z)%m;
		dfs(y,x,nta,ntb,pw*10%m,t+1);
	}
}

void calc()
{
	for(int i=1; i<=p; i++)
	{
		ans+=(LL)bb[(m-a[i].sa)%m];
		ans+=(LL)aa[(m-a[i].sb)%m];
	}
	for(int i=1; i<=p; i++)
	{
		aa[a[i].sa]++;
		bb[a[i].sb]++;
	}
	
	p=0;
}

void solve(int x,int fa,int Large)
{
	rt_size=Large+1;
	find_root(x,fa,Large);
	
	v[rt]=1;
	
	p=0;
	aa[0]++; bb[0]++;
	for(int i=head[rt]; i; i=nxt[i])
	{
		int y=ver[i],z=edge[i];
		if(y==fa || v[y])
			continue;
		
		dfs(y,rt,(LL)z%m,(LL)z%m,10,1);
		calc();
	}
	
	aa.clear();  bb.clear();
	
	for(int i=head[rt]; i; i=nxt[i])
	{
		int y=ver[i];
		if(y==fa || v[y])
			continue;
		solve(y,x,size[y]);
	}
}

int main()
{
	scanf("%d%lld",&n,&m);
	for(int i=1; i<n; i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++;  y++;
		add(x,y,z);  add(y,x,z);
	}
	
	LL pw=1;
	for(int i=1; i<=n; i++)
	{	
		(pw*=10)%=m;
		LL x,y;
		exgcd(pw,m,x,y);
		x=(x%m+m)%m;
		inv[i]=x;
	}
	
	solve(1,0,n);
	
	printf("%lld",ans);

	return 0;
}

\(100+15+100=215\)\(\mathrm{rk}\space6\)