NOIP联考总结(11.19)
T1
算法:模拟,推式子
一个看起来很复杂其实非常暴力的题,一开始以为那几个方程要用自己不会有点虚,然后细想可以直接模拟分情况记圆心的答案,同一个圆上的答案,以及不同圆上的答案,\(O(n)\) 统计答案。一直没太理清楚,没有写出清晰的式子后在写,导致一直到快两个小时才写完。赛后知道还有 \(O(1)\) 做法,感觉意义不大。
原题链接
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const double pi=3.1415926585;
#define int long long
int ansq,ansp,n,m;
const int p=1e18;
signed main()
{
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
cin>>n>>m;
if(m==1)//m==1从圆心走直线。
{
int las=0;
for(int i=2;i<=n;i++)
{
las=(las+i-1)%p;
ansq=(ansq+las)%p;
}
las=(las+n*2)%p;
ansq=(ansq+las)%p;
for(int i=n+2;i<=2*n;i++)
{
las=(las+i-1)%p;
ansq=(ansq+las)%p;
}
cout<<setprecision(9)<<ansp*pi+ansq<<'\n';
}
else
{
ansq=(1+n)*n%p*m%p;//圆心到周围的距离。
int jg,las=0;
for(int i=1;i<=m;i++)
{
double x=2*1.0*m/i*1.0;
if(x<pi){jg=i;break;}//走圆环与直径的分界点
}
for(int i=2;i<=n;i++)
{
ansp=(ansp+jg*(jg-1)%p*(n-i+1)%p*2%p*(i-1)%p)%p;//走圆环
ansq=(ansq+((m-jg)*2%p+1)%p*4%p*(n-i+1)%p*m%p*(i-1)%p)%p;
las=(las+(i-1)*2%p*m%p*2%p*m%p)%p;
ansq=(ansq+las)%p;
ansp=(ansp+jg*(jg-1)%p*(n-i+1)%p)%p;//走圆环
ansq=(ansq+((m-jg)*2%p+1)%p*2%p*(n-i+1)%p*m%p)%p;
}
ansp=(ansp+jg*(jg-1)%p*n)%p;//走圆环
ansq=(ansq+(((m-jg)*2%p+1)%p*2%p*n%p*m%p))%p;
cout<<setprecision(9)<<ansp*pi+ansq<<'\n';
}
return 0;
}
估分100->100
T2
算法:DP,组合数学
做为一道计数题,一直在想DP,大概推到了\(O(n^2)\) 的算法。设\(f[i][j]\) 表示处理了前 \(i\) 个,最大值为 \(j\) 的方案数。显然可以分类讨论,第 \(i\) 个数填 \(j\) 和第 \(i\) 个数不填 \(j\) 。\(f[i][j]=\sum_{l=j-k}^{j-1}f[i-1][l]+f[i-1][j]*(j-i+1)\) 。因为与最大值有关,所以考虑,改为 \(dp[i]\) 填了 \(i\) 个数最大值为\(i\) 的方案数。考虑转移 \(dp[j]=\sum_{i=j-k}^{j-1}f[i]\frac{(n-i-1)!}{(n-j)!}\)
考虑转移方程怎么来的,现在前面连续填了一段,其中的最大值是\(i\),如果要从 \(i\) 转移则 \(j\) 只能放在第一块的最后面(不重),剩下的数随便选。
原题链接
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int p=998244353,N=1e7+10;
#define int long long
int n,k,inv[N],jc[N],dp[N],sum[N];
int ksm(int x,int y)
{
int ans=1;
while(y)
{
if(y&1)ans=ans*x%p;
y>>=1;
x=x*x%p;
}
return ans;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
jc[0]=inv[0]=1;
for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%p;//预处理阶乘。
inv[n]=ksm(jc[n],p-2);
for(int i=n-1;i;i--)inv[i]=inv[i+1]*(i+1)%p;//预处理阶乘逆元
for(int i=1;i<=k;i++)
{
dp[i]=jc[n-1]*inv[n-i]%p;
dp[i]=(dp[i]+sum[i-1]*inv[n-i])%p;
sum[i]=(sum[i-1]+dp[i]*jc[n-i-1])%p;
}
for(int i=k+1;i<=n;i++)
{
dp[i]=(sum[i-1]-sum[i-k-1]+p)%p*inv[n-i]%p;
sum[i]=(sum[i-1]+dp[i]*jc[n-i-1]%p)%p;
}
cout<<dp[n]<<'\n';
return 0;
}
估分20->20
T3
个人觉得比 \(T2\) 简单,还是计数题,还是 \(dp\) 。先分析题目性质,显然一个数能被删除当且仅当小于他的数在全在左边或全在右边,所以考虑设\(dp[l][r][k]\) 表示处理到第 \(k\) 个数,剩下的数放在小于等于 \(l\) 个空隙或大于第 \(r\) 个空隙的方案数(知道 \(m\) 个数其他的数插空放,共\(m+1\) 个空,每个空可以放多个)。答案就是\(dp[1][m+1][n]\),转移方程就是分类讨论当前数在不在 \(m\) 数之中。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int p=998244353,N=510;
#define int long long
int n,m,a[N],pos[N],dp[N][N][N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)cin>>a[i];
for(int i=1;i<=m;i++)pos[a[i]]=i;//存在a数组中的位置
if(pos[0])dp[pos[0]][pos[0]+1][0]=1;//0在a中放在pos[0]与pos[1]之间
else for(int i=1;i<=m+1;i++)dp[i][i][0]=1;//随便放
for(int k=1;k<n;k++)
{
if(!pos[k])
{
for(int r=1;r<=m+1;r++)
{
int sum=0;
for(int l=r;l>=1;l--)
sum=(sum+dp[l][r][k-1])%p,dp[l][r][k]=(dp[l][r][k]+sum)%p;
}//放右边
for(int l=1;l<=m+1;l++)
{
int sum=0;
for(int r=l;r<=m+1;r++)
sum=(sum+dp[l][r][k-1])%p,dp[l][r][k]=(dp[l][r][k]+sum)%p;
}//放右边
}
else
{
for(int l=1;l<=m+1;l++)
for(int r=l;r<=m+1;r++)
dp[min(l,pos[k])][max(pos[k]+1,r)][k]=(dp[min(l,pos[k])][max(pos[k]+1,r)][k]+dp[l][r][k-1])%p;
}
}
cout<<dp[1][m+1][n-1]<<'\n';
return 0;
}
估分 0->0
T4
太难了,我不会。
反思
没有出现挂分的情况但是,自己对于计数方面不熟悉,对于 \(dp\) 题和第二题长期拿不到分需要重点突破。同时这段时间的总结反思太少了,要多总结,一周至少写一次。