2021.11.6 JXNU ACS算法组月赛 部分题解

 A 流放

描述:

yezzz 近日被流放到了北方的牧场养牛,er_sz 给了他一块面积为s平方米的矩形牧场,还需用栅栏围起来。已知每米栅栏 1 元,问最少花多少钱,便能将这块牧场围起来?

注意:牧场可为满足“长 × 宽 = 面积 s”条件的任意长宽的矩形,长宽皆为整数。

输入:

第一行,一个整数 T,表示有 T 组测试样例,题目保证 0<T≤100。

接下来 T 行,每行一个整数 s 表示牧场面积 (0≤s≤109)。

输出:

输出共 T 行,每行输出一个整数,表示对于该组样例 yezzz 最少花多少钱。

样例输入:

2
9
5

样例输出:

12
12

样例输入:

3
3
7
15

样例输出:

8
16
16

解题思路:

这道题数据量很大1e9*1e2的,肯定不能枚举,所以我们想更快的方法,1e9怎么简化。

我们知道面积是长×宽,所以我们枚举一边就可以了,这样可以直接降低一个平方根的复杂度

但是我们仔细一想,如果是奇数这种没法取平方怎么办呢,那也很简单,我们提前把答案设成minn = 2*(x+1),其中x代表的是长,1表示宽。

代码示例:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;

int t;
int s;

int main(){
	cin>>t;
	for(int k=1;k<=t;++k){
		cin>>s;
		int minn=(s+1)*2;
		for(int i=2;i*i<=s;++i){
			if(s%i==0){
				minn=min(minn,2*(i+s/i));
			}
		}
		cout<<minn<<endl;		
	}
}


 B 抢糖果

描述:

万圣节那天,er_sz 从 yezzz 那里抢走了 n 堆糖果,然后为了表示自己还是讲武德的,他还制定了一个游戏规则:对于这 n 堆糖果,他只能一堆一堆的取(每次都要取完整的一堆),并且要保证到最后他获得的糖果总数是个合数。er_sz 很贪,他想知道他最多能取走多少堆糖果?

输入:

第一行,一个整数 T,表示有 T 组测试样例,题目保证 0<T≤100。

每组样例输入共 2 行。

第一行一个整数 n (0<n<104) 。

第二行有 n 个整数 a1,a2,...,an (表示第 i 堆糖果有 ai 个,0<ai≤104) 。

输出:

输出共 T 行,每行输出一个整数,表示对于该组样例 er_sz 最多能取多少堆糖果。

样例输入:

1
3
1 2 3

样例输出:

3

样例输入:

2
6
2 2 2 2 2 3
5 
1 2 3 4 7

样例输出:

5
4

注释:

数据保证一定有解!

解题思路:

合数是素数对应的数集,记住这一点你就成功一半了,我们加和所有的a数组,结果如果是合数就直接输出n,如果是素数,除了2都是奇数必然是有多出来的奇数,减去这个就可以了,也就是n-1

代码示例:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;

int t;
int n;
int x;
int cnt;

bool isprime(int a){
	if(a==2) return 1;
	for(int i=2;i<=sqrt(a);i++){
		if(a%i==0) return 0;
	}
	return 1;
}

int main(){
	cin>>t;
	while(t--){
		cin>>n;
		cnt=0;
		for(int i=1;i<=n;i++){
			cin>>x;
			cnt+=x;
		}
		if(isprime(cnt)) cout<<n-1<<endl;
		else cout<<n<<endl;
	}
}


D 魔鬼的步伐

描述:

yezzz 今天心情"很好",因为 er_sz 要请他干饭!已知出发点在 (a,b),饭馆在 (c,d),yezzz 每一步可以向东南西北任意方向移动(每一步移动1个单位),但 er_sz 有个要求:连续的两步不能沿着同一方向移动。

给定 (a,b) 和 (c,d),请计算 yezzz 最少需要几步移动到饭馆。

求最少步数。可以证明,他永远可以到达终点。

输入:

第一行,一个整数 T,表示有 T 组测试样例,题目保证 0<T≤100。

接下来 TT 行,每行四个整数 a,b,c,d 代表一组数据,其中 (a,b) 为起点,(c,d) 为终点。(−104<a,b,c,d<104)

输出:

输出 T 行,第 i 行代表第 i 组数据的答案。

样例输入:

3
-2 0 -2 1
0 1 3 3
-1 1 1 1

样例输出:

1
5
4

解题思路:

你在光滑的地上摩擦就可以了

他的限制条件是不能连续向同一方向走两次,所以我们要绕个远路,也就是本来 →→ 就可以了,但是不能连续的话就只能→↑→↓才行。我们对这道题要先求出从一点到另一点会有几次向左(右)和几次向上(下),然后我们把差值算出来,如果大于1就要考虑绕远路了。并且绕远路回来又可以同原来方向继续走一次然后再绕远路。

代码示例:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

int t;
int a,b,c,d;
int x,y;

int main(){
	cin>>t;
	while(t--){
		cin>>a>>b>>c>>d;
		x=abs(a-c);
		y=abs(b-d);
		int cnt=abs(x-y);
		int ans=x+y;
		if(cnt<=1) cout<<ans<<endl;
		else{
			if(cnt%2==0) ans+=cnt;
			else ans+=cnt-1;
			cout<<ans<<endl;
		} 
	}
	return 0;
}


 F Σ逛超商

描述:

在观看了著名教育片《杰哥不要》后,Σ 先生受到了极大的启发,并立志决定从今日(当然,也可能是明日)起开始认真学算法,并成为像杰哥一样的人!

《杰哥不要》里逛超商的一幕深深吸引了 Σ,于是她决定效仿其中的内容,并带上一个最多可容纳 k 件物品的手推车开始逛超商!值得一提的是,在进入超商之前,她的手推车中可能已经存放了若干件物品。

当然了,Σ 即将临幸的超商非常与众不同!这个超商一共有 n 个货架,在第 i 个货架面前,Σ 将购买或卖出 ai 件物品,当 ai>0 时,表示 Σ 在此处购买了 ai 件物品,ai<0 则表示 ΣΣ 在此处卖出了 ai 件物品。

在任何一个货架面前,手推车中的物品总数都不能超过 k ,当然也不可能小于 0 。现在 Σ 希望你能求出,进入超商前手推车内的物品数量共有多少可能性?

注:每个货架上的商品与手推车内的物品性质相同,Σ 可在任意货架将这些物品买入或卖出。

输入:

第一行包含两个整数 n , k。其中 n 代表货架的个数,k 代表手推车最大可容纳的商品数。保证 1≤n≤1000,1≤k≤10^{9}

第二行包含 n 个整数 a1,a2,…,an 。第 i 个整数代表 Σ 在第 ii 个货架上交易的物品数量。保证 −10^{6}≤ai≤10^{6}

输出:

一个整数,表示 Σ 进入超商前,手推车内商品共有多少种合法情况。

样例输入:

3 5
2 1 -3

样例输出:

3

对于样例 1 ,初始时 Σ 手推车中的数目可以有 0,1,2 个,共三种情况。

样例输入:

2 4
-1 1

样例输出:

4

样例输入:

4 10
2 4 1 2

样例输出:

2

分析题目:

现有一个数字 m,将对其进行若干次增加或减少的操作,并要求每次操作后该数字不允许大于 k (题目已给定)或小于 0 ,问数字 m 可能是几种不同的数字(如果无可能则输出 0 )

解题思路:

本题的关键是找到在这些操作中,该数字 m 最多会增加及减少的数目,而由于每一步操作后的结果都与之前的所有步骤有关,因此可以考虑运用前缀和累加操作(显然单纯累加也可以)

代码示例:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;

int n,k;
int x;
int cnt;
int minn;
int maxx;

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>x;
		cnt+=x; 
		maxx=max(maxx,cnt);
		maxx=min(maxx,k);
		minn=min(cnt,minn);
	}
	if(minn>0){
		if((k-maxx+1)<=0) cout<<"0"<<endl;
		else cout<<k-maxx+1<<endl;
	}
	else{
		if((k-maxx+minn+1)<=0) cout<<"0"<<endl;
		else cout<<k-maxx+minn+1<<endl;
	} 
	return 0;
}


G Σ与酒神

描述:

或许你并不知道,HeartFireY 其实是 ACS 的主(酒)人(神)!且由于某些特殊原因,Σ 先生的地位极低,只能担当给 HeartFireY 倒酒的工作。

在某个被 HeartFireY 承包的酒吧里共有 n 瓶不同的酒和 m 个相同的酒杯,其中第 i 瓶酒的容量是 ti 升(保证 ti 为正整数)。值得一提的是,HeartFireY 在喝酒时有一个奇怪的癖好,即:他不希望一瓶酒被倒进两个以上的杯子里,但他能接受将不同的酒混在一个杯子里!

为了让 HeartFireY 被服务(?)的十分满意,Σ 先生抽空练成了一项绝不手抖的神技,这使得她每一秒能够稳定从每一个酒瓶里倒出一升的酒(不要问,问就是魔法)。倒酒师 Σ 可以选择花费 ti 的时间将第 i 瓶酒直接倒入某个指定的杯子里,也可以选择将第 i 瓶酒分为 ai 升和 bi 升并花费对应的时间分别倒入两个不同的杯子。注意:拆分后的 ai 和 bi 必须是正整数,且满足 ai + bi = ti。

然而,由于所有的酒瓶都只有一个瓶口,因此在同一时段内,每瓶酒只能倒入一个杯子。且一个杯子也不允许在同一时段内被两瓶不同的酒同时倒入,但允许起止时间重叠,例如:若前一瓶酒在第 3 秒结束倒入,则后一瓶酒必须在至少第 3 秒起开始倒入。

Σ 希望能够尽快摆脱作为一个倒酒师的命运,因此她希望倒酒的过程能够结束的越快越好!现在请你帮助她想出一个倒酒的方法,使得 Σ 能够早日翻身做主人。需要特别注意的是,Σ 懒得和狗一样,若当前有多个杯子可供选择,且选择任意一个杯子均不改变倒酒的总时间,则她将选择编号其中最小的杯子。

输入:

第一行包含两个整数 n 和 m,分别代表酒的瓶数和酒杯的数量,保证 1≤n,m≤10^{5}.

第二行包含 n 个整数 t1,t2,…,tn。第 i 个整数代表第 i 瓶酒的容量。 0<ti≤10^{9}

输出:

共输出 i 行,其中第 i 行代表第 i 瓶酒的倒酒计划。

每一行的第一位数字为整数 k ( k=1 或 2 ),表示 Σ 将把这瓶酒倒进 k 个酒杯里。随后将以时间顺序输出 k 个数对 (id,l,r) ,其中 id 代表倒入的酒杯编号,l 为倒入该酒杯的开始时间,r 为倒入该酒杯的结束时间。保证 1≤id≤m,0≤l<r≤10^{18}

样例输入:

5 3
1 2 3 4 5

样例输出:

1 1 0 1
1 1 1 3
2 2 0 1 1 3 5
1 2 1 5
1 3 0 5

注释:

以下是对样例一的补充说明: 本题中,可行且同样最优的方案还有:

1 1 0 1
1 2 0 2
1 2 2 5
1 1 1 5
1 3 0 5

但由于 Σ 很懒,她将在可行且最优的情况下每次选择编号最小的酒杯,因此该样例的标准答案如输出样例中所示。其中输出样例第三行的意思为:对于第三瓶酒,Σ 将把它倒进两个酒杯里,在第 0−1 秒,这瓶酒将倒进编号为 2 的酒杯,在第 3−5 秒,这瓶酒将倒进编号为 1 的酒杯。由于按时间顺序排列,输出时倒入编号 2 酒杯在倒入编号 1 酒杯的前面。

遵循此条件后,本题答案唯一。

解题思路:

将 n 瓶不同的酒倒入 m 个相同的酒杯里,每瓶酒最多允许倒入两个杯子,且保证能够每秒从每瓶酒内倒出 1 L 的酒,问在最小时间内倒完所有酒的方案。本题中,不难得知需要花费的最小时间为【单瓶酒的最大容量,所有酒容量/杯子数(该结果注意上取整)】中的较大值。以该较大值为每个杯子的容量上限,逐一遍历即可。若当前杯子的剩余容量可直接倒完某瓶酒,则仅需当前一个杯子,否则倒满该杯子并将剩余部分倒进下一杯。注意本题数据范围会爆 int

代码示例:

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long ll;
ll a[N],sum,ma;
int n,m;
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>a[i];
        sum+=a[i];
        ma=max(ma,a[i]);
    }
    ll ave=sum/m;
    if(sum%m!=0)//处理向上取整 
        ave++;
    sum=max(ave,ma);//sum为最优状态下每个杯子最多倒入的酒
    ll t=sum,id=1;//此时每个酒杯剩余容量都为sum 并从编号为1的开始
    for(int i=0;i<n;i++){
        if(t>=a[i]){//说明该酒杯的剩余容量足够一次性倒完 
            cout<<1<<" "<<id<<" "<<sum-t<<" "<<sum-t+a[i]<<endl;
            //因为前面可能已经倒过了,所以起始应该是sum-t
            t-=a[i];//此酒杯剩余容量减少a[i] 
            if(t==0){//如果倒完这瓶酒后 杯子的剩余容量为0 
                id++;//更换到下一个杯子 
                t=sum;//下一个杯子起始时剩余的容量为sum 
            }
        }
        else{
            /*使用两个杯子,那么就先把前一个还有剩余容量但是不够一次性倒完的杯子使用了,可以在该杯子中倒入t升 
            剩余还需将a[i]-t升倒入下一个杯子里,开始:0,所需:a[i]-t
             
            */
            cout<<2<<" "<<id+1<<" "<<0<<" "<<a[i]-t<<" "<<id<<" "<<sum-t<<" "<<sum<<endl;
            id++;//由于第id号杯子已经被倒满了 因此换到下一个  
            t=sum-(a[i]-t);//下一个杯子的剩余容量为sum-(a[i]-t); 
        }
    }
    return 0;
}


J er_sz被压迫了

描述:

虽然 yezzz 搬过几次砖,但因为某些不知名原因,他再次返贫了,所以为了脱贫,他决定拿出自己练习多年的本领——织围巾,想依靠这个本领摆脱贫困。为了织出更好看的围巾,他请 er_sz 帮忙在他织的围巾上设计一些装饰。在 yezzz 的万(暴)般(力)请(压)求(迫)下,er_sz 满含眼泪的在所有围巾上写分别了一个仅由 26 个小写字母构成的字符串。不过这个字符串并没有得到 yezzz 的满意,于是 yezzz 在告(暴)别(打)er_sz 之后,决定自己动手解决。yezzz 想将所有的字符串变成他心目中完美的字符串——回文串。yezzz 想让你告诉他,他是否可以选定字符串中出现过的任意一个字母,通过在字符串删除这个字母(删除个数不确定,可能不删除,可能全部删除;删除位置也不确定)得到一个回文串,与此同时,因为yezzz 比较懒,所以他想通过删除最少的字母得到一个回文串。请告诉 yezzz,他是否可以通过这种操作得到一个回文串,如果可以,输出最少需要删除多少字母;如果不可以,输出 −1。

注意:删除的字母必须和选定的字母相同。

回文串:对于一个字符串,如果从左到右看和从右到左看完全一致,我们称这个字符串为回文串。比如:字符串 ′kek′ , ′abacaba′ , ′r′和 ′papicipap′都是回串,而,′abcabc′ 则不是回文串。

输入:

第一行是一个整数 T (1≤T≤100) — 测试样例的数量。接下来的 2∗T行是对每一个测试样例的描述。

对于每个测试样例的描述包含两行,第一行是一个整数 n(1≤n≤105) 表示字符串的长度,第二行是绣在围巾上的字符串。

输出:

对于每个测试用例,如果可能的话,输出使字符串成为回文所需的最小操作次数,如果不可能的话,输出 −1 。

样例输入:

5
8
abcaacab
6
xyzxyz
4
abba
8
rprarlap
10
khyyhhyhky

样例输出:

2
-1
0
3
2

注释:

在第一个测试用例中,你可以选择一个字母 ′a′ 并擦除它的第一次和最后一次出现,你会得到一个字符串 ′bcaacb′′bcaacb′,这是一个回文。你也可以选择一个字母 ′b′,擦掉它的所有出现,你会得到一个字符串 ′acaaca′,这也是一个回文。 在第二个测试用例中,可以看出,不可能选择一个字母并擦除它的一些出现来获得回文。 在第三个测试用例中,您不需要删除任何符号,因为字符串已经是一个回文。

解题思路:

对于一个字符拆你,我们是否可以通过删除某个字母(可以将该字母全部删除,也可以都不删除),得到一个回文串,如果可以最少需要删除多少;不可以输出-1;

回文串的定义就是,从前往后遍历和从后往前遍历,得到结果是相同的。

那么对于回文串的遍历,我们就可以使用 “双指针算法” 来进行遍历,即分别用两个变量标记我们从头和从尾开始遍历到了哪里(这两个变量实际上就是数组的两个下标)。假设我们选定删除字母 'a' 以期望得到一个回文串,那么在我们遍历过程中,对于这两个 “指针” 所指向的字母,我们进行一次判断,如果这两个字母相同,那么我们继续遍历(因为我们要求最小删除次数,所以,即便这俩个字母都为 ‘a' 我们也不进行删除操作);如果某一个 “指针” 指向的字母为 'a' 那么我们将该位置的 'a' 删除,然后继续遍历,如果两个"指针"指向的字母不同,那么我们停止遍历。

因为本题的字符串都是由小写字母构成(也就是说,字符串中只有26种字符),所以,想要知道删除哪一个字母符合题目要求,我们可以进行一次循环,从 'a' 一直到 'z' 最后得出最小删除数目,这个循环中就可以进行上述过程,知道完成26次遍历得到答案(min 或者 -1)。

代码示例:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e6 + 10;
int n, m, t;
char str[N];

void solve()
{
    scanf("%d%s", &n, str + 1);
    int ans = -1;
    for (char i = 'a'; i <= 'z'; i++) // 从 'a' 到 'z' 进行遍历
    {
        int cnt = 0, l = 1, r = n;
        while (l < r)
        {
            if (str[l] == str[r])//两个字母相同,继续遍历
            {
                l++;
                r--;
            }
            else
            {
                if (str[r] == i && str[l] != i)
                {
                    cnt++;
                    r--;
                }
                else if (str[l] == i && str[r] != i)//有一个字母等于 i 删除后继续遍历
                {
                    cnt++;
                    l++;
                }
                else if (str[l] != str[r])  //字母不相同,遍历结束
                {
                    cnt = 1e9;
                    break;
                }
            }
        }
        if (cnt != 1e9 && (ans == -1 || cnt < ans))
            ans = cnt;
    }
    printf("%d\n", ans);
}

int main()
{
    scanf("%d", &t);
    while (t--)
    {
        solve();
    }
}


L er_sz又双叒叕压迫了

描述:

他来了,他又来了,yezzz 又来压迫 er_sz 了,这次他给了 er_sz 一个相对简单的任务:有 T 个 整数,对于每一个整数 n ,判断是否可以通过删除其中的几位(可能没有进行删除操作)使得这个数可以被 25 整除,如果可以,输出最少需要删除几位数

如:251,删除最后一位 1 得到 25 输出 1

​ 215,删除第二位 1 得到 25 输出 1

​ 250,不需要删除即符合要求 输出 0

输入:

第一行一个整数 T (1≤T≤1000) 接下来 TT 行,每行一个整数 n (25≤n≤108)

输出:

每行一个整数,表示需要删除的位数(不需要删除,输出 0 )

样例输入:

5
100
71345
3259
50555
2050047

样例输出:

0
3
1
3
2

解题思路:

给一个数字,想要通过删除其中的某几位(可能不需要删除)得到一个新的数字,使这个数可以被 25 整除,输出最小删除次数。

题目保证对于给出的每一个样例,都可以通过题目所述操作使其可以被25整除。

对于一个数,如果可以被 25 整除,那么这个数最后两位一定是 25/50/75/00 有且仅有这四种情况。所以,我们只需要知道如果得到这四种情况中的一种,最少需要删除多少数字即可。

代码示例:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010;
int n;

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        int ans = 0;
        cin >> n;
        if (n % 25 == 0)
            cout << ans << endl;
        else
        {
            int fg1 = 0;
            int fg2 = 0;
            int c;
            while (n)
            {
                ans++;
                c = n % 10;
                if(fg1 && (c == 2 || c == 7))
                {
                    cout<<ans - 2 <<endl;
                    break;
                }
                
                if(fg2 && (c == 0 || c == 5))
                {
                    cout<<ans - 2 <<endl;
                    break;
                }

                if(c == 0)
                    fg2 = 1;

                if(c == 5)
                    fg1 = 1;

                n /= 10;
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>