暑假嗨1~6难题思路总结(未完待续)

本文是作者对暑假期间参与的编程挑战难题的思路总结,涵盖多个算法问题,包括字符串处理、图论、动态规划等方面。文章分析了每个问题的关键点和解题思路,如利用“连续的”条件优化问题、运用图论和BFS解决路径问题、通过三维DP和记忆化搜索处理复杂字符串序列等。通过对这些难题的深入解析,展示了在面对复杂编程问题时如何找到突破口和正确解题方法。
摘要由CSDN通过智能技术生成

看到了老师发的文章觉得自己没有正确的总结题目,于是今天想把补题放一放,把做过的难题思路再整理一遍。

暑假嗨1

C Yet Another Broken Keyboard

题意:
给一串字符,再给出可以使用的字母,问用给的字母能够打出多少连续的子串,是连续的!

当时做的时候想的是枚举,枚举长度,先找只包含所给字母的长度为1的子串,再找长度为2的,再找长度为3的,但是这样做需要不停地将整个字符串遍历,而且遍历时还要判断是否满足条件。代码就算写出来,也肯定超时,所以否定了这个想法。

要做出这道题,必须要认真分析题目中的限定条件。
这道题的突破点是“连续的”这个条件。
通过这个条件,就可以只看整个字符串包含所给字母的抱团的部分,以为子串必定在抱团的子串中产生。然后第二个难点是要想到公式,有了公式就可以做出来了。
知识点:map。

E. Nearest Opposite Parity

题意:
给一串数,每一个数都可以向左或者向右移动当前所在位置上的数,问最少移动多少次,使得奇数变为偶数,偶数变为奇数。

完全没有思路,不知道从哪开始下手,当时还有后来在看的时候完全是懵的,主要是看不透本质,不知道往哪方面上想。

这道题给的虽然是一行数,但这一行数的本质却是一个错综复杂的网。这是切入点。如何表示一个网的连接关系,这时候要能够想到图论。用一个二维数组把复杂的网变为一个图表。这里要求熟练掌握如何建图(还不熟练多看几次)。建出来的图应该是以纵轴为终点,横轴存:可以一步就到达这个终点的点的位置。
然后就又到了一个难想的地方。。。图建好了该怎么办呢?
图最终只是为了计算机能够看懂这个网,只是一个工具。
因为奇数要到达偶数,偶数要到达奇数,两个判断条件不一样,合在一起难做,可以把他俩分开讨论。这里要求掌握bfs的知识。可以按照bfs搜索所有的点。就可像葡萄串一样把所有的点都求出结果。
知识点:图论,bfs
参考博客

F. Two Bracket Sequences

题意:
输入两个括号序列 s,t(不一定合法),你需要构造一个尽可能短的合法括号序列使得s,t 都是这个序列的子序列(子序列意味着不用连续)

完全没有思路。

为什么会想到dp呢?还是三维dp?我觉得需要一定的直觉才能做出这个题。

回到题目,因为是对“()”,“()”字符串合法的性质,在任意一个位置只要前面所有的“(”的个数大于等于“)”的个数,就可以通过补“)”变为合法。只要在某一个位置出现了前面的“)”的个数大于“(”的个数,那么这个是无论如何也合不了法的。

本题第一个思考点:枚举的方法。
我们要做的是构建一个包含s,t的一个最短字符串f,因为每一个位置只有两个可能,“(”或者“)”,这也是这道题的精妙之处,我们可以用枚举的方法,来枚举所有的可能性。
那么如何记录枚举到的每一种可能性呢。
这是本题第二个思考点:如何记录。
这时候我们要能想到,f要与三个量有关,一是包含s,二是包含t,三是合法。要保证知道这三个要素的实时状况,我们就要想到用三维数组,我觉得要想到这一步需要一定的直觉。
开辟一个三维数组dp[s][t][z],s代表枚举到当下已经包括了s的多少元素,t代表枚举到当下已经包括了t的多少元素,z代表“(”比“)”多出了多少,数组中存的是满足这三个条件的最短序列的长度。这样就可以运用dp的思想,因为后面的状态来源于前面的状态。而前面的状态又已经都被枚举了,所以可以用dp思想解决这道题。
状态转移:

枚举答案合法序列的每一位放置“(”或者“)”

放置(,如果s[i]’(’ -> ni=i+1,t[j]’(’ -> nj=j+1, dp[ni][nj][z+1]=dp[i][j][z]+1

放置),如果s[i]’)’ -> ni=i+1,t[j]’)’ -> nj=j+1, dp[ni][nj][z-1]=dp[i][j][z]+1

最终状态:dp[s.size()][t.size()][0]
这样就可以得到最短的长度了,这已经够难的了,但是题目有点狠,他竟然还让输出这个串!

这就是第三个思考点:如何输出这个总串。
要克服这个需要,记忆化搜索,也就是顺藤摸瓜,对于dp来说,这也是很重要的一个思想,就是通过当前状态,找到上一个个状态,这里用的是一个结构体,值的记住这种方法。
好了,这就是这道题的思路过程。
涉及知识点:三维dp ,记忆化搜索。
同时要有一定直觉。

暑假嗨2

D ----A Game with Traps

题意:
有m个士兵,t秒,你要带尽可能多的士兵从0去n+1,且他们不能被杀死。路上有一些陷阱,陷阱d[i]会杀死能力比它小的士兵,陷阱位置在l[i],当你走到r[i]时可以拆除它。每次你可以向左或者向右移动。自己不会被陷阱杀死,可以先去把陷阱拆除再回来带兵。

没有思路,根本都想不到用二分。

回到题目,在时间允许的情况下,带最多的士兵。
第一个难点:
要跟二分产生感应。二分能带走的最弱的士兵。判断把它安全带离这一段路所需要的时间是不是超过规定时间。
第二个难点:
判断,这里我自己做的时候有一个坑被我踩了,就是有一种情况,前面一小段路如果没有陷阱或者陷阱炸不死任何一个士兵,可以直接带士兵走过去,不用把士兵留到原点,这样更节省时间,节省时间意味着可以排更多的雷,意味着可以带更弱的士兵。所以我自己做一遍的时候被卡了。
这道题巧妙的地方在于每一个陷阱都是先走到陷阱而不是先碰到它的开关,这也是判断的核心。
因此我们可以用前缀和的做法,把必须排除的陷阱位置设为1,把开关位置设为-1,从第一次遇到1开始,到整个前缀和为0结束,是我们要单独走的一段路。
记住这种样子的题要想二分。
参考博客

E----Tournament

题意:
现在有n个人打拳,序号小的打不过序号大的,并且如果你花ai元贿赂第i个人,你就能赢。你朋友参加了比赛,你可以分配每次比赛的组队以及贿赂,问你最少需要多少钱。
解法:
如果你不贿赂要是第一,那么你只能是第n个人,如果不贿赂进二强,那么你必须是第n/2到第n个人。如果进入4强,那么必须是第n/4到第n个,这样递归下去。
那么你就能知道你朋友是第几强,如果还要往上,那么就要贿赂能够进下一场比赛中的一个即可,所以是贿赂用钱最少的那个。那么只需要每次判断是否是2的幂次,如果是,那么就是要在之后的人中至少要贿赂一个。

思维题,纯纯的思维题,也可以说是一个规律题吧。
仔细研究一下代码,代码写的特别精简,特别灵巧

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
ll a[N];
multiset<ll>s;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    ll ans=0;
    for(int i=n;a[i]!=-1;i--)
    {
        s.insert(a[i]);
        if(!(i&(i-1)))//i&(i−1)的作用是将i的二进制表示中的最低位为 1的改为0。而且如果i是二的次幂的话返回0。

        {
            ans+=*s.begin();
            s.erase(s.begin());
        }
    }
    printf("%lld",ans);
    return 0;
}



暑假嗨3

C----Platforms Jumping

题意:
有一条宽度为n的河。河的左岸编号为0,右岸编号为n+1。河流上还有m个木制平台,第i个平台的长度为ci(所以说第i个平台占据河流的ci个连续位置)。保证平台长度的总和不超过n。

你正站在0(左岸),并且想到达右岸即n+1的位置。如果您站在位置x,则可以跳到[x+1,x+d]范围内的任何位置。但是, 你只能跳到木质平台上( 即不能下水 )。例如,如果d=1,则只能跳到下一个位置(如果这个位置上有木制平台)。您可以假设单元格0和n+1属于木制平台。

您可以将任意平台向左或向右移动任意次数(也可以不移动),只要它们彼此不重叠(但两个平台可以挨在一起)。也就是说你不能更改平台的相对顺序。

请注意,你应该先移动平台再跳跃(一旦你出发后,你就不能再移动平台了)。
思路:
每一次都跳极限距离,加上木板所能到达的极限距离超过小河的宽度则证明能跳过去,否则跳不过去。这点很容易判断。难点在于木板要怎样安排,以及输出结果。
这是这个题唯一的难点
不能乱跳,为了防止前面跳的太少,后面跳不到对岸了。
所以还是要尽可能的多跳。
跳到什么程度呢?跳到所有的省下木板正好连成一路或者剩不下木板(跳加木板刚刚好的情况),然后走过去。

说起来好说,代码还得多熟悉两眼
这个if,else用的特别好。

	for(int i=1;i<=m;i++)
	{
		if(pos+sum+d-1<n+1)
			pos+=d;
		else
			pos=n+1-sum;
		for(int j=pos;j<=pos+a[i]-1;j++)
			ans[j]=i;
		pos+=a[i]-1;
		sum-=a[i];
	}

知识点:贪心

E. Yet Another Division Into Teams

题意:
将 n 个数分组,每组的至少有 3 个数,求如何分组才能使分组之后各组的极差(最大值 - 最小值)之和最小。

肯定要先排序。这谁想不到啊。
然后下面再。。。。嗯。。。。
难点1:
首先要能分析出来最多是5个,因为6个的时候还不如两个3个(因为数不一定是连续的)。这是题目未给的需要自己分析的条件,也是此题的精妙之处也是突破点。如果分析不出上限,那么无法写出状态转移方程。dp写到一半,发现没有办法状态转移,难道每次都要遍历到最后吗?
难点2:想到dp
因为前面3人一组,4人一组,5人一组对后面的建队有着影响,所以有后效性,所以考虑dp。
难点3:dp实现
有了dp之后,就是很正常的dp思路,从前往后推试试。从前往后推,状态转移方程循环三次,分别是3人,4人,5人,找到最小的存到dp[i]中,代表到i为止最小的结果,老思路了。
难点4:开始的时候细节
for (int j=3;j<=min(5,i);j++)还没到3个人的时候不能转移

难点5:输出
一般dp输出个数就结束了,但这个题它偏不。这也是这个题一大亮点。他要求你输出每个人所在的队伍编号,这是这个题最难的地方,城里人真会玩!

for (int i=N;i;) {
        for (int j=3;j<=min(5,i);j++)
            if (dp[i]==dp[i-j]+a[c[i]]-a[c[i-j+1]]) //用这种方式判断是由谁转移到当今状态,我只想说,我服了。记住记住记住。
            {
                ++ans;
                for (int k=1;k<=j;k++) b[c[i-k+1]]=ans;
                i-=j;
                break;
            }
    }

还有一个小技巧对于这个输出很重要:
那就是排序没有对原数组排,而是按照数组中数的大小对数的序号排。要记住这种小技巧
知识点:dp,技巧
参考博客

F----Equalizing Two Strings

题意:
给出两个长度相等的字符串,每次操作必须同时翻转两个字符串中相同长度的子串,求是否可以通过这种操作使得两个串相等。

完全没思路。。。。

这个题的突破点在于能认识到反转一个字符串的本质是两两相互交换若干次。认识到这一点需要一定的运气。也可能有人本来就知道这个性质。如果不研究反转,就永远也做不出来,这个突破点藏得有点点深。既然现在知道了,那就该记住。

abcd->abdc->adbc->dabc
dabc->dacb->dcab
dcab->dcab
dcba

有了这个性质以后就简单点了。

1.如果有字母在S和T中出现次数不同,显然 NO
2.如果S和T中存在一个字母出现不止一次,把 S 中这两个字母想办法变成相邻 这一定能做到
然后每次操作交换 S 的这两个相邻字母 T就可以任意交换,S 不变 ,T 一定可以变成 S,因为可以看作每一个字符任意右移 每次把需要移动的移过去就行了
3.其它情况(每个字母都是一个的情况)
我们可以先想办法把 S 变成升序 ,同时对T任意选择两个相邻字母每次交换这两个 ,这一步的次数取决于 S 中逆序对的个数 ,假设这个数为x这样之后再把 T 变成升序 假设 T 中逆序对个数为y 这一步需要的步数等于y+1或y−1(x为奇数) 或 y(x为偶数),如果x为奇数 在"任意选择两个相邻字母每次交换这两个"操作中 S排序完后T的逆序对显然会增加或减少一个 而如果x为偶数 这个数不会改变,同时也要在S上进行这些操作(任意选择两个相邻字母每次交换这两个),可以看出x+y为偶数是能做到上面一步 否则如果一个为升序 另一个逆序对必会为1。换句话说就是x和y奇偶性相同就一定有解。
参考博客1
参考博客2

暑假嗨4

D—Yet Another Monster Killing Problem

题意:
你有若干英雄,每个英雄有两个属性,能力和耐力,现在你要派某个英雄去打怪,只能派一个,每个怪兽都有一个耐力值,英雄的能力值大于等于怪兽能力值则能打过,打一个怪消耗一个耐力值,当耐力值耗尽或能力值小于当前怪的时候,英雄回来,一天只能派一个英雄,一个英雄可以被派遣多次,问最少需要多少天打完所有怪。

完全没思路。。。。

难在英雄有两个属性。耐力和力量。两个就让人大脑混乱:
耐力值大的力量小,力量大的耐力小,最好有耐力力量都大的,两个标准啊,也找不出来啊。
可能有一串怪兽很弱,派一个力量不强但耐力很强的把他们全打死,但怎么找这样的英雄呢?
。。。。
反正想题的时候脑子是一团浆糊,理不出思路。

最难点:
既然是两个标准,我们就可以固定一个标准,去选择最好的另一个标准。
我们先对英雄的力量值从小到大排序。然后从后往前进行一次查找。求出攻击力大于heros[i].p的所有英雄的最大耐力是多少,存入c[i]中。
然后再对怪兽进行一次处理,按顺序去遍历怪物,用一个mmax来存下消灭到当前怪物要达到的攻击力是多少,一个len来存要消灭的怪物的数量,其实就是要消灭的怪物中的最大攻击力是多少,然后用二分查找,找出第一个攻击力大于mmax的英雄,下标位idx,那么我们只要判断c[idx]是否比要消灭的怪物的数量len小,如果小,就表示前面一个怪物是能一次性消灭的最多的怪物的最后一个,那么ans++,并且mmax=mos[i],表示重新开始一天,len=1。如果大,那么说明还可能再多打一点怪,就再继续看下一个怪物(这就属于贪心了)。直到遍历完所有怪物。
如果最大怪物的攻击力大于最大英雄的攻击力,那么表示不能通关,输出-1

整体思路就是;把两个标准化为一个,而另一个是在这个不低于力量的情况下耐力最好的英雄。这样我们就可以找出满足某一条件的最好英雄。而这一条件由怪兽提供。
然后就要处理怪物数组,按照怪物所需情况二分选择符合打败这个怪兽最适合的英雄,如果能够打败还有多余,那贪心看看能不能再多打败一个,如果不能,说明上一个就是一个英雄所能打败的最多怪物,但如果能的话,那就再贪,贪到不能贪为止。

E—The Contest

题意:
初始有三个组,共n个数,现在要求改变一些数的位置,使得第一个组为这n个数的前缀,第三个组为这n个数的后缀,第二个组为其余数字,移动后一些组可能为空,要求最少移动次数。

完全没有思路。。。dp啊,你变成三行我就不认识你了。

难点1:
首先根据题意要能得出后面的数受前面的数的影响。本质上还是从1到n遍历,后面的状态由前面的得出。这是这道题的突破点。也是得出用dp做的依据。
难点2:dp数组的建立
知道用dp了之后如何确定dp数组的形式呢?
要有一个下标存当前到哪个数了,还得有一个存这个数在哪各组,这样后面的才能确定不能去哪些组。所以形式为dp[i][j],表示第i个数分到第j组
难点3:建立状态转移
我们可以认为一个数只受前一个数的影响,前一个数有三种可能性,在组一组二组三,我们要dp[i][j]最小,所以要遍历前一个数的三种情况,再遍历这个数的情况,于是有了:

for(int i=1;i<=n;i++){
        for(int j=1;j<=3;j++){//枚举前一个数字所在的组数
            for(int k=j;k<=3;k++){//枚举后一个数字所在的组数
                dp[i][k]=min(dp[i][k],dp[i-1][j]+(a[i]!=k));
            }
        }
    }

本质相当于还是枚举了一遍所有情况,所以n个数的最小移动次数即为dp[n][1],dp[n][2],dp[n][3]中的较小者。

F— Make Them Similar

题意:
给定一些数,让这些数都异或同一个数,是否存在这样一个数x,使得得到的结果的二进制位含有1的个数相等。

完全没有思路。。
当时能想到的唯一一种方法就是枚举,然后被自己否决了,枚举2的30次方,还要把每个数转化为二进制,然后统计1的个数,这必定超时,就觉得这种做法肯定不合理,就没再往这方面想过。然后以为是规律题,又去找规律,找了半天,啥也没找到。
难点1:想到折半枚举
2的30次方很大,但是2的15次方我算了一下只有3000多,可以枚举,这是此题的突破口,想到折半这道题就解决了一小半。
想到折半之后就要考虑怎样将一个数折半,得到前十五位用移位操作,将a[i]>>15,要得到后十五位用取余操作,a[i]%(1<<15),得到两个数就可以分别求前十五位和后十五位。
t统计1的个数用到一个函数__builtin_popcount(x)(返回一个数二进制中1出现的次数),也可以自己写,我觉得x&1,然后x>>=1,这样循环直到x=0.
难点2:技巧

前后15位我们都会得到这样一张表
在这里插入图片描述
如果再暴力枚举前十五位的列与后十五位的列相加,就是215乘以215,又会超时,所以不能这样做,接下来要做的需要认真钻研才能想到结果,而不是顺理成章或者显而易见。
最大难点:思考得到
可以先把,低15位。
cnt[ 0 ][ 2 ] - cnt[ 0 ][ 1 ], cnt[ 0 ][ 3 ] - cnt[ 0 ][ 2 ]; … cnt[ 0 ][ n ] - cnt[ 0 ][ n - 1 ];保存在vector中,

那我们枚举高15位时。 我们的 vector 存的就是 -1 * cnt[ 1 ]的后一位减去前一位。 即

-1 * ( cnt[ 1 ][ 2 ] - cnt[ 1 ][ 1 ] ), … -1 * ( cnt[ 1 ][ n ] - cnt[ 1 ][ n - 1 ] );

只要前十五位和后十五位两个vector每一位都相等,即cnt[ 0 ][ 2 ] - cnt[ 0 ][ 1 ] = -1 * ( cnt[ 1 ][ 2 ] - cnt[ 1 ][ 1 ] ),即cnt[ 0 ][ 2 ]+cnt[ 1 ][ 2 ]= cnt[ 0 ][ 1 ]+cnt[ 1 ][ 1 ],那么就说明可以找到这样的数。

非常巧妙
知识点:折半,枚举,技巧
参考博客

暑假嗨5

D—Salary Changing

题意:
题意:
你是一个大型企业的负责人。在你的企业当中共有n位员工为您工作,而且非常有趣的事是这个n是一个奇数(n不能被2整除)。你必须给你的员工分配工资。最初,您有s美元,而第ii个员工应得的薪水应该是l i​∼r i​之间的一个值。而无论怎么分配每个人的工资,你必须使得所有分配的工资的中位数最大。
不必把所有的钱都花在员工的开支上。

完全没思路。。。
当时的感觉就是一团乱麻,没地方下手

理一理。
题目要求中位数最大,这时候要能找准切入点,中位数的范围。怎么找到这个数的范围呢?因为是中位数,所以这个必须要能使两边的人数相等,最右边界应该是把所有薪资的右边界排序取中位数,可以想到再往右一点,那么左边的人数会恒大于右边的人数,同理左边界就是把每个人的薪资上限排序,取中位数。这样就能找到薪资中位数的取值范围。然后很自然的会想到二分查找中位数,判断条件就是你手里所有的钱。既然是钱,那么我们要尽可能的省钱,薪资下限小于中位数的,就给他们薪资下限薪资上限小于中位数的人也给他们薪资下限,另外就是一些穿插的人,左边的人数小于右边的,就让这些人去左边给薪资下限,右边的人数小于左边的就让这些人去右边用中位数给钱即可。好了,行云流水。比上一个有思路多了,能够顺理成章。
知识点:二分,贪心(这俩好像特别喜欢放一起?)

E2

题意:
有N个人,他们都是投票的人,我们现在想的就是让这N个人都把票投给自己,第i个人有一个mi和pi指的是,如果有mi个人已经把票投给你了,那么他也会把票投给你,否则你可以花费pi让他把票投给你。为了让所有的人都把票投给你,问你需要的最少花费是多少?与E1的不同是测试数据量大了

完全没思路。。。
首先能够看到如果从众值为i的都不用贿赂了,则<i的人都不用贿赂了。我们要尽可能的让这个i大一点,能白嫖更多的人说明能省更多的钱,那么我们可以从n到0来贪心.

我们可以先将所有m值为i的人放到一个"隔离区", 假设隔离区里的人都不被收买,

那么被收买的人的个数就是n−size, n是总人数, size就是"隔离区"里人的个数.

如果n−size<i, 就说明放入"隔离区"的人多了啊, 我们应该将其中的人"赎"出来,

为了付出最少的代价使得n−size>=i, 我们可以使用优先队列来作为这个"隔离区", 将p最小的人一个一个往外买就对了.
知识点:贪心
参考博客

暑假嗨6

B1—Books Exchange (easy version)

B2—Books Exchange (hard version)

题意:
讲的是经过多少次轮换,自己的书又回到自己手上,每个人手上都有一个数,被转过来的书,就会转到自己手上那个数指的人身上。如此反复,求每个人的书转了多少次,才回到自己手上。
困难版本和简单版本唯一的不同就是数据范围的不同
简单版本:
怎么想的就怎么做,一个一个遍历求解就行,循环套循环,也可以循环套深搜
困难版本:
不会。。
这道题的突破点在于能发现规律。模拟一下过程就能发现规律。
数据变大了之后我们想着一个一个循环肯定是不行了。我们就要仔细分析,看看有啥规律吗。我们可以发现既然是回到原点,那就是一个环,环上所有的点回到原点要走的次数是相等的。然后,我们可以一圈一圈的找,找过的做好标记,O(n)的时间复杂度,完美。
知识点:找规律

C1—Good Numbers (hard version)

题意:
这一道题就是求一个好数,这个数是由3的幂次方累加起来的,但不能重复,即不能加3的相同的幂次方。
首先

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值