多校A层冲刺NOIP2024模拟赛12

多校A层冲刺NOIP2024模拟赛12

T1 A. Alice 和璀璨花 65pts/65pts/65pts

  • 部分分

    • 测试点 110 :设 fi,j 表示前 i 位中生长趋势子序列长度为 j 时的末尾最小元素,然后进行暴力转移。
    • 测试点 1113 :观察到至多选择 log21018=40 个数。
    • 测试点 1416 :做法同最长上升子序列。
    点击查看代码
    ll a[1000010],b[1000010],d[1000010],f[1000010],g[1000010];
    struct BIT
    {
    	ll c[1000010];
    	ll lowbit(ll x)
    	{
    		return (x&(-x));
    	}
    	void update(ll n,ll x,ll val)
    		{
    		for(ll i=x;i<=n;i+=lowbit(i))
    		{
    			c[i]=max(c[i],val);
    		}
    	}
    	ll getsum(ll x)
    	{
    		ll ans=0;
    		for(ll i=x;i>=1;i-=lowbit(i))
    		{
    			ans=max(ans,c[i]);
    		}
    		return ans;
    	}
    }T;
    int main()
    {	
    	freopen("alice.in","r",stdin);
    	freopen("alice.out","w",stdout);
    	ll n,ans=0,flag=1,i,j;
    	scanf("%lld",&n);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld",&a[i]);
    		d[i]=a[i];
    	}
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld",&b[i]);
    		flag&=(b[i]==1);
    	}
    	if(flag==1)
    	{
    		sort(d+1,d+1+n);
    		d[0]=unique(d+1,d+1+n)-(d+1);
    		for(i=1;i<=n;i++)
    		{
    			a[i]=lower_bound(d+1,d+1+d[0],a[i])-d;
    			g[i]=T.getsum(a[i]-1)+1;
    			T.update(d[0],a[i],g[i]);
    			ans=max(ans,g[i]);
    		}
    	}
    	else
    	{
    		flag=1;
    		for(i=1;i<=n;i++)
    		{
    			flag&=(b[i]>1);
    		}
    		memset(f,0x3f,sizeof(f));
    		f[0]=0;
    		if(flag==1)
    		{
    			for(i=1;i<=n;i++)
    			{
    				for(j=min(i,40ll);j>=1;j--)
    				{
    					if(f[j-1]!=0x3f3f3f3f3f3f3f3f&&f[j-1]*b[j-1]<a[i])
    					{
    						f[j]=min(f[j],a[i]);
    					}
    				}
    			}
    			for(i=min(n,40ll);i>=1;i--)
    			{
    				if(f[i]!=0x3f3f3f3f3f3f3f3f)
    				{
    					ans=i;
    					break;
    				}
    			}
    		}
    		else
    		{
    			for(i=1;i<=n;i++)
    			{
    				for(j=i;j>=1;j--)
    				{
    					if(f[j-1]!=0x3f3f3f3f3f3f3f3f&&f[j-1]*b[j-1]<a[i])
    					{
    						f[j]=min(f[j],a[i]);
    					}
    				}
    			}
    			for(i=n;i>=1;i--)
    			{
    				if(f[i]!=0x3f3f3f3f3f3f3f3f)
    				{
    					ans=i;
    					break;
    				}
    			}
    		}
    	}
    	printf("%lld\n",ans);
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 正解

    • i 一定时,有 fi,jj 单调递增。
    • 不妨把 fi1 分为两部分使得前半部分的数 <ai ,后半部分的数 ai ,并设前半部分在 k 处结尾。
    • j[1,k][k+2,n] 时,转移是没有意义的;当 j=k+1 时才可能进行转移。
      • j[1,k] 是比较显然的。
      • j[k+2,n] ,由 k 的分割性有 aifi1,k+1fi1,j1 ,自然不会再进行转移。
    点击查看代码
    ll a[1000010],b[1000010],f[1000010];
    int main()
    {	
    	freopen("alice.in","r",stdin);
    	freopen("alice.out","w",stdout);
    	ll n,ans=0,i,j;
    	scanf("%lld",&n);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld",&a[i]);
    	}
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld",&b[i]);
    	}
    	memset(f,0x3f,sizeof(f));
    	f[0]=0;
    	for(i=1;i<=n;i++)
    	{
    		j=lower_bound(f+1,f+1+n,a[i])-f;
    		if(f[j-1]*b[j-1]<a[i])
    		{
    			f[j]=min(f[j],a[i]);
    		}
    	}
    	for(i=n;i>=1;i--)
    	{
    		if(f[i]!=0x3f3f3f3f3f3f3f3f)
    		{
    			ans=i;
    			break;
    		}
    	}
    	printf("%lld\n",ans);
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

T2 B. Bob 与幸运日 30pts/30pts/30pts

  • 部分分

    • 测试点 13
      • 等价于求 {(x1)d+ya(modw)(y1)d+xb(modw)1x,ymin(m,d) 的数量.
      • 同余两边同时加 d ,得到 {xd+ya+d(modw)yd+xb+d(modw)
      • xd+y=k1w+a+d ,有 y=k1w+a+dxd1 ,得到 xd+1adwk1min(m,d)+xdadw
      • y=k1w+a+dxd 代入第二个式子,并设 (k1w+a+dxd)d+x=k2w+b+d ,移项得到 k1wd+k2w=b+dadd2+xd2x
      • 为方便书写,用 (a+d)modw,(b+d)modw 分别代替 a,b ,可以证明其并不会对答案产生影响。
      • 此时有 k1wd+k2w=bad+xd2x ,而容易得到 k1wd+k2w=w 的一组特解 {k1=0k2=1 ,从而得到 k1
      • k2Z 进一步化简可以得到 bad+xd2x=x(d21)+bad0(modw) ,即 x(d21)adb(modw)
      • O(min(m,d)) 枚举合法的 x 即可。
    • 测试点 46
      • 考虑直接枚举 xmodw 的值,进一步求出 yaxd(modw) ,接着手动判断是否有 x+ydbmodw
      • 而计算 [1,min(m,d)] 中模 w 等于一个特定值的数个数是平凡的。
      • 时间复杂度为 O(w)
    • 测试点 78
      • 因为数据点随机,所以不会有 d210(modw) 的数据。
      • 直接移项得到 xadbd21(modw) ,进一步求出 yaxd=aad2bdd21(modw)
      • 时间复杂度为 O(logw)
    点击查看代码
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    ll work(ll m,ll d,ll x,ll w)
    {
    	return (min(m,d)-x)/w+(1<=x&&x<=min(m,d));
    }
    int main()
    {	
    	freopen("bob.in","r",stdin);
    	freopen("bob.out","w",stdout);
    	ll t,m,d,w,a,b,c,e,ans,x,y,i,l,r;
    	scanf("%lld",&t);
    	for(i=1;i<=t;i++)
    	{
    		scanf("%lld%lld%lld%lld%lld",&m,&d,&w,&a,&b);
    		ans=0;
    		a=(a+d)%w;
    		b=(b+d)%w;
    		c=(b-a*d%w+w)%w;
    		e=(d*d-1)%w;
    		if(e==0)
    		{
    			if(min(m,d)<=w)
    			{
    				for(x=1;x<=min(m,d);x++)
    				{
    					if((c+e*x%w)%w==0)
    					{
    						l=ceil(1.0*(x*d+1-a)/w);
    						r=(min(m,d)+x*d-a)/w;
    						ans+=(l<=r)*(r-l+1);
    					}
    				}
    			}
    			else
    			{
    				for(x=0;x<=w-1;x++)   
    				{
    					y=(a-x*d%w+w)%w;
    					ans+=((y*d+x)%w==b)*work(m,d,x,w)*work(m,d,y,w);
    				}
    			}
    		}
    		else
    		{
    			x=(w-c)%w*qpow(e,w-2,w)%w;
    			y=(a-x*d%w+w)%w;
    			ans=work(m,d,x,w)*work(m,d,y,w);
    		}
    		printf("%lld\n",ans);
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 正解

    • 考虑 (d21)=(d1)(d+1)0(modw) 时怎么处理。
    • d1(modw)
      • 展开得到 x+yab(modw) ,并令 ab(modw)
      • 枚举 x 的时间复杂度是 O(w) ,但枚举 x+y 的时间复杂度是 O(min(m,d)w) ,直接根号分治即可。
    • d1(modw)
      • 展开得到 yxab(modw) ,并令 ab(modw)
      • 类似地根号分治即可。
    点击查看代码
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    ll work(ll m,ll d,ll x,ll w)
    {
    	return (min(m,d)-x)/w+(1<=x&&x<=min(m,d));
    }
    int main()
    {	
    	freopen("bob.in","r",stdin);
    	freopen("bob.out","w",stdout);
    	ll t,m,d,w,a,b,c,e,ans,x,y,s,i,j;
    	scanf("%lld",&t);
    	for(i=1;i<=t;i++)
    	{
    		scanf("%lld%lld%lld%lld%lld",&m,&d,&w,&a,&b);
    		ans=0;
    		a=(a+d)%w;
    		b=(b+d)%w;
    		c=(b-a*d%w+w)%w;
    		e=(d*d-1)%w;
    		if(e==0)
    		{
    			s=sqrt(min(m,d))+1;
    			if(d%w==1)
    			{
    				if(a==b)
    				{
    					if(w<=s)
    					{
    						for(x=0;x<=w-1;x++)
    						{
    							y=(a-x+w)%w;
    							ans+=work(m,d,x,w)*work(m,d,y,w);
    						}
    					}
    					else
    					{   
    						for(j=a;j<=2*min(m,d);j+=w)
    						{
    							ans+=(j<=min(m,d))?j-1:2*min(m,d)-j+1;
    						}
    					}
    				}
    			}
    			else
    			{
    				if(a==w-b)
    				{
    					if(w<=s)
    					{
    						for(x=0;x<=w-1;x++)
    						{
    							y=(a+x)%w;
    							ans+=work(m,d,x,w)*work(m,d,y,w);
    						}
    					}
    					else
    					{
    						for(j=a;j<=min(m,d)-1;j+=w)
    						{
    							ans+=min(m,d)-j;
    						}
    						for(j=b;j<=min(m,d)-1;j+=w)
    						{
    							ans+=min(m,d)-j;
    						}
    					}
    				}
    			}
    		}
    		else
    		{
    			x=(w-c)%w*qpow(e,w-2,w)%w;
    			y=(a-x*d%w+w)%w;
    			ans=work(m,d,x,w)*work(m,d,y,w);
    		}
    		printf("%lld\n",ans);
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
    • 挂一下官方题解的 set 维护做法。
      • a1,b1 分别代替 a,b ;且 x,y[0,min(m,d)1]

T3 C. Charlie 的运输网 5pts/5pts/5pts

  • 没看出来是二分图。

T4 D. David 与和谐号 0pts/0pts/0pts

  • 容易想到的一种翻转方案是,从大到小枚举 i ,每次将 i 翻转到 a1 再翻转到 ai ,故答案上界为 2n2

  • 观察到 n25 ,故步数不多,考虑迭代加深。

  • 每次翻转只会改变一对相邻数对之间的差,故最终答案一定会大于相邻数对之间差 >1 的数量。

  • 需要适当剪枝,时间复杂度为 O()

    点击查看代码
    int a[30],flag;
    bool check(int n)
    {
    	for(int i=1;i<=n;i++)
    	{
    		if(a[i]!=i)
    		{
    			return false;
    		}
    	}
    	return true;
    }
    void dfs(int sum,int cnt,int last,int ans,int n)
    {
    	if(sum+cnt>ans)//最优性剪枝
    	{
    		return;
    	}
    	if(check(n)==true)
    	{
    		flag=1;
    		return;
    	}
    	for(int i=n;i>=2;i--)
    	{
    		if(i!=last)//转回来一定不优
    		{   
    			int tmp=cnt-(i!=n&&abs(a[i]-a[i+1])>1)+(i!=n&&abs(a[1]-a[i+1])>1);
    			reverse(a+1,a+1+i);
    			dfs(sum+1,tmp,i,ans,n);
    			reverse(a+1,a+1+i);
    			if(flag==1)
    			{
    				return;
    			}
    		}
    	}
    }
    int main()
    {	
    	freopen("david.in","r",stdin);
    	freopen("david.out","w",stdout);
    	int t,n,cnt,ans=0,i,j;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>n;
    		cnt=flag=0;
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    		}
    		for(i=2;i<=n;i++)
    		{
    			cnt+=(abs(a[i]-a[i-1])>1);
    		}
    		for(ans=0;;ans++)
    		{
    			dfs(0,cnt,0,ans,n);
    			if(flag==1)
    			{
    				cout<<ans<<endl;
    				break;
    			}
    		}
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

总结

  • T1 一开始以为做法和最长上升子序列差不多,遂写了权值树状数组、动态开点线段树优化转移,然后发现假了。在写完 50pts 的部分分后去上了趟厕所,回来的时候已经快 9:10 了,强逼着自己把测试点 1416 部分分写完了就去推 T2 的式子了。
    • 貌似最长上升子序列做法也是对的,详见 @ccxswl 的做法,但是需要平衡树维护,没理解为啥对。
  • T2 推了半天式子才写了个 O(m) 的做法,结果发现和 O(m2) 一个分。
  • T3,T4 仅留了半个小时看题,直接摆烂了。
posted @   hzoi_Shadow  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
扩大
缩小
点击右上角即可分享
微信分享提示