初中信息奥赛模拟测试

初中信息奥赛模拟测试

\(T1\) ZEW 玩扫雷 \(100pts\)

  • 原题:luogu P2670 [NOIP2015 普及组] 扫雷游戏
    • 本题修改:本题的非地雷地区为 .
  • 正解
    char a[1010][1010];
    int main()
    {
    	//freopen("mine.in","r",stdin);
    	//freopen("mine.out","w",stdout);
    	int n,m,i,j,sum;
    	cin>>n>>m;
    	for(j=0;j<=m+1;j++)
    	{
    		a[0][j]=a[n+1][j]='.';
    	}
    	for(i=1;i<=n;i++)
    	{
    		a[i][0]='.';
    		for(j=1;j<=m;j++)
    		{
    			cin>>a[i][j];
    		}
    		a[i][m+1]='.';
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			if(a[i][j]=='*')
    			{
    				cout<<"*";
    			}
    			else
    			{
    				sum=(a[i-1][j-1]=='*')+(a[i-1][j]=='*')+(a[i-1][j+1]=='*')+(a[i][j-1]=='*')+(a[i][j+1]=='*')+(a[i+1][j-1]=='*')+(a[i+1][j]=='*')+(a[i+1][j+1]=='*');
    				cout<<sum;
    			}
    		}
    		cout<<endl;
    	}
    	//fclose(stdin);
    	//fclose(stdout);
    	return 0;
    }
    

\(T2\) ZEW 的游戏 \(100pts\)

  • 原题:luogu P2665 [USACO08FEB] Game of Lines S
  • 正解
    • 因为一个点可以引多条直线,求互不平行直线个数等价于求不同斜率的个数。
    • 另外的一些细节:因存在精度误差,求出斜率后不用 double 去存,而是存其对应的最简分式的分子和分母;注意要处理平行于坐标轴的情况,以免导致除法运算时除数为 \(0\) 导致 \(RE\)
      • @xrlong 等人因此挂分。
        bitset<500>vis[500];//bitset可用bool数组代替
        map<int,map<int,int> >a;
        int x[500],y[500];
        int main()
        {
        	//freopen("game.in","r",stdin);
        	//freopen("game.out","w",stdout);
        	int n,i,j,d,ans=0,p,q,pd,pd1=0,pd2=0;//pd1存是否有平行于y轴,pd2存是否有平行于x轴
        	cin>>n;
        	for(i=1;i<=n;i++)
        	{
        		cin>>x[i]>>y[i];
        	}
        	for(i=1;i<=n;i++)
        	{
        		for(j=1;j<=n;j++)
        		{
        			if(i!=j&&vis[i][j]==0)
        			{
        				vis[i][j]=1;
        				p=y[i]-y[j];
        				q=x[i]-x[j];
        				pd=(p<0)*(q<0);
        				if(pd==1)//同时消去分子、分母的负号
        				{
        					p=-p;
        					q=-q;
        				}
        				else
        				{
        					if(q<0)//如果斜率为负数,规定分子为负数,分母为正数
        					{
        						p=-p;
        						q=-q;
        					}
        				}
        				if(q==0)
        				{
        					ans+=(pd1==0);
        					pd1=1;
        				}
        				else
        				{
        					if(p==0)
        					{
        						ans+=(pd2==0);
        						pd2=1;
        					}
        					else
        					{
        						d=__gcd(abs(p),abs(q));//先取abs再求gcd不再多说
        						ans+=(a[p/d][q/d]==0);
        						a[p/d][q/d]=1;
        					}
        				}
        			}
        		}
        	}
        	cout<<ans<<endl;
        	//fclose(stdin);
        	//fclose(stdout);
        	return 0;
        }
        

\(T3\) ZEW 学分块 \(40pts\)

  • 强化版:luogu P1182 数列分段 Section II | luogu P2884 [USACO07MAR] Monthly Expense S
    • 本题修改:本题规定 \(m=3\)
  • 部分分
    • \(40pts\) :枚举左右端点,没什么好说的。
  • 正解
    • 二分答案,时间复杂度为 \(O(n\log_{2}V)\) ,其中 \(V\) 是值域。
      ll a[5000010];
      bool check(ll mid,ll n)
      {
      	ll num=0,ans=0,i;
      	for(i=1;i<=n;i++)
      	{
      		if(num+a[i]<=mid)
      		{
      			num+=a[i];
      		}
      		else
      		{
      			num=a[i];
      			ans++;
      		}
      	}
      	return (ans>=3);
      }
      int main()
      {
      	//freopen("divide.in","r",stdin);
      	//freopen("divide.out","w",stdout);
      	ll n,i,l=0,r=0,mid;
      	scanf("%lld",&n);
      	for(i=1;i<=n;i++)
      	{
      		scanf("%lld",&a[i]);
      		l=max(l,a[i]);
      		r+=a[i];
      	}
      	while(l<=r)
      	{
      		mid=(l+r)/2;
      		if(check(mid,n)==true)
      		{
      			l=mid+1;
      		}
      		else
      		{
      			r=mid-1;
      		}
      	}
      	printf("%lld\n",l);
      	//fclose(stdin);
      	//fclose(stdout);
      	return 0;
      }
      
    • 因本题规定 \(m=3\) ,故也可使用双指针做法,时间复杂度为 \(O(n)\)
      • 注:赛时 \(AC\) 的只有 @wang54321 使用了该做法。
      • 将原序列分成 \(1 \sim l,l+1 \sim r,r+1 \sim n\) 三个部分。固定 \(r\) 不动,此时已知 \(sum_{n}-sum_{r}\) ,从贪心的角度分析,若要使 \(\max(sum_{l},sum_{r}-sum_{l},sum_{n}-sum_{r})\) 尽可能的小,便要使 \(sum_{l}\)\(sum_{r}-sum_{l}\) 尽可能接近。类似 2022年普及组3 T2 特 ,使用双指针维护第一个 \(sum_{l}>sum_{r}-sum_{l}\) 的位置 \(l\) ,并可以得到最后一个 \(sum_{l} \le sum_{r}-sum_{l}\) 的位置 \(l\) ,分别对三者取 \(\max\) 然后取 \(\min\) 即可。
        ll a[5000010],sum[5000010];
        int main()
        {
            //freopen("divide.in","r",stdin);
            //freopen("divide.out","w",stdout);
            ll n,i,l,r,ans=0x7f7f7f7f;
            scanf("%lld",&n);
            for(i=1;i<=n;i++)
            {
                scanf("%lld",&a[i]);
                sum[i]=sum[i-1]+a[i];
            }
            for(l=1,r=2;r<=n-1;r++)
            {
                while(sum[l]<sum[r]-sum[l])
                {
                    l++;
                }
                ans=min(ans,min(max(sum[l],max(sum[r]-sum[l],sum[n]-sum[r])),max(sum[l-1],max(sum[r]-sum[l-1],sum[n]-sum[r]))));
            }
            printf("%lld\n",ans);
            //fclose(stdin);
            //fclose(stdout);
            return 0;
        }
        
    • 贪心的做法,详见 @minecraft1145144.【初中信息奥赛模拟测试】

\(T4\) ZEW 玩 thd \(10pts\)

  • 部分分
    • \(10pts\) :输出 0
    • \(20pts\) :输出 \(\sum\limits_{i=1}^{n}g_{i}\)
  • 正解
    • \(DP\)
      • 可以滚动数组优化。
    • 一个比较显然的贪心思想:如果我们想要得到一个小兵的金币,应该让塔攻击这个小兵尽可能多的次数,然后人来进行补刀。
      • 不一定只能补刀 \(1\) 次。
    • 观察到塔和人的攻击可以看作是同步进行的,转化为人可以留着攻击次数。
    • \(ta_{i}(1 \le i \le n)\) 表示关于 \(x\) 的不等式 \(qx<h_i\) 的最小正整数解, \(ren_{i}= \left\lceil\dfrac{h-ta_{i}q}{p}\right\rceil\) 。即 \(ren_{i}(1 \le i \le n)\) 表示在塔打出最高伤害后,为击杀小兵人需要的补刀次数。
    • \(f_{i,j}\) 表示塔攻击第 \(i\) 个小兵时,留着 \(j\) 次攻击次数的得到的最大金币数。转移过程中有两种操作:不攻击该小兵,,消耗 \(0\) 次攻击次数,让塔将其击杀,获得 \(0\) 个金币;进行补刀,消耗 \(ren_{i}\) 次攻击次数,获得 \(g_i\) 个金币。
    • 枚举攻击次数 \(j\) ,消耗 \(0\) 次攻击次数,塔需要攻击 \(ta_{i}+1\) 次才能击杀第 \(i\) 个小兵,有 \(f_{i,j+ta_{i}+1}= \max(f_{i,j+ta_{i}+1},f_{i-1,j}+0)\) ;在 \(j \ge ren_{i}-ta_{i}\) 时,消耗 \(ren_{i}-ta_{i}\) 次攻击次数,有 \(f_{i,j-(ren_{i}-ta_{i})}= \max(f_{i,j-(ren_{i}-ta_{i})},f_{i-1,j}+g_i)\)
      ll h[101],g[101],ren[101],ta[101],f[101][20001];//因本题数据过于逆天,所以不要吝啬于枚举值域的大小
      int main()
      {
      	//freopen("thd.in","r",stdin);
      	//freopen("thd.out","w",stdout);
      	ll p,q,n,i,j,k,ans=0;
      	cin>>p>>q>>n;
      	memset(f,-0x3f,sizeof(f));
      	for(i=1;i<=n;i++)
      	{
      		cin>>h[i]>>g[i];
      		ta[i]=(h[i]%q==0)?(h[i]/q-1):(h[i]/q);
      		ren[i]=(ll)(ceil((1.0*(h[i]-q*ta[i])/p)));
      	}
      	f[0][1]=0;
      	for(i=1;i<=n;i++)
      	{
      		for(j=0;j<=10000;j++)
      		{
      			f[i][j+ta[i]+1]=max(f[i][j+ta[i]+1],f[i-1][j]+0);
      			if(j-(ren[i]-ta[i])>=0)
      			{
      				f[i][j-(ren[i]-ta[i])]=max(f[i][j-(ren[i]-ta[i])],f[i-1][j]+g[i]);
      			}
      		}
      	}
      	for(i=0;i<=20000;i++)
      	{
      		ans=max(ans,f[n][i]);
      	}
      	cout<<ans<<endl;
      	//fclose(stdin);
      	//fclose(stdout);
      	return 0;
      }
      

总结

没有挂分,挺好的。

\(20:40\) 之后基本就在罚坐了,头脑一片空白。

及时使用 \(Geany\)\(RE\)

\(T3\) 告诉我们,有时候可以选择从一般到特殊的思考方向,不要拘泥于某一种方法。而且犯了和 普及模拟1 T1 Past 一样的问题,没有意识到是原题。之所以出现这个问题,是因为对题目本质的认识不清楚,或者说是没有一个相对模块化的思想。

\(T4\) 告诉我们:在时间足够的情况下,不要吝啬于枚举值域的大小;在内存足够的情况下,不要吝啬于数组空间的大小。因为你无法保证,你赌的值域能不被恶心的数据卡掉。

后记

题目没有 \(\LaTeX\) ,差评。

\(T4\)\(10\) 个点不符合数据范围,差评。

比赛的时候机房人比平常人多了不少。从奥赛宣讲后,已经过了多半年了,甚至还有人没学文件读写来打 \(OI\) 赛制比赛,难评。\(feifei\) 因有人没学文件读写,而把文件读写删了,导致删后没有重交(误以为 \(OI\) 赛制只能提交一次)的人爆零了,虽然赛后的确安排了重测,但这属实不应该出现在校内考试中。

posted @ 2024-01-13 22:48  hzoi_Shadow  阅读(107)  评论(9编辑  收藏  举报
扩大
缩小