我是本次比赛 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 <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;
}
奇怪做法:单调队列
破环为链之后,用单调队列维护连续\(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<y<z \leq n \end{cases} \]的所有正整数解
\(O(n^3)\)做法
直接枚举\(x,y,z\),判断是否符合上述式子
这样显然超时
\(O(n^2)\)做法
可以枚举\(x,y\),然后判断对应的\(z\)是不是平方数(平方根是不是整数)
稍微快一点,
\(O(n\sqrt n)\)做法
考虑对\(x^2\)分解成\(x^2=ab\),其中\(a>x>b>0\)且为正整数
得二元一次方程组
解得
所以有一个要求
才能使\(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)\)做法,可以发给我
邮箱: 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;
}
其他做法:打表
本题显然可以打表,但不是求出对于每一个\(n\)的解,代码过长会导致编译错误
而是找出\(30000\)以内的基本勾股数的\(z\)(4775项)
再对应每个基本勾股数,求出\(n\)以内派生勾股数的数量
数学名词自行百度
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 游戏
思维题,好题
首先化简分数
设拿分值为\(i\)的牌的数量为\(a_i\),易得方程组
显然有多解
这里提供一种构造方法,只拿\(\lfloor x\rfloor\)(向下取整)和\(\lceil x\rceil\)的牌(向上取整)
比如:\(1.35\),就只拿\(1\)和\(2\)
假设一开始只拿某一种牌,然后通过看与\(x\)的差
渐渐将一些牌改成另一种牌
其实就是小学奥数的假设法
先认为全是某一种东西
然后再比对与想要结果的差进行修改
一个更直接的说法:鸡兔同笼
#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大佬,本人还没有调出来,只有\(92\)分