关系变成没关系,问题是没问题。
——草东
|

DengDuck

园龄:3年7个月粉丝:37关注:22

中山市迪茵公学第一届“图灵杯”信息学编程大赛初试题解

我是本次比赛 T2 的出题人

本次比赛没有出现高深的算法,主要在于思维上的比拼

个人认为这次比赛题目质量不错,组题组的也很妙

感谢何教练带来一场优质的比赛

建议开题顺序 T3 -> T1 ->T2 ->T4

周末看能不能搞一个视频题解

T1 小朋友玩游戏

题意

对于一个有正有负的环,求出当中的最大区间和

O(n3)做法

枚举所有区间,对于每个区间分别枚举求出区间和,再记录最大值

这种做法期望拿到20分部分分

请注意题目要求的是一个环,自行思考如何枚举到所有区间,求区间和需要注意什么

O(n2)做法

考虑优化求区间和的办法

我们在输入时处理出一个数组sum,sumi表示从第一项到第i项之和

这被称之为前缀和,接下来介绍如何用前缀和求出一个区间的和

如上,如果想求出区间[L,R]的和,显然就是求出黄色部分

我们发现黄色部分正好在[1,R]区间中,而且两区间结尾相同

那么多出来了什么呢?显然是蓝色部分,蓝色部分是什么?

你可能以为是[1,L],那就错了,其实是[1,L1],第L项也是[L,R]的一部分,不可忽视

我们通过前缀和求出了所有[1,i]和为sumi,所以区间和可以这么求

sum[L,R]=sumRsumL1

这样求的时间复杂度为O(1),比暴力求好多了

然后还是枚举区间

注意到题目是个环,所以区间可能是这样的

这种情况请大家自行思考

事实上,有两种方式,请大家把两种都想出来

提示:整体减空白

这种做法可以拿到40

正解:O(n)做法

其实区间只有两种情况,如下

(P.S.:我的Pinta崩了,所以这里换了一个画图工具,造成前后风格不一致,在此抱歉)

先看看第一种情况

考虑枚举R,再想办法找出一个L,使得sum[L,R]最大

因为前面我们推导出了

sum[L,R]=sumRsumL1

sumR固定了,我们应该让sumL1最小,才能让sum[L,R]最大

我们干脆记录下sum1R1中的最小值,就可以快速求出sum[L,R]的最大值0了

这个最小值初始为0,我们可以一边求解一边更新最小值(先求解后更新,题目要求不能不选)

代码如下

for (int i = 1; i <= n; i++) {
ans = max(ans2, sum[i] - mnsum);
mnsum = min(mnsum, sum[i]);
}

解决了第一种情况,来看看第二种情况

其实可以利用整体减空白的思想,让不选的区间的和尽可能小

问题转化成了一个类似于第一种情况的问题了

求解最小区间的原理与最大区间类似

我们应该让sumL1最大,记录下sum1R1中的最大值

不过注意题目要求不能不选,所以特判一下,避免不选的区间选择了全部

以上做法满分

#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;
}

奇怪做法:单调队列

破环为链之后,用单调队列维护连续nsum中的最小值,(类似滑动窗口)

问题类似于上面的情况一

不懂单调队列的同学可以不管

T2 直角三角形的数量

我校评测机太好了(优于大多数Online Judge),优化的暴力可以满分

如果想要O(nn)及以下复杂度才能通过的平台,可以选择Luogu

(本人测试,一些在我校平台满分的代码在Luogu只有30分)

链接:T239755 [DuckOI&DiyinOI]直角三角型的数量||简单三角形计数题

然后,因为本人疏忽,本题一个符号打错了,可能给大家带来不便,抱歉

简化题意

大家都知道勾股定理吧,对于一个直角三角形,两直角边平方之和等于斜边的平方

式子表示为a2+b2=c2(a,b,cc)

其实还有一个勾股逆定理,若三角形满足a2+b2=c2,三角形为直角三角形

所以本题可以理解为

已知正整数n,求

{x2+y2=z2x<y<zn

的所有正整数解

O(n3)做法

直接枚举x,y,z,判断是否符合上述式子

这样显然超时

O(n2)做法

可以枚举x,y,然后判断对应的z是不是平方数(平方根是不是整数)

稍微快一点,

O(nn)做法

x2+y2=z2x2=z2y2x2=(z+y)(zy)//z>0,y>0z+y>zy

考虑对x2分解成x2=ab,其中a>x>b>0且为正整数
得二元一次方程组

{z+y=azy=b

解得

{z=a+b2y=ab2

所以有一个要求

ab(mod2)//

才能使z,y为整数

对于每一个x,求出a,b,判断以上注意事项即可

如何分解

如果直接分解x2,需要枚举1~x,复杂度太高了,我们需要简单的O(x)做法

可以分解x,在推广到x2

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(n)

其实还有一堆优化(甚至可以把难度提升至弱省选难度),在此不多阐述

经过众人讨论,现在发现以下复杂度的做法

O(nn),O(n54),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 游戏

思维题,好题

首先化简分数

x=nm

设拿分值为i的牌的数量为ai,易得方程组

{a1+2a2+3a3+4a4+5a5=na1+a2+a3+a4+a5=m

显然有多解

这里提供一种构造方法,只拿x(向下取整)和x的牌(向上取整)

比如:1.35,就只拿12

假设一开始只拿某一种牌,然后通过看与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

posted @   DengDuck  阅读(248)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起