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\) 题和第二题长期拿不到分需要重点突破。同时这段时间的总结反思太少了,要多总结,一周至少写一次。

posted @ 2024-11-21 16:54  storms11  阅读(1)  评论(0编辑  收藏  举报