【考试题解】多校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\) 数组产生额外贡献;对于是 \(+\) 的情况,该项结束,所以把之前没结束的答案累加。
对于运算符的情况就相当显然了,这个数结束了,所以赋值为 \(0\);对于数字的情况,此时 \(re\) 数组就派上用场了,这里的被累加几次是包括了之前乘积做的贡献的,所以直接乘就行。
对于 \(+\),之前的项直接结束,没有乘积贡献,所以初始化为经过次数;对于 \(*\),要乘上之前的数,所以累加 \(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)
不会。