NOIP2024模拟1

NOIP2024模拟1

\(T1\) GHzoj 3752. 分糖果 \(100pts\)

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

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

  • 得到不等式组 \(\begin{cases} 0 \le 3a+b \le cnt_{1} \\ 0 \le 3c+b \le cnt_{0} \\ 0 \le 3d+b \le cnt_{2} \\ 0 \le b \le \min(cnt_{0},cnt_{1},cnt_{2}) \end{cases}\) ,枚举 \(b \in [0,\min(cnt_{0},cnt_{1},cnt_{2})]\) ,有 \(\begin{cases} \max \{ a \}=\left\lfloor \frac{cnt_{1}-b}{3} \right\rfloor \\ \max \{ c \}=\left\lfloor \frac{cnt_{0}-b}{3} \right\rfloor \\ \max \{ d \}=\left\lfloor \frac{cnt_{2}-b}{3} \right\rfloor \end{cases}\) ,计算 \(\max(a+b+c+d)\) 即可。

  • 最终,有 \(\max\limits_{b=0}^{\min(cnt_{0},cnt_{1},cnt_{2})} \{ \left\lfloor \frac{cnt_{1}-b}{3} \right\rfloor+b+\left\lfloor \frac{cnt_{0}-b}{3} \right\rfloor+\left\lfloor \frac{cnt_{2}-b}{3} \right\rfloor \}\) 即为所求。

    点击查看代码
    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 普及组] 乒乓球

  • 部分分

    • \(30pts\)\(O(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]\) ,再接着开始这一轮分数同时减少这一操作对他们的结果(此时不再管分数 \(\ge 11\) 的限制)是没有影响的。
    • \(st_{a,b}\) 表示最早是第几个球时 zwh 和小红的单局比赛比分为 \(a:b\) ,此时 zwh 和小红分别胜了 \(va_{a,b},vb_{a,b}\) 场。当下一次遇到 zwh 和小红的单局比赛比分为 \(a:b\) 时,设此时是第 \(st'\) 个球, zwh 和小红分别胜了 \(A,B\) 场,则可以将其看作 \(st_{a,b} \sim st'\) 是一个周期, zwh 和小红分别增加的胜的场数为 \(A-va_{a,b},B-vb_{a,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\)

  • 部分分

    • 正解

      • 容易有 \(\begin{cases} x|y \ge \max(x,y) \\ x \& y \le \min(x,y) \\ x \& y \le x|y \end{cases}\) ,进而得到 \(x|y \& z \le \min(x|y,z) \le \max(x \& y,z) \le 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\)

  • \(a_{0}=a_{n+1}=1\)
    • \(f_{l,r}\) 表示 \([l,r]\) 是否可以全部离开,状态转移方程为 \(f_{l,r}=\max\limits_{k=l}^{r} \{ f_{l,k-1} \times f_{k+1,r} \times [\gcd(a_{k},a_{l-1})>1 \lor \gcd(a_{k},a_{r+1})>1] \}\) ,边界为 \(\begin{cases} f_{l,r}=1 & 0 \le r<l \le n+1 \\ f_{i,i}=[\gcd(a_{i},a_{i-1})>1 \lor \gcd(a_{i},a_{i+1})>1] & i \in [1,n+1] \end{cases}\)

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

        点击查看 hack 数据 1
        in:
        5
        3 8 9 20 18
        ans:
        4
        
        
        点击查看 hack 数据 2
        in:
        3
        342982640 708917468 453005496
        
        ans:
        2
        
        
    • \(dp_{i}\) 表示前 \(i\) 个人中在留下第 \(i\) 个人的情况下最多能离开多少个人。状态转移方程为 \(dp_{i}=\max\limits_{j=0}^{i-1} \{ dp_{j}+f_{j+1,i-1} \times ((i-1)-(j+1)+1) \}\) ,边界为 \(dp_{0}=dp_{1}=0\)

    • 最终,有 \(dp_{n+1}\) 即为所求。

      • 其实 \(dp_{n}\) 也是正确的。因为当最终第 \(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\)

  • 部分分

    • 正解

      • \(f_{i,j}\) 表示听了 \(i\) 种歌,总愉悦程度为 \(j\) 的方案数,因为有 \(id\) 的限制,填表法有点难写,考虑刷表,有 \(f_{i+1,j+a_{k}}+=f_{i,j} (k \ne id)\) ,边界为 \(f_{0,0}=1\)
      • 对于每个 \(id \in [1,n]\) 均进行一次 \(DP\) ,最终有 \(\sum\limits_{i=0}^{n-1}\sum\limits_{j=s-a_{id}}^{s-1} \dfrac{f_{i,j} \times A_{i}^{i}}{A_{n}^{i+1}}\) 即为所求。单次询问时间复杂度为 \(O(n^{2}s)\) ,复杂度瓶颈在状态转移过程中的枚举 \(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\) 组是这个难度,直接爆炸了。

  • 题目背景多次出现 \(huge\)\(T4\) 样例解释中出现赵喜娜。

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

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

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

posted @ 2024-07-08 19:23  hzoi_Shadow  阅读(67)  评论(0编辑  收藏  举报
扩大
缩小