NOIP2024模拟1

NOIP2024模拟1

T1 GHzoj 3752. 分糖果 100pts

  • 数据加强版: U203804 test1(只输出最多小组数)

  • 设最终答案中有 a 个小组中的小朋友的糖数 mod3 均等于 1b 个小组中的小朋友的糖数 mod3 互不相等, c 个小组中的小朋友的糖数 mod3 均等于 0d 个小组中的小朋友的糖数 mod3 均等于 2

  • 得到不等式组 {03a+bcnt103c+bcnt003d+bcnt20bmin(cnt0,cnt1,cnt2) ,枚举 b[0min(cnt0,cnt1,cnt2)] ,有 {max{a}=cnt1b3max{c}=cnt0b3max{d}=cnt2b3 ,计算 max(a+b+c+d) 即可。

  • 最终,有 maxb=0min(cnt0,cnt1,cnt2){cnt1b3+b+cnt0b3+cnt2b3} 即为所求。

    点击查看代码
    ll f[100010],cnt[5];
    queue<ll>q[5];
    int main()
    {
    	ll n,ans=0,pos=0,i,j,k,a,b,c,d;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>f[i];
    		cnt[f[i]%3]++;
    		q[f[i]%3].push(i);
    	}
    	for(b=0;b<=min(cnt[0],min(cnt[1],cnt[2]));b++)
    	{
    		a=(cnt[1]-b)/3;
    		c=(cnt[0]-b)/3;
    		d=(cnt[2]-b)/3;
    		if(a+b+c+d>ans)
    		{
    			ans=a+b+c+d;
    			pos=b;
    		}
    	}
    	cout<<ans<<endl;
    	if(ans!=0)
    	{
    		for(i=1;i<=pos;i++)
    		{
    			for(k=0;k<=2;k++)
    			{
    				cout<<q[k].front()<<" ";
    				q[k].pop();
    			}
    			cout<<endl;
    		}
    		for(k=0;k<=2;k++)
    		{
    			for(i=1;i<=(cnt[k]-pos)/3;i++)
    			{
    				for(j=1;j<=3;j++)
    				{
    					cout<<q[k].front()<<" ";
    					q[k].pop();
    				}
    				cout<<endl;
    			}
    		}
    	}
    	return 0;
    }
    

T2 GHzoj 3753. 乒乓球 30pts

  • 严格弱化版: luogu P1042 [NOIP2003 普及组] 乒乓球

  • 部分分

    • 30ptsO(n) 枚举。

      点击查看代码
      char s[100010];
      int main()
      {
      	ll n,k,suma=0,sumb=0,ansa=0,ansb=0,i;
      	cin>>n>>k>>(s+1);
      	for(i=1;i<=n;i++)
      	{
      		if(s[(i-1)%k+1]=='A')
      		{
      			suma++;
      			if(suma>=11&&suma-sumb>=2)
      			{
      				ansa++;
      				suma=sumb=0;
      			}
      		}
      		else
      		{
      			sumb++;
      			if(sumb>=11&&sumb-suma>=2)
      			{
      				ansb++;
      				suma=sumb=0;
      			}
      		}
      	}
      	cout<<ansa<<":"<<ansb<<endl;
      	return 0;
      }
      
  • 正解

    • 猜测在一定条件下, zwh 和小红的得分是会有循环节的。而这个条件主要限制来自 n 和追分环节。
    • 预先排除无意义追分,比如 ABABBABA
    • 若在新的一轮环节开始时, zwh 和小红已经进入追分环节,二人分数同时减少一个定值使其缩小至 [0,11] ,再接着开始这一轮分数同时减少这一操作对他们的结果(此时不再管分数 11 的限制)是没有影响的。
    • sta,b 表示最早是第几个球时 zwh 和小红的单局比赛比分为 a:b ,此时 zwh 和小红分别胜了 vaa,b,vba,b 场。当下一次遇到 zwh 和小红的单局比赛比分为 a:b 时,设此时是第 st 个球, zwh 和小红分别胜了 A,B 场,则可以将其看作 sta,bst 是一个周期, zwh 和小红分别增加的胜的场数为 Avaa,b,Bvba,b 。跳出整个大周期后继续处理剩余的时间即可。
    点击查看代码
    ll len[100010],to[100010],va[100010],vb[100010];
    char s[100010];
    void ask(ll pos,ll newlen,ll newa,ll newb,ll &ansa,ll &ansb,ll n)
    {
    	if(len[pos]<=n)
    	{
    		ansa+=va[pos]+(n-len[pos])/(newlen-len[pos])*(newa-va[pos]);//计算循环节的贡献
    		ansb+=vb[pos]+(n-len[pos])/(newlen-len[pos])*(newb-vb[pos]);
    		n=(n-len[pos])%(newlen-len[pos]);//除去多出来的部分
    	}
    	else
    	{
    		pos=0;
    	}
    	ll st=pos;
    	while(len[to[pos]]-len[st]<=n&&to[pos]!=st)
    	{
    		pos=to[pos];
    	}
    	ansa+=va[pos]-va[st];//计算多出来的贡献
    	ansb+=vb[pos]-vb[st];
    }
    int main()
    {
    	ll n,k,suma,sumb,ansa=0,ansb=0,flag=0,i,j,pos;
    	cin>>n>>k>>(s+1);
    	s[0]=s[k];
    	memset(to,0x3f,sizeof(to));
    	for(i=0;i<=k;)
    	{
    		pos=i;   
    		suma=sumb=0;
    		for(j=1;;j++)//开始找循环节,记录步数
    		{
    			if(j>=max(k,22ll)*2+2)//无意义追分
    			{
    				flag=1;
    				break;
    			}
    			i=(i+1)%k;
    			suma+=(s[i]=='A');
    			sumb+=(s[i]=='B');
    			if(suma>=11&&suma-sumb>=2)
    			{
    				to[pos]=i;
    				if(to[i]<k)//第二次遇到,找到循环节了
    				{
    					ask(i,len[pos]+j,va[pos]+1,vb[pos],ansa,ansb,n);
    					flag=1;
    				}
    				len[i]=len[pos]+j;
    				va[i]=va[pos]+1;
    				vb[i]=vb[pos];
    				break;
    			}
    			if(sumb>=11&&sumb-suma>=2)
    			{
    				to[pos]=i;
    				if(to[i]<k)//第二次遇到,找到循环节了
    				{
    					ask(i,len[pos]+j,va[pos],vb[pos]+1,ansa,ansb,n);
    					flag=1;
    				}
    				len[i]=len[pos]+j;
    				va[i]=va[pos];
    				vb[i]=vb[pos]+1;
    				break;
    			}
    		}
    		if(flag==1)
    		{
    			break;
    		}
    	}
    	cout<<ansa<<":"<<ansb<<endl;
    	return 0;
    }
    

T3 GHzoj 3754. 与或 15pts

  • 部分分

    • 正解

      • 容易有 {x|ymax(x,y)x&ymin(x,y)x&yx|y ,进而得到 x|y&zmin(x|y,z)max(x&y,z)x&y|z ,即若要使表达式值大需要尽可能将 & 放到 | 前面。但要使字典序小需要尽可能将 | 放到 & 前面。故可以得知最后的答案一定形如 |...|&...&|...| ,其中 &...& 中可能会夹杂着 |
      • 展开后按位前缀和统计 1 的个数,接着手动实现 &| ,在不影响理论答案最大时将选择合适的 |& 交换。
      点击查看代码
      ll a[200010],sum[200010][70];
      char s[200010];
      ll ask(ll l,ll k,ll n,ll num)
      {
      	if(l<=n-k)//[l,n-k] 是 &
      	{
      		for(ll i=0;i<=60;i++)
      		{
      			if(((num>>i)&1)&&sum[n-k][i]-sum[l-1][i]!=n-k-l+1)
      			{
      				num-=(1ll<<i);
      			}
      		}
      	}
      	if(n-k+1<=n)//(n-k,n] 是 |
      	{
      		for(ll i=0;i<=60;i++)
      		{
      			if(sum[n][i]-sum[n-k+1-1][i]!=0)
      			{
      				num|=(1ll<<i);
      			}
      		}
      	}
      	return num;
      }
      int main()
      {
      	ll n,k,ans,num,i,j;
      	cin>>n>>k;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      		for(j=0;j<=60;j++)
      		{
      			sum[i][j]=sum[i-1][j]+((a[i]>>j)&1);
      		}
      	}
      	ans=ask(2,k,n,a[1]);
      	cout<<ans<<endl;
      	num=a[1];
      	for(i=2;i<=n;i++)
      	{
      		if(k>=1&&ask(i+1,k-1,n,num|a[i])==ans)
      		{
      			num|=a[i];
      			k--;
      			cout<<"|";
      		}
      		else
      		{
      			num&=a[i];
      			cout<<"&";
      		}
      	}
      	return 0;
      }
      

T4 GHzoj 3755. 跳舞 0pts

  • a0=an+1=1
    • fl,r 表示 [l,r] 是否可以全部离开,状态转移方程为 fl,r=maxk=lr{fl,k1×fk+1,r×[gcd(ak,al1)>1gcd(ak,ar+1)>1]} ,边界为 {fl,r=10r<ln+1fi,i=[gcd(ai,ai1)>1gcd(ai,ai+1)>1]i[1,n+1]

      • 边界如果处理得不恰当,则会被 hack

        点击查看 hack 数据 1
        in:
        5
        3 8 9 20 18
        ans:
        4
        
        
        点击查看 hack 数据 2
        in:
        3
        342982640 708917468 453005496
        
        ans:
        2
        
        
    • dpi 表示前 i 个人中在留下第 i 个人的情况下最多能离开多少个人。状态转移方程为 dpi=maxj=0i1{dpj+fj+1,i1×((i1)(j+1)+1)} ,边界为 dp0=dp1=0

    • 最终,有 dpn+1 即为所求。

      • 其实 dpn 也是正确的。因为当最终第 n 个人和另一个人跳舞后,第 n 个人离开但另一个人留下时完全可以转化为另一个人离开但第 n 个人留下,只是涉及钦定让谁留下的问题。
      点击查看代码
      ll a[510],d[510][510],f[510][510],dp[510];
      ll gcd(ll a,ll b)
      {
      	return b?gcd(b,a%b):a;
      }
      int main()
      {
      	ll n,i,j,k,len,l,r;
      	cin>>n;
      	a[0]=1;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      	}
      	a[n+1]=1;
      	for(i=1;i<=n;i++)
      	{
      		for(j=1;j<=n;j++)
      		{
      			d[i][j]=gcd(a[i],a[j]);
      		}
      	} 
      	for(i=1;i<=n+1;i++)
      	{
      		for(j=0;j<=i-1;j++)
      		{
      			f[i][j]=1;
      		}
      		if(d[i][i-1]>1||d[i][i+1]>1)
      		{
      			f[i][i]=1;
      		}
      	}
      	for(len=2;len<=n;len++)
      	{
      		for(l=1,r=l+len-1;r<=n;l++,r++)
      		{
      			for(k=l;k<=r;k++)
      			{
      				f[l][r]=max(f[l][r],f[l][k-1]*f[k+1][r]*(d[k][l-1]>1||d[k][r+1]>1));
      			}
      		}
      	}
      	dp[0]=dp[1]=0;
      	for(i=2;i<=n+1;i++)
      	{
      		for(j=0;j<=i-1;j++)
      		{
      			dp[i]=max(dp[i],dp[j]+f[j+1][i-1]*((i-1)-(j+1)+1));
      		}
      	}
      	cout<<dp[n+1]<<endl;
      	return 0;
      }
      

T5 GHzoj 3752. 音乐播放器 0pts

  • 部分分

    • 正解

      • fi,j 表示听了 i 种歌,总愉悦程度为 j 的方案数,因为有 id 的限制,填表法有点难写,考虑刷表,有 fi+1,j+ak+=fi,j(kid) ,边界为 f0,0=1
      • 对于每个 id[1,n] 均进行一次 DP ,最终有 i=0n1j=saids1fi,j×AiiAni+1 即为所求。单次询问时间复杂度为 O(n2s) ,复杂度瓶颈在状态转移过程中的枚举 k
      • 枚举 k 实际上是计算除 id 以外的贡献,这就提醒我们可以先计算总的贡献,然后将贡献撤销回去从而统计单个答案,再把答案加回来,实质上是一个回退背包。
      点击查看代码
      const ll p=998244353;
      ll a[110],inv[110],jc[110],jc_inv[110],f[110][10010];
      ll A(ll n,ll m,ll p)
      {
      	return (n>=m&&n>=0&&m>=0)?jc[n]*jc_inv[n-m]%p:0;
      }
      ll A_inv(ll n,ll m,ll p)
      {
      	return (n>=m&&n>=0&&m>=0)?jc_inv[n]*jc[n-m]%p:0;
      }
      int main()
      {
      	ll n,s,ans=0,i,j,k;
      	cin>>n>>s;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      	}
      	inv[1]=1;
      	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
      	for(i=2;i<=n;i++)
      	{
      		inv[i]=(p-p/i)*inv[p%i]%p;
      		jc[i]=jc[i-1]*i%p;
      		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
      	}
      	f[0][0]=1;
      	for(i=1;i<=n;i++)
      	{
      		for(j=n-1;j>=0;j--)
      		{
      			for(k=0;k<=s-a[i]-1;k++)
      			{
      				f[j+1][k+a[i]]=(f[j+1][k+a[i]]+f[j][k])%p;
      			}
      		}
      	}
      	for(i=1;i<=n;i++)
      	{
      		ans=0;
      		for(j=0;j<=n-1;j++)
      		{
      			for(k=0;k<=s-a[i]-1;k++)
      			{
      				f[j+1][k+a[i]]=(f[j+1][k+a[i]]-f[j][k]+p)%p;
      			}
      		}
      		for(j=0;j<=n-1;j++)
      		{
      			for(k=s-a[i];k<=s-1;k++)
      			{
      				ans=(ans+(f[j][k]*A(j,j,p)%p)*A_inv(n,j+1,p)%p)%p;
      			}
      		}
      		for(j=n-1;j>=0;j--)
      		{
      			for(k=0;k<=s-a[i]-1;k++)
      			{
      				f[j+1][k+a[i]]=(f[j+1][k+a[i]]+f[j][k])%p;
      			}
      		}
      		cout<<ans<<" ";
      	}
      	return 0;
      }
      

总结

  • 有大样例,赢。
  • T1 喜提除 admin 外最劣解。一开始少了一种情况下发大样例后调了半天才发现,还特意写了个阉割般的 Special Judge 来检验答案,力挽狂澜,要不然就没 @STA_Morlin 分高了。
  • T3 因为 1<<i 最终的类型是 int ,但 1ll<<i 最终的类型是 long long 调了半天。
  • T4 看起来像是有后效性 DP ,等把状态转移方程写出后发现有 max ,高斯消元解不了。

后记

  • 信息与公告如下。要是明年 J 组是这个难度,直接爆炸了。

  • 题目背景多次出现 hugeT4 样例解释中出现赵喜娜。

  • T1 赛时才添加的 Special Judge ,导致交的早的没有 Special Judge ,交的晚的才有 Special Judge 。赛后安排了重测。

  • 数据太水了,放掉了不少的假贪心和边界处理不恰当的代码。

  • 官方题解中防抄袭代码明显。

posted @   hzoi_Shadow  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
扩大
缩小
点击右上角即可分享
微信分享提示