中山市迪茵公学第一届“图灵杯”信息学编程大赛初试题解
我是本次比赛 T2 的出题人
本次比赛没有出现高深的算法,主要在于思维上的比拼
个人认为这次比赛题目质量不错,组题组的也很妙
感谢何教练带来一场优质的比赛
建议开题顺序 T3 -> T1 ->T2 ->T4
周末看能不能搞一个视频题解
T1 小朋友玩游戏
题意
对于一个有正有负的环,求出当中的最大区间和
做法
枚举所有区间,对于每个区间分别枚举求出区间和,再记录最大值
这种做法期望拿到
请注意题目要求的是一个环,自行思考如何枚举到所有区间,求区间和需要注意什么
做法
考虑优化求区间和的办法
我们在输入时处理出一个数组
这被称之为前缀和,接下来介绍如何用前缀和求出一个区间的和
如上,如果想求出区间
我们发现黄色部分正好在
那么多出来了什么呢?显然是蓝色部分,蓝色部分是什么?
你可能以为是
我们通过前缀和求出了所有
这样求的时间复杂度为
然后还是枚举区间
注意到题目是个环,所以区间可能是这样的
这种情况请大家自行思考
事实上,有两种方式,请大家把两种都想出来
提示:整体减空白
这种做法可以拿到
正解: 做法
其实区间只有两种情况,如下
(P.S.:我的Pinta崩了,所以这里换了一个画图工具,造成前后风格不一致,在此抱歉)
先看看第一种情况
考虑枚举
因为前面我们推导出了
我们干脆记录下
这个最小值初始为
代码如下
for (int i = 1; i <= n; i++) { ans = max(ans2, sum[i] - mnsum); mnsum = min(mnsum, sum[i]); }
解决了第一种情况,来看看第二种情况
其实可以利用整体减空白的思想,让不选的区间的和尽可能小
问题转化成了一个类似于第一种情况的问题了
求解最小区间的原理与最大区间类似
我们应该让
最大,记录下 中的最大值
不过注意题目要求不能不选,所以特判一下,避免不选的区间选择了全部
以上做法满分
#include <bits/stdc++.h> using namespace std; long long n, a[1000005], sum[1000005], mxsum , mnsum , ans1 = 1e9, ans2 = -1e9, vis, ans; int main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; sum[i] = sum[i - 1] + a[i]; } for (int i = 1; i <= n; i++) { ans1 = min(ans1, sum[i] - mxsum); ans2 = max(ans2, sum[i] - mnsum); mnsum = min(mnsum, sum[i]); if (mxsum <= sum[i]) { mxsum = sum[i]; vis = 1; } } ans = ans2; if ((vis == 1 || ans1 != sum[n]) && sum[n]-ans1 > ans2) { ans = sum[n]-ans1; } cout << ans; }
奇怪做法:单调队列
破环为链之后,用单调队列维护连续
问题类似于上面的情况一
不懂单调队列的同学可以不管
T2 直角三角形的数量
我校评测机太好了(优于大多数Online Judge),优化的暴力可以满分
如果想要
(本人测试,一些在我校平台满分的代码在Luogu只有30分)
链接:T239755 [DuckOI&DiyinOI]直角三角型的数量||简单三角形计数题
然后,因为本人疏忽,本题一个符号打错了,可能给大家带来不便,抱歉
简化题意
大家都知道勾股定理吧,对于一个直角三角形,两直角边平方之和等于斜边的平方
式子表示为
其实还有一个勾股逆定理,若三角形满足
所以本题可以理解为
已知正整数
,求 的所有正整数解
做法
直接枚举
这样显然超时
做法
可以枚举
稍微快一点,
做法
考虑对
得二元一次方程组
解得
所以有一个要求
才能使
对于每一个
如何分解
如果直接分解
可以分解
memset(cnt,0,sizeof(cnt)); long long k=x; for(int j=2;j<=k;j++) { if(k%j==0) { cnt[0][0]++; cnt[cnt[0][0]][0]=j; while(k%j==0) { cnt[cnt[0][0]][1]++; k/=j; } cnt[cnt[0][0]][1]*=2; //to be x^2 } }
然后枚举每一个素因子,复杂度为
其实还有一堆优化(甚至可以把难度提升至弱省选难度),在此不多阐述
经过众人讨论,现在发现以下复杂度的做法
希望有人可以发现
做法,可以发给我 邮箱: moudengya123@qq.com
代码如下
#include<bits/stdc++.h> using namespace std; long long n,x,ans,cnt[20005][2]; void dfs(long long t,long long b) { if(t>cnt[0][0]) { long long a=x*x/b; if(a%2==b%2) { if(x<(a-b)/2&&(a+b)/2<=n) { ans++; } } return; } for(int i=0;i<=cnt[t][1];i++) { dfs(t+1,b); b*=cnt[t][0]; } } int main() { cin>>n; for(x=1;x*x*2<=n*n&&x<=n;x++) { memset(cnt,0,sizeof(cnt)); long long k=x; for(int j=2;j<=k;j++) { if(k%j==0) { cnt[0][0]++; cnt[cnt[0][0]][0]=j; while(k%j==0) { cnt[cnt[0][0]][1]++; k/=j; } cnt[cnt[0][0]][1]*=2; } } dfs(1,1); } cout<<ans<<endl; }
其他做法:打表
本题显然可以打表,但不是求出对于每一个
而是找出
再对应每个基本勾股数,求出
数学名词自行百度
upd:更新了效率更高的代码
这份代码的思路不变,但是预处理出了素数,并用位运算代替了一些操作,等等优化
原先的代码运行了4000ms
,本代码只有300ms
#include <stdio.h> #include <bits/stdc++.h> using namespace std; int n, x, ans, cnt[21][2], s[30005], b[1005]; void dfs(int t, int b) { if (t > cnt[0][0]) { int a = x * x / b; if (!((a & 1) ^ (b & 1))) { if (x < ((a - b) >> 1) && ((a + b) >> 1) <= n) { ans++; } } return; } for (int i = 0; i <= cnt[t][1]; i++) { dfs(t + 1, b); b *= cnt[t][0]; if (b * b > x * x) return; } } int main() { cin >> n; s[1] = 1; for (int i = 2; i <= 25000; i++) { if (s[i] == 0) { b[++b[0]] = i; for (int j = i + i; j <= 25000; j += i) { s[j] = 1; } } } for (x = 3; x * x * 2 <= n * n && x <= n; x++) { memset(cnt, 0, sizeof(cnt)); int k = x; for (int j = 1; j <= k; j++) { if (!((k % b[j]) ^ 0)) { cnt[0][0]++; cnt[cnt[0][0]][0] = b[j]; while (!((k % b[j]) ^ 0)) { cnt[cnt[0][0]][1]++; k /= b[j]; } cnt[cnt[0][0]][1] <<= 1; } } dfs(1, 1); } cout << ans << endl; }
T3 奖金设置
一道简单枚举题,跳过
本人代码有一些优化,事实证明不优化也可以
#include <bits/stdc++.h> using namespace std; long long n,m,ans; int main() { cin>>n>>m; if(m%100==0) { m/=100; } else { cout<<0<<endl; } n=round(n*1.000000/5);//四舍五入函数 n-=1; m-=30; for(int A=1;A<=n;A++) { for(int B=A;A+B<=n;B++) { int C=n-A-B; if(C<B)continue; for(int a=30;a>=1;a--) { for(int b=a;b>=1;b--) { int c=m-A*a-B*b; if(c<=0)continue; if(c%C!=0)continue; c/=C; if(c>b)continue; ans++; } } } } cout<<ans; }
T4 游戏
思维题,好题
首先化简分数
设拿分值为
显然有多解
这里提供一种构造方法,只拿
比如:
假设一开始只拿某一种牌,然后通过看与
渐渐将一些牌改成另一种牌
其实就是小学奥数的假设法
先认为全是某一种东西
然后再比对与想要结果的差进行修改
一个更直接的说法:鸡兔同笼
#include<bits/stdc++.h> using namespace std; #define ll long long double n; ll a,b,g,k,ans[11]; ll gcd(ll x,ll y) { return x%y==0?y:gcd(y,x%y); } int main() { cin>>n; a=(ll)(n*1000000000),b=1000000000; g=gcd((ll)(n*1000000000),1000000000); a/=g,b/=g; for(int i=5;i>=1;i--) { if(b*i<a) { k=i; break; } } int x=a-b*k; //cout<<a<<' '<<b<<' '<<k<<' '<<x<<endl; ans[k]=b-x,ans[k+1]=x; for(int i=1;i<=5;i++) { cout<<ans[i]<<' '; } return 0; }
代码来自于Huangzixin大佬,本人还没有调出来,只有
如果觉得不错的话,就给一个赞吧!
作者是 DengDuck ,转载请注明出处
文章链接: https://www.cnblogs.com/dengduck/p/16263670.html
感谢您阅读!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步