文章目录
The First Week
不积跬步,无以至千里。 ————荀子
一、前言
开学了,但是感觉每周写博客记录一下自己学了什么挺好的,看看我能不能多坚持一段时间吧,这次就是纯自觉啦。希望还是每周都有新东西。这周好像要打天梯选拔赛?稍微冲一下。
周六,开始回顾之前的题目,从第一次的洛谷开始,把题全补了,之前怎么都看不懂题解补不了题,现在可以了,虽然有些还是做不太出来,不过有进步啦嘿嘿。
周二打了一次天梯选拔赛,打得人不多看不太出水平其实,有一道题目题意理解错误,一直wa,后来自己补题一次过的,还是得多做一点。持续补题中…
又是周六,第二把天梯选拔赛。oi赛制,自我感觉还行吧,该对的都对了也就尽力了,还是有些函数和算法很不熟悉,有时间继续坎看课。
周日补题,敲代码ing“咔咔咔…”
二、算法
1.线性DP
<1>(洛谷 P8638)
不敢想象这题我琢磨了多久,感觉就是快做出来了又做不出来,看着题解研究这个dp状态方程,简直离谱。
题解:
题意要求给定一个字符串,最少增加多少个字符之后使其成为回文字符串。
查找LCS(最长公共子序列)是非常经典的一个题型,这里用dp查找给定字符串s和它的逆序s2的最长公共子序列,总长度减去其后就为答案。dp的难度主要在书写状态方程,这里也没有什么特殊性,我就不再过多说了。写了点注释。
这里有几个点导致我一直错误。
第一是数组开在主函数内,会一直报错Process finished with exit code -1073741571 (0xC00000FD),当在主函数内定义较大数组时,这些数组通常时在栈上分配内存,栈空间比堆小很多,可能会导致栈溢出。可以使用new和delete关键字来动态分配和释放内存,以避免栈溢出错误。
第二点是dp状态方程,这里写作f数组,必须从1遍历到n,因为dp状态层层推进,f[0][0]=0也有需要被用到,不然从0开始的话会导致找不到上一位,这个地方以后开数组也要注意。
第三点是我的问题了,因为从1开始开,但是我的s是从0开始,也就是赋值s1,s2的时候,写错了导致代码垮掉,啧,这个问题确实不应该。
代码:
#include<bits/stdc++.h>
using namespace std;
char s1[1005],s2[1005];
int f[1005][1005]; //记录s1的前i个字符与s2的前j个字符的最长公共子序列长度
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
string s;
cin>>s;
int n=s.length();
for(int i=1;i<=n;i++){
s1[i]=s[i-1];
s2[i]=s[n-i];
} //s2为s1的逆向字符串
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){ //dp状态方程
if(s1[i]!=s2[j])f[i][j]=max(f[i-1][j],f[i][j-1]);
else f[i][j]=f[i-1][j-1]+1;
}
}
cout<<n-f[n][n]<<endl; //长度减去最长回文子字符串
return 0;
}
2.树形DP
<1>(洛谷 P8625)
在做上题线性dp的时候,意外发现这题是树形dp,去研究了一下啊,几乎是照猫画虎了,还是没学会举一反三,但比之前好一点,能看懂了,这就是学会dfs了吧
题解:
题目给出n个节点的权值和n-1条双向边,要求计算节点集合S的最大值,可以是空集。
先是利用vector制造一颗树,以一个dfs计算每个节点的最大dp,进行比较即可。具体可看注释。
代码:
#include<bits/stdc++.h>
using namespace std;
vector<int> to[100005]; //vector数组中每一个节点可存放未知数量数字
long long int a[100005]; //树上每个节点的权值
long long dp[100005]; //每个节点的最大和
long long ans=0; //赋初值为0,空集的情况
void dfs(int u,int fa){ //dfs遍历每个节点,赋值dp与ans
dp[u]=a[u]; //初始赋值为自身权值
for(int v:to[u]){ //虚拟代数v为to[u]的每个遍历
if(v==fa)continue; //fa为其父节点,防止陷入死循环
else dfs(v,u);
dp[u]=max(dp[u],dp[u]+dp[v]); //确认是否需要加上节点v的权值
} //其实只要是负数都舍去,正数都加入的啦
ans=max(ans,dp[u]);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
to[u].push_back(v); //vector的一种用法
to[v].push_back(u); //可以记住一下,高光高光
}
dfs(1,0); //从第一个开始遍历,父节点设为0
cout<<ans<<endl;
return 0;
}
3.二分算法
最后一次系统的二分,希望以后的二分题都能写对
题解:
不难看出l,r,mid的赋值状况以及循环条件均一致,l,r的变化情况也一致,判断条件以及输出一直在变,找到第一个数输出l,找到最后一个数输出r,找不到输出false,有些情况一定能找到。
代码:
//找到arr中等于key的数字
int l=0,r=n-1,mid;
while(l<=r){
mid=(l+r+1)/2;
if(arr[mid]==key)return mid;
else if(arr[mid]>key)r=mid-1;
else if(arr[mid]<key)l=mid+1;
}
return false;
//找到arr中第一个等于key的数字
int l=0,r=n-1,mid;
while(l<=r){
mid=(l+r+1)/2;
if(arr[mid]>=key)r=mid-1;
else if(arr[mid]<key)l=mid+1;
}
if(l<n && arr[l]==key) return l;
//查找第一个大于等于key的数字就不需要“arr[l]==key”的判断条件
else return false;
//找到arr中最后一个等于key的数字
int l=0,r=n-1,mid;
while(l<=r){
mid=(l+r+1)/2;
if(arr[mid]<+key)l=mid+1;
else if(arr[mid]>key)r=mid-1;
}
if(r>=0 && arr[r]==key) return r;
else return false;
//查找数组中最后一个小于等于key的数字
int l=0,r=n-1,mid;
while(l<=r){
mid=(l+r+1)/2;
if(arr[mid]<=key)l=mid+1;
//查找数组中最后一个小于key的数字就是将“<="改成”<“
else r=mid-1;
}
return r;
//查找数组中第一个大于key的数字
int l=0,r=n-1,mid;
while(l<=r){
mid=(l+r+1)/2;
if(arr[mid]>key)l=mid+1;
else r=mid-1;
}
return l;
<1>(洛谷 B3691)
记不太清这题有没有补过了,反正再做一次还是很懵,看了解析自己又写了一遍,主要是想不到这题是二分,思路还是很清晰的。
4.位运算
二进制枚举
1<<m :意味着1向左移动m位,右侧补足0,即2的m次方。如1<<3意味着2的3次方即8,因为1000的十进制表示是8。
1>>m :是往右位移m位,如1001往右边位移1位就是0100,即右边去掉几位后左侧补0。
二进制枚举的思想是选与不选的暴力思想,选与不选分别是0与1的情况,而总共有m个数字,每种数字有俩个情况,所以需要2的m次方的情况数组,也就是上面的符号,二进制直接位移即可。二进制枚举是一种暴力枚举,相较于dfs,其没有剪枝功能,必须遍历完所有的2的n次方种情况,相对时间复杂度骄傲,但是写起来相对简单,在赛场上较节约时间。
<1>(洛谷 P1036)
很简单的板子,之前还用dfs写过一次,就不写题解啦,在补周二练习赛的题的时候发现了这个陌生又熟悉的名词,就先去学习了一下,做这题了解了一下基本概念,据说是基础,果然还是欲速则不达啊。
代码:
#include <iostream>
using namespace std;
int n,k;
int x[25];
bool is_prime(int ans){
if(ans==1||ans==2)return true;
for(int i=2;i*i<ans;i++){
if(ans%i==0)return false;
}
return true;
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++){
cin>>x[i];
}
int t=0;
for(int i=0;i<(1<<n);i++){
int cnt=0;
int ans=0;
for(int j=0;j<n;j++){
if(i>>j&1) //表示i在第j位上是否是1
{
cnt++;ans+=x[j];
}
}
if(cnt!=k)continue; //没取出k个数字直接排除情况
if(is_prime(ans))t++;
}
cout<<t<<endl;
return 0;
}
<2>(天梯选拔赛(一)B 孵化小鸡)
这有个问题就是我以后该怎么找到题源呢,这题很想补可是它的题源放不上来,要不我题意写仔细一点吧。另外比赛的时候确实是一点思绪也没有,补题的时候看了会代码就懂了,没有什么特别难度好像,比较经典的题。
题解:
题意为给出俩个正整数N,M,在N行中分别输入a,b,m,分别表示在a到b的范围内鸡蛋的温暖值是m。在M行中分别输入l,r,k,p,分别表示在l到r的区域内温度上升k需要p元。要求找出最少的花销使得所有鸡蛋都可以正常孵化,M个暖源。
运用了桶排序的思想,将每个位置的鸡蛋算作一个桶,后运用二进制枚举的思想,一一列举情况使用这个暖源以及不使用,将所有满足条件的比较取得最小值。
思想大概是这样,就不写注释了。
代码:
#include<iostream>
using namespace std;
int a[22],b[22],m[22];
int x[105]={-1};
int l[12],r[12],k[12],p[12];
int N,M;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>N>>M;
for(int i=0;i<N;i++){
cin>>a[i]>>b[i]>>m[i];
for(int j=a[i];j<=b[i];j++){
x[j]=m[i];
}
}
for(int i=0;i<M;i++){
cin>>l[i]>>r[i]>>k[i]>>p[i];
}
int ans=1000000;
for(int i=0;i<(1<<M);i++){
int t[105]={0};
int sum=0;
for(int j=0;j<M;j++){
if((i>>j)&1){
sum+=p[j];
for(int ls=l[j];ls<=r[j];ls++){
t[ls]+=k[j];
}
}
}
bool st=true;
for(int i=0;i<=102;i++){
if(x[i]!=-1&&x[i]>t[i]){
st=false;
break;
}
}
if(st)ans=min(ans,sum);
}
cout<<ans<<endl;
}
<3>(天梯选拔赛(二)C—04—L1—8)
2024常熟理工学院团体程序设计天梯赛选拔L1—8 该加训啦
该加训啦 加训啦 训啦 啦。这题代码非常简单,逻辑也很易懂,当时不少人都做出来了,但是我实在不太知道或与亦或,所以怎么都写不出来,后来拿了一分吧,呵呵,前缀和倒是挺了解的,就不开它的模块了(希望吧),异或符号都差点写不出来,要不是后面有提示,一点也不会呢。
题解:
题意函数过于复杂,不写了,上张真值表。
a | b | a&b | a|b | a xor b(异或) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 0 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
可以得到(a&b) xor (a∣b)=a xor b
代码:
#include<iostream>
using namespace std;
int main() {
int n;
cin >> n;
long long int x[n];
int s[n];
for (int i = 1; i <= n; i++) {
cin >> x[i];
s[i] = s[i - 1] xor x[i]; //前缀和
} //x[I] xor 0=x [I]
int m;
cin >> m;
for (int i = 0; i < m; i++) {
int l, r;
cin >> l >> r;
int t = s[r] xor s[l - 1]; //s[r] xor s[l-1]等于s[l]到s[r]之间所有元素的异或结果
cout << t << endl;
}
}
5.DFS算法
<1>(天梯选拔赛(二)C—04—L2—2)
2024常熟理工学院团体程序设计天梯赛选拔L2—2 swj学长的精灵融合
其实这题的案例我是相当不理解,要么不过要么全过,可惜选拔赛不能看样例。补题的时候做了好几次一直都不过,加了几个sum函数储存经验值之后忽然就过了,我也是百思不得其解,只能说下次小范围数据还是存数组吧,还用了vector,这个还挺实用的。另外这题试着标准化了一下,用了一些define和const,非常之不习惯。
题解:
题意要求输入俩个整数最终精灵编号x和融合关系总数m,输入m行a,b,c,d。c是种类,不同种类精灵升级所需要的经验值不同,d是所需达到的等级,a是合成的精灵,b是自己的编号。
这题的思路重点在于只算上自己目标精灵树的经验值,所以用到dfs层层遍历标记,要注意vector可储存任意数量数字,所以可以用来一次记录所需精灵。写过挺多dfs题目了,这题思路大致相同,不过多解释了。
代码
#include<iostream>
#include<vector>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int x, m;
int res = 0;
int zl[N];
int dj[N];
int sum1[N], sum2[N], sum3[N];
std::vector<int> p[N];
int vis[N] = {0};
void dfs(int u) {
vis[u] = 1;
for (int i = 0; i < p[u].size(); i++) {
int q = p[u][i];
if (!vis[q]) {
if (zl[q] == 1) {
res += sum1[dj[q]];
}
if (zl[q] == 2) {
res += sum2[dj[q]];
}
if (zl[q] == 3) {
res += sum3[dj[q]];
}
dfs(q);
} else continue;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int ans = 1, idx = 0;
for (int i = 2; i <= 100; i++) {
sum1[i] = ans + idx;
ans = sum1[i];
idx += 1;
}
ans = 1, idx = 0;
for (int i = 2; i <= 100; i++) {
sum2[i] = ans + idx;
ans = sum2[i];
idx += 2;
}
ans = 1, idx = 0;
for (int i = 2; i <= 100; i++) {
sum3[i] = ans + idx;
ans = sum3[i];
idx += 5;
}
cin >> x >> m;
for (int i = 0; i < m; i++) {
int a, b, c, d;
cin >> a >> b >> c >> d;
p[a].push_back(b);
zl[b] = c;
dj[b] = d;
}
dfs(x);
cout << res << endl;
return 0;
}
6.其它
<1>(string函数)
find,rfind,和replace函数.
(r)find函数:用于string中,找到即返回位置或元素,找不到则返回string::npos即-1;
string str = "Hello, world!";
auto it = find(str.begin(), str.end(), 'w');
if (it != str.end())
cout << distance(str.begin(), it) << '\n'; //输出7
else
cout << "Not found\n";
int pos=str.find('w');
if(pos==-1)
cout << "Not found\n";
else cout<<pos; //输出字符串左侧位置7
int poss=str.rfind("world"); //从右侧开始匹配,输出从右往左找到的
//第一个符合条件的字符串的最左侧字符的位置
int ppos=str.find('o',5) 从第五个位置开始寻找字符o的位置
replace函数:
string& replace (size_t pos, size_t len, const string& str);
str=str.replace(str.find(“a”),2,“#”); //从第一个a位置开始的两个字符替换成#
s=s.replace(‘.’,“.xixixixi.”);
replace函数是对原函数进行修改,不用再度赋值给s。
<2>(质因数分解)
实在没什么好说的,记录一下这一小段我研究了很久的代码,可用于质因数分解求因子数。
代码:
int calc(int x) {
int ans = 1;
for (int i = 2, cnt; i * i <= x; i++) {
if (x % i) continue;
int cnt = 0;
while (x % i == 0) {
cnt++;
x /= i; //相同的因子,如2
}
ans *= (cnt + 1); //加一是为了要算上最后的数字
}
if (x > 1) ans *= 2; //x本身也是个因子
return ans;
}
三、总结
有十一种dp诶,动态规划也太难了,而且跟贪心竟然还不一样,感觉有点分不太清。
开始看之前的BNU_ACM了,讲得超级清楚,寒假怎么就不好好看呢。哦还是要交周报,继续外力坚持了。
这周一直在比赛和补题,看视频学习的时间其实相对较少,还是得继续学的,很多算法还是很不清楚,下周要平衡一下时间,不过大多数的题目其实半暴力半算法还是做出来了的,但是话费了更多时间,有一点得不偿失。
另外发现我补题的时候好像都是在做一些之前已经知道的,有些思路的题目,那我不会一直在做同一个类型的题吧,还是得补一些平时不做的赛场上也没思路的题,说不定下次就会了呢。
根本没时间打cf和牛客,什么时候才能给我的cf上个分啊,时间也太难调和了,后悔寒假没有多打了。