我是本次比赛 T2 的出题人
本次比赛没有出现高深的算法,主要在于思维上的比拼
个人认为这次比赛题目质量不错,组题组的也很妙
感谢何教练带来一场优质的比赛
建议开题顺序 T3 -> T1 ->T2 ->T4
周末看能不能搞一个视频题解
T1 小朋友玩游戏
题意
对于一个有正有负的环,求出当中的最大区间和
\(O(n^3)\)做法
枚举所有区间,对于每个区间分别枚举求出区间和,再记录最大值
这种做法期望拿到\(20\)分部分分
请注意题目要求的是一个环,自行思考如何枚举到所有区间,求区间和需要注意什么
\(O(n^2)\)做法
考虑优化求区间和的办法
我们在输入时处理出一个数组\(sum\),\(sum_i\)表示从第一项到第\(i\)项之和
这被称之为前缀和,接下来介绍如何用前缀和求出一个区间的和
如上,如果想求出区间\([L,R]\)的和,显然就是求出黄色部分
我们发现黄色部分正好在\([1,R]\)区间中,而且两区间结尾相同
那么多出来了什么呢?显然是蓝色部分,蓝色部分是什么?
你可能以为是\([1,L]\),那就错了,其实是\([1,L-1]\),第\(L\)项也是\([L,R]\)的一部分,不可忽视
我们通过前缀和求出了所有\([1,i]\)和为\(sum_i\),所以区间和可以这么求
这样求的时间复杂度为\(O(1)\),比暴力求好多了
然后还是枚举区间
注意到题目是个环,所以区间可能是这样的
这种情况请大家自行思考
事实上,有两种方式,请大家把两种都想出来
提示:整体减空白
这种做法可以拿到\(40\)分
正解:\(O(n)\)做法
其实区间只有两种情况,如下
(P.S.:我的Pinta崩了,所以这里换了一个画图工具,造成前后风格不一致,在此抱歉)
先看看第一种情况
考虑枚举\(R\),再想办法找出一个\(L\),使得\(sum_{[L,R]}\)最大
因为前面我们推导出了
\(sum_R\)固定了,我们应该让\(sum_{L-1}\)最小,才能让\(sum_{[L,R]}\)最大
我们干脆记录下\(sum_{1~R-1}\)中的最小值,就可以快速求出\(sum_{[L,R]}\)的最大值0了
这个最小值初始为\(0\),我们可以一边求解一边更新最小值(先求解后更新,题目要求不能不选)
代码如下
for (int i = 1; i <= n; i++) {
ans = max(ans2, sum[i] - mnsum);
mnsum = min(mnsum, sum[i]);
}
解决了第一种情况,来看看第二种情况
其实可以利用整体减空白的思想,让不选的区间的和尽可能小
问题转化成了一个类似于第一种情况的问题了
求解最小区间的原理与最大区间类似
我们应该让\(sum_{L-1}\)最大,记录下\(sum_{1~R-1}\)中的最大值
不过注意题目要求不能不选,所以特判一下,避免不选的区间选择了全部
以上做法满分
#include
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;
}
奇怪做法:单调队列
破环为链之后,用单调队列维护连续\(n\)个\(sum\)中的最小值,(类似滑动窗口)
问题类似于上面的情况一
不懂单调队列的同学可以不管
T2 直角三角形的数量
我校评测机太好了(优于大多数Online Judge),优化的暴力可以满分
如果想要\(O(n\sqrt{n})\)及以下复杂度才能通过的平台,可以选择Luogu
(本人测试,一些在我校平台满分的代码在Luogu只有30分)
链接:T239755 [DuckOI&DiyinOI]直角三角型的数量||简单三角形计数题
然后,因为本人疏忽,本题一个符号打错了,可能给大家带来不便,抱歉
简化题意
大家都知道勾股定理吧,对于一个直角三角形,两直角边平方之和等于斜边的平方
式子表示为\(a^2+b^2=c^2(a,b,c为三角形三边,c为最长边)\)
其实还有一个勾股逆定理,若三角形满足\(a^2+b^2=c^2\),三角形为直角三角形
所以本题可以理解为
已知正整数\(n\),求
\[\begin{cases} x^2+y^2=z^2\\ x的所有正整数解
\(O(n^3)\)做法
直接枚举\(x,y,z\),判断是否符合上述式子
这样显然超时
\(O(n^2)\)做法
可以枚举\(x,y\),然后判断对应的\(z\)是不是平方数(平方根是不是整数)
稍微快一点,
\(O(n\sqrt n)\)做法
\[x^2+y^2=z^2 \\ x^2=z^2-y^2 \\ x^2=(z+y)(z-y)//平方差公式\\ \because z>0,y>0 \\ \therefore z+y>z-y \]考虑对\(x^2\)分解成\(x^2=ab\),其中\(a>x>b>0\)且为正整数
得二元一次方程组\[\begin{cases} z+y=a\\ z-y=b \end{cases} \]解得
\[\begin{cases} z=\frac{a+b}2\\ y=\frac{a-b}2 \end{cases} \]所以有一个要求
\[a \equiv b (mod\; 2)//也就是,要么都是奇数,要么都是偶数 \]才能使\(z,y\)为整数
对于每一个\(x\),求出\(a,b\),判断以上注意事项即可
如何分解
如果直接分解\(x^2\),需要枚举\(1\)~\(x\),复杂度太高了,我们需要简单的\(O(\sqrt{x})\)做法
可以分解\(x\),在推广到\(x^2\)
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 } }
然后枚举每一个素因子,复杂度为\(O(ans)\),略高于\(O(\sqrt{n})\)
其实还有一堆优化(甚至可以把难度提升至弱省选难度),在此不多阐述
经过众人讨论,现在发现以下复杂度的做法
\[O(n\sqrt{n}),O(n^{\frac5 4}),O(nlogn) \]希望有人可以发现\(O(n)\)做法,可以发给我
代码如下
#include
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< 其他做法:打表
本题显然可以打表,但不是求出对于每一个\(n\)的解,代码过长会导致编译错误
而是找出\(30000\)以内的基本勾股数的\(z\)(4775项)
再对应每个基本勾股数,求出\(n\)以内派生勾股数的数量
数学名词自行百度
upd:更新了效率更高的代码
这份代码的思路不变,但是预处理出了素数,并用位运算代替了一些操作,等等优化
原先的代码运行了
4000ms
,本代码只有300ms
#include
#include 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
using namespace std; long long n,m,ans; int main() { cin>>n>>m; if(m%100==0) { m/=100; } else { cout<<0< =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< T4 游戏
思维题,好题
首先化简分数
\[x=\frac n m \]设拿分值为\(i\)的牌的数量为\(a_i\),易得方程组
\[\begin{cases} a_1+2a_2+3a_3+4a_4+5a_5=n\\ a_1+a_2+a_3+a_4+a_5=m\\ \end{cases} \]显然有多解
这里提供一种构造方法,只拿\(\lfloor x\rfloor\)(向下取整)和\(\lceil x\rceil\)的牌(向上取整)
比如:\(1.35\),就只拿\(1\)和\(2\)
假设一开始只拿某一种牌,然后通过看与\(x\)的差
渐渐将一些牌改成另一种牌
其实就是小学奥数的假设法
先认为全是某一种东西
然后再比对与想要结果的差进行修改
一个更直接的说法:鸡兔同笼
#include
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 代码来自于Huangzixin大佬,本人还没有调出来,只有\(92\)分