【考试题解】多校A层冲刺NOIP2024模拟赛17

A. 网格(grid)

题目内容

给你一个 \(n\times m\) 的字符网格 \(s\)\(s_{i,j}\in[1,9]\cup\{+,*\}\),从 \((1,1)\) 开始,仅向下或向右走并最终到达 \((n,m)\) 的路径被称为合法路径,求所有合法路径对应的表达式的运算结果之和,答案对 \(998244353\) 取模。

部分分

44pts

爆搜,枚举路径,直接算表达式值然后求和。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b;
const int mod=998244353;
char c[2002][2002],d[4004];
long long ans;
il void check()
{
	stack<int>num;
	stack<char>op;
	register long long rn=0;
	for(ri i=1;i<=a+b-1;i++)
	{
		if(d[i]>='0'&&d[i]<='9')
		{
			rn=(rn*10+d[i]-'0')%mod;
		}
		else
		{
			if(!op.empty()&&op.top()=='*')
			{
				op.pop();
				rn=(rn*num.top())%mod;
				num.pop();
			}
			num.push(rn);
			op.push(d[i]);
			rn=0;
		}
	}
	if(!op.empty()&&op.top()=='*')
	{
		op.pop();
		rn=(rn*num.top())%mod;
		num.pop();
	}
	num.push(rn);
	rn=0;
	while(!num.empty())
	{
		rn=(rn+num.top())%mod;
		num.pop();
	}
	ans=(ans+rn)%mod;
}
void dfs(int x,int y)
{
	d[x+y-1]=c[x][y];
	if(x==a&&y==b)
	{
		check();
		return;
	}
	if(x!=a)
	{
		dfs(x+1,y);
	}
	if(y!=b)
	{
		dfs(x,y+1);
	}
}
int main()
{
	freopen("grid.in","r",stdin);
	freopen("grid.out","w",stdout);
	scanf("%d%d",&a,&b);
	for(ri i=1;i<=a;i++)
	{
		for(ri j=1;j<=b;j++)
		{
			scanf("%c",&c[i][j]);
			if((c[i][j]<'0'||c[i][j]>'9')&&c[i][j]!='+'&&c[i][j]!='*')
			{
				j--;
			}
		}
	}
	dfs(1,1);
	printf("%lld",ans);
	return 0;
}

55pts

对于没有运算符的情况,容易观察到一个点对经过它的所有路径的贡献是相同的,而经过一个点有多少合法路径是经典问题,直接算就行。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b;
const int mod=998244353;
bool te1;
char c[2002][2002],d[4004];
long long ans,jc[4004],ny[4004],ten[4004];
void exgcd(int u,int v,long long &x,long long &y)
{
	if(!v)
	{
		x=1;
		y=0;
		return;
	}
	exgcd(v,u%v,y,x);
	y-=u/v*x;
}
il long long C(int x,int y)
{
	return (((jc[x]*ny[x-y])%mod)*ny[y])%mod;
}
il void check()
{
	stack<int>num;
	stack<char>op;
	register long long rn=0;
	for(ri i=1;i<=a+b-1;i++)
	{
		if(d[i]>='0'&&d[i]<='9')
		{
			rn=(rn*10+d[i]-'0')%mod;
		}
		else
		{
			if(!op.empty()&&op.top()=='*')
			{
				op.pop();
				rn=(rn*num.top())%mod;
				num.pop();
			}
			num.push(rn);
			op.push(d[i]);
			rn=0;
		}
	}
	if(!op.empty()&&op.top()=='*')
	{
		op.pop();
		rn=(rn*num.top())%mod;
		num.pop();
	}
	num.push(rn);
	rn=0;
	while(!num.empty())
	{
		rn=(rn+num.top())%mod;
		num.pop();
	}
	ans=(ans+rn)%mod;
}
void dfs(int x,int y)
{
	d[x+y-1]=c[x][y];
	if(x==a&&y==b)
	{
		check();
		return;
	}
	if(x!=a)
	{
		dfs(x+1,y);
	}
	if(y!=b)
	{
		dfs(x,y+1);
	}
}
namespace task1
{
	short main()
	{
		jc[0]=ny[0]=ten[0]=1;
		for(ri i=1;i<=a+b;i++)
		{
			jc[i]=(jc[i-1]*i)%mod;
			register long long j;
			exgcd(jc[i],mod,ny[i],j);
			while(ny[i]<0)
			{
				ny[i]+=mod;
			}
			ten[i]=(ten[i-1]*10)%mod;
		}
		for(ri i=1;i<=a;i++)
		{
			for(ri j=1;j<=b;j++)
			{
				register long long k=(C(i+j-2,i-1)*C(a+b-i-j,a-i))%mod;
				k*=(ten[a+b-i-j]*(c[i][j]-'0'))%mod;
				ans+=k%mod;
				ans%=mod;
			}
		}
		printf("%lld",ans);
		return 0;
	}
}
int main()
{
	freopen("grid.in","r",stdin);
	freopen("grid.out","w",stdout);
	scanf("%d%d",&a,&b);
	te1=true;
	for(ri i=1;i<=a;i++)
	{
		for(ri j=1;j<=b;j++)
		{
			scanf("%c",&c[i][j]);
			if((c[i][j]<'0'||c[i][j]>'9')&&c[i][j]!='+'&&c[i][j]!='*')
			{
				j--;
			}
			if(c[i][j]=='+'||c[i][j]=='*')
			{
				te1=false;
			}
		}
	}
	if(te1)
	{
		return task1::main();
	}
	dfs(1,1);
	printf("%lld",ans);
	return 0;
}

正解

观察一个表达式一定是由几个乘积项相加得来,即形如:\(a_1*a_2+a_3*a_4\) 的式子。应用数学定义:几个数相乘为一项,则表达式为一个多项式。考虑DP:设 \(dp_{i,j}\) 表示当前位置已经处理过的项的和,\(now_{i,j}\) 表示当前位置没有处理(即这个数字还没有结束)的数字之和,\(re_{i,j}\) 表示如果这里是数字,那么这个数字会被加几次。对于转移要分讨当前位置:

\[dp_{i,j}=\begin{cases}dp_{i-1,j}+dp_{i,j-1},s_{i,j}\neq+\\dp_{i-1,j}+dp_{i,j-1}+now_{i-1,j}+now_{i,j-1},s_{i,j}=+\end{cases} \]

这个就比较显然了。对于不是 \(+\) 的情况,由于该项还没有结束,所以不会对 \(dp\) 数组产生额外贡献;对于是 \(+\) 的情况,该项结束,所以把之前没结束的答案累加。

\[now_{i,j}=\begin{cases}0,s_{i,j}\notin[0,9]\\now_{i-1,j}+now_{i,j-1}\times10+re_{i-1,j}\times s_{i,j},s_{i,j}\in[0,9]\end{cases} \]

对于运算符的情况就相当显然了,这个数结束了,所以赋值为 \(0\);对于数字的情况,此时 \(re\) 数组就派上用场了,这里的被累加几次是包括了之前乘积做的贡献的,所以直接乘就行。

\[re{i,j}=\begin{cases}C_{i+j-2}^{i-1},s_{i,j}=+\\now_{i-1,j}+now_{i,j-1},s_{i,j}=*\\re_{i-1,j}+re_{i,j-1},s_{i,j}\in[0,9]\end{cases} \]

对于 \(+\),之前的项直接结束,没有乘积贡献,所以初始化为经过次数;对于 \(*\),要乘上之前的数,所以累加 \(now\);对于数字,没有额外贡献,直接把之前乘积的贡献累加。

注意最终答案为 \(dp_{a,b}+now_{a,b}\)。注意阶乘要预处理到 \(n+m\)

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,b;
const int mod=998244353;
char c[2002][2002];
long long dp[2002][2002],re[2002][2002],now[2002][2002],jc[4004],ny[4004];
void exgcd(int u,int v,long long &x,long long &y)
{
	if(!v)
	{
		x=1;
		y=0;
		return;
	}
	exgcd(v,u%v,y,x);
	y-=u/v*x;
}
il long long C(int x,int y)
{
	return (((jc[x]*ny[x-y])%mod)*ny[y])%mod;
}
int main()
{
	freopen("grid.in","r",stdin);
	freopen("grid.out","w",stdout);
	scanf("%d%d",&a,&b);
	for(ri i=1;i<=a;i++)
	{
		for(ri j=1;j<=b;j++)
		{
			scanf("%c",&c[i][j]);
			if((c[i][j]<'0'||c[i][j]>'9')&&c[i][j]!='+'&&c[i][j]!='*')
			{
				j--;
			}
		}
	}
	jc[0]=ny[0]=1;
	for(ri i=1;i<=a+b;i++)
	{
		jc[i]=(jc[i-1]*i)%mod;
		register long long j;
		exgcd(jc[i],mod,ny[i],j);
		while(ny[i]<0)
		{
			ny[i]+=mod;
		}
	}
	re[0][1]=1;
	for(ri i=1;i<=a;i++)
	{
		for(ri j=1;j<=b;j++)
		{
			switch(c[i][j])
			{
				case '+':
				{
					dp[i][j]=(dp[i-1][j]+dp[i][j-1]+now[i-1][j]+now[i][j-1])%mod;
					re[i][j]=C(i+j-2,i-1);
					now[i][j]=0;
					break;
				}
				case '*':
				{
					dp[i][j]=(dp[i-1][j]+dp[i][j-1])%mod;
					re[i][j]=(now[i-1][j]+now[i][j-1])%mod;
					now[i][j]=0;
					break;
				}
				default :
				{
					dp[i][j]=(dp[i-1][j]+dp[i][j-1])%mod;
					re[i][j]=(re[i-1][j]+re[i][j-1])%mod;
					now[i][j]=((now[i-1][j]+now[i][j-1])*10+re[i][j]*(c[i][j]-'0'))%mod;
					break;
				}
			}
		}
	}
	printf("%lld",(dp[a][b]+now[a][b])%mod);
	return 0;
}

B. 矩形(rect)

题目内容

二维平面上有 \(n\) 个不重复的点,定义正交矩形为四条边都与坐标轴平行的矩形,选 \(4\) 个点组成正交矩形,求方案数。

部分分

20pts

\(O(n^4)\) 暴力枚举,直接判断是否合法。

60pts

使用伟大的bitset对每一个 \(x\) 坐标存这上面有点的位置,然后枚举两个 \(x\) 求与,对答案的贡献是 \(C_{cnt}^{2}\),其中 \(cnt\) 为与完以后 \(1\) 的数量。复杂度 \(O(\frac{n^3}{w})\)。再特判一个 \(x_i\le2\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,mx;
pair<int,int> b[200002];
bitset<2002>bit[2002];
long long ans;
il long long C(int x)
{
	register long long rn=x;
	rn*=x-1;
	rn>>=1;
	return rn;
}
namespace task1
{
	bitset<200002>one,two;
	short main()
	{
		for(ri i=1;i<=a;i++)
		{
			if(b[i].first==1)
			{
				one[b[i].second]=1;
			}
			else
			{
				two[b[i].second]=1;
			}
		}
		ri re=(one&two).count();
		printf("%lld",C(re));
		return 0;
	}
}
int main()
{
	freopen("rect.in","r",stdin);
	freopen("rect.out","w",stdout);
	scanf("%d",&a);
	for(ri i=1;i<=a;i++)
	{
		scanf("%d%d",&b[i].first,&b[i].second);
		mx=max(mx,b[i].first);
	}
	if(mx<=2)
	{
		return task1::main();
	}
	for(ri i=1;i<=a;i++)
	{
		bit[b[i].first][b[i].second]=1;
	}
	for(ri i=1;i<=mx;i++)
	{
		for(ri j=i+1;j<=mx;j++)
		{
			ri k=(bit[i]&bit[j]).count();
			ans+=C(k);
		}
	}
	printf("%lld",ans);
	return 0;
}

正解

如果我们对于一个点从 \(x_i\)\(y_i+n\) 连边,那么题目就转化为四元环计数。这个就是套路了,建议自行学习。推个好博

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define inf 0x3f3f3f3f
int a,u,v,in[400004],f[400004],g;
vector<int>vec[400004];
long long ans,cnt[400004];
struct node
{
	int h,to;
}pic[400004];
il void ndin(int x,int y)
{
	g++;
	pic[g].to=y;
	pic[g].h=f[x];
	f[x]=g;
}
il bool cmp(int x,int y)
{
	if(in[x]!=in[y])
	{
		return in[x]<in[y];
	}
	return x<y;
}
int main()
{
	freopen("rect.in","r",stdin);
	freopen("rect.out","w",stdout);
	scanf("%d",&a);
	for(ri i=1;i<=a;i++)
	{
		scanf("%d%d",&u,&v);
		ndin(u,a+v);
		ndin(a+v,u);
		in[u]++;
		in[a+v]++;
	}
	for(ri i=1;i<=(a<<1);i++)
	{
		for(ri j=f[i];j;j=pic[j].h)
		{
			ri k=pic[j].to;
			if(cmp(i,k))
			{
				vec[i].push_back(k);
			}
		}
	}
	for(ri i=1;i<=(a<<1);i++)
	{
		for(ri j=f[i];j;j=pic[j].h)
		{
			ri k=pic[j].to;
			for(ri h:vec[k])
			{
				if(cmp(i,h))
				{
					ans+=cnt[h];
					cnt[h]++;
				}
			}
		}
		for(ri j=f[i];j;j=pic[j].h)
		{
			ri k=pic[j].to;
			for(ri h:vec[k])
			{
				if(cmp(i,h))
				{
					cnt[h]=0;
				}
			}
		}
	}
	printf("%lld",ans);
	return 0;
}

C. 集合(set)

不会。

D. 倒水(bottle)

不会。

posted @ 2024-11-01 19:46  一位很会的教授er~  阅读(19)  评论(0编辑  收藏  举报