暑假集训总结——区间DP,堆的概念及应用,STL(vector、set、pair、map、priority_queue),hash表,树状数组,图论
序言:
经过长达十几天的集训,确实学了不少知识点。我想如果再不总结的话,6天之后又要忘完了。
所以发一篇具有总结回忆性的博客,供大家回忆。
目录会本人自己排列的时间的先后顺序来排列,可直接食用。
目录:
一 、 动态规划1.区间DP二 、 STL1.vector2.pair3.set4.map5.priority_queue
三 、数据结构1.字符串hash2.hash表3.树状数组4.堆及其运用
四 、 图论最短路1.Floyd2.Dijkst3.SPFA4.Bellman-Ford及其优化优先队列 O(mlogn)fionacci堆O(nlogn + m)这个本人能力实在有限便不说了,想知道的可以去问问
动态规划
动态规划总共分为三个部分:
- 状态和状态变量
- 阶段和阶段变量- 策略和最优策略- 状态转移方程
什么是区间动态规划?
区间DP也是分为这三个部分,虽然它也是属于线性Dp的一种,但是它的阶段是以“区间长度”为单位。
区间DP的初始形态一般就由长度为111的“元区间”所构成的。它需要用左端点、右端点描述每个维度,在一些特殊的题中有可能会用到中点。
第一题 石子合并:
合并石子1
若最初的的第lll堆石子和rrr堆石子被合并在一起,则说明lrl~rl r之间的每堆石子也已经被合并,这样lll和rrr才有可能相邻。
因此,在任何时刻,任意一堆石子均可以用一个闭区间[l,r]来描述,表示这一堆是由最初的第lrl~rl r堆石子合并成的。
所以假设 如果有3堆石子,
则有2种合并方案,((1,2),3)和(1,(2,3))
如果有k堆石子呢?
不管怎么合并,总之最后总会归结为2堆,如果我们把最后两堆分开,左边和右边无论怎么合并,都必须满足最优合并方案,整个问题才能得到最优解。
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>using namespace std;
int min(int x,int y){return x>y?y:x;
}
int dp[105][105];
int s[105];
int n,x,k;
int main(){scanf("%d",&n);memset(dp,0x3f3f3f3f,sizeof(dp));for(int i=1;i<=n;i++){scanf("%d",&x);s[i]=s[i-1]+x;}for(int i=1;i<=n;i++){dp[i][i]=0;}for(int len=2;len<=n;len++){for(int j=1;j<=n-len+1;j++){int r=j+len-1;for(int k=1;k<r;k++)dp[j][r]=min(dp[j][r],dp[j][k]+dp[k+1][r]);dp[j][r]+=(s[r]-s[j-1]);}}printf("%d",dp[1][n]);return 0;
}
第二题能量项链:
能量项链
这道题几乎只是与合并石子有点区别,此时它是一个环,序列的长度是合并石子的两倍。
只需要循环的长度变成两倍即可。
思路就是:
用 dp[i][j]dp[i][j]dp[i][j] 表示合并区间 iii 到 jjj 的最大能量,
第一重循环表示珠子分组的终点,第二重循环的表示从珠子分组的起点 ,第三重循环表示截断的点。
AC代码:
#include<iostream>
using namespace std;
int n,e[205],f[205][205]={0};
int main()
{int i,j,k,mx=0;cin>>n;for(i=1;i<=n;i++){cin>>e[i];e[n+i]=e[i];}for(j=2;j<=2*n-1;j++)for(i=j-1;i>0&&j-i<n;i--)for(k=i;k<j;k++)f[i][j]=max(f[i][k]+f[k+1][j]+e[i]*e[k+1]*e[j+1],f[i][j]);for(i=1;i<=n;i++)mx=max(mx,f[i][i+n-1]);cout<<mx; return 0;
}
前面的都是版题以及版题的变式
现在来一点不是板子题的题。
第三题戳西瓜
戳西瓜
从题来看,我第一次是没看懂是说的一个什么意思。
不过有各位大佬,鼎力相助我才读懂这道题,并进而AC这道题
首先分析一下如果我戳破了第iii个,那么会留下i−1i-1i−1和i+1i+1i+1,但是此时你的和却跟i−1i-1i−1,iii,i+1i+1i+1都有关。
从这个条件不难看出它是一个(i-1,i+1)的开区间。
但是题目却要我们戳完所有的气球。怎么办呢?
这是我们就可以,设两个端点最左端点和最右端点分别为:
a[0]a[0]a[0]和a[n+1]a[n+1]a[n+1]且初值都赋值为111。
现在就不存在存在i−1i - 1i−1和i+1i + 1i+1是开区间的情况了。
设状态dp[i][j]dp[i][j]dp[i][j],表示戳破第i个到第j个的西瓜。
目标为dp[0][n+1]dp[0][n + 1]dp[0][n+1]。
气球 iii 和气球 jjj 之间的所有气球都可能是最后被戳破的那一个,
假设最后戳破的为 kkk
因为最后戳的是kkk所以要先把iii到kkk的全部戳破,答案为dp[i][k]dp[i][k]dp[i][k]
然后kkk到jjj肯定也要戳完,答案为dp[k][j]dp[k][j]dp[k][j]
现在在气球iii到气球jjj之间就只剩下iii,kkk,jjj了,
所以戳爆kkk的价值就是nums[i]∗nums[k]∗nums[j]nums[i]*nums[k]*nums[j]nums[i]∗nums[k]∗nums[j]
AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>using namespace std;
int dp[505][505];
int a[505];
int main(){int n;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}n++;a[0]=1;a[n+1]=1;for (int i = n; i >= 0; i--){for (int j = i + 1; j < n + 2; j++){for (int k = i + 1; k < j; k++) {dp[i][j]=max(dp[i][j], dp[i][k] + dp[k][j] + a[i]*a[j]*a[k]);}}}printf("%d",dp[0][n+1]);return 0;
}
二·二叉堆
在这之前我们需要一滴滴的知识储备就是,树。
完全二叉树:
如果一棵深度为k的二叉树,1至k-1层都是满的,即每层结点数满足2i-1,只有最下面一层的结点数小于2i-1,并且最下面一层的结点都集中在该层最左边的若干位置,则此二叉树称为完全二叉树。
那么我们所学的二叉堆呢,总的来说就是一个数组。
它可以被看作一棵完全二叉树。树中每个结点与数组中存放该结
点中值的那个元素相对应。如图所示::
设数组AAA的长度为lenlenlen,二叉树的结点个数为sizesizesize,
size≤lenize≤lenize≤len,则A[i]A[i]A[i]存储二叉树中编号为i的结点值
(111≤iii≤sizesizesize),而A[size]A[size]A[size]以后的元素并不属于相应的堆,
树的根为A[1]A[1]A[1],并且利用完全二叉树的性质,
我们很容易求第iii个结点的父结点fa(i)fa(i)fa(i)、左孩子lch(i)lch(i)lch(i)、
右孩子rch(i)rch(i)rch(i)的下标,分别是i/2i/2i/2、2i2i2i、2i+12i+12i+1。
除了这一性质之外,同时对除根以外的每个结点i,A[fa(i)]≥A[i]。
即除根结点以外,所有结点的值都不得超过其父结点的值,
这样就推出,堆中最大元素存放在根结点中,
且每一结点的子树中的结点值都小于等于该结点的值,
这种二叉堆又称为“大根堆”;反之,称为“小根堆”。
堆一般有两个重要的操作,putputput(往堆中加入一个元素)和
getgetget(从堆中取出并删除一个元素),putputput操作(也可用于建堆,
首先创建一个小根堆为例):
1、在堆尾加入一个元素,并把这个结点置为当前结点。2、比较当前结点和它父结点的大小
如果当前结点小于父结点,则交换它们的值,并把父结点置为当前结点,
继续转2。
如果当前结点大于等于父结点,则转3。3、结束。
以此循环nnn次,即可建立一个小根堆出来。
具体的操作请见其他各大博客,均有提及。
那么现在来看一下建小根堆的代码:
#include <cstdio>
#include <algorithm>using namespace std;
const int maxn = 15;
int heap[maxn], n, heap_size[maxn];
void PUT(int k){int fa, now;heap[++heap_size] = k;//把现在需要插入的数放到堆尾 now = heap_size;//把堆尾的这个数设为当前操作数 while(now > 1){//向上与父节点比较知道比完了各节点结束 fa = now >> 1;if(heap[now] >= heap[fa])//不小于父节点的值就结束 break;swap(heap[now], heap[fa]);//交换函数 now = fa;//交换后又继续往上比较 }
}int main(){scanf("%d", &n);for(int i = 1;i <= n;i++){//插入输入 scanf("%d",&a);PUT(a);}for(int i = 1;i <= n;i ++){//输出小根堆 printf("%d", heap[i]);}return 0;
}
从堆中去除并删除的getgetget操作:
1、取出堆中根结点的值。
2、把堆的最后一个结点(heapsizeheap_sizeheapsize)放到根的位置上,把根覆盖掉,堆长度减一。
3、把根结点置为当前父结点,即当前操作结点nownownow。
4、如果nownownow无儿子(now>heapsize/2now>heap_size/2now>heapsize/2),则转6;否则,把nownownow的两(或一)个儿子中值较小的那一个置为当前子结点sonsonson。
5、比较nownownow与sonsonson的值,如果nownownow的值小于等于sonsonson,转6;否则交换两个结点的值,把nownownow指向sonsonson,转4。
6、结束。
由于技术问题,本人找不到这张流程图,在这里道歉。
那么就来看一下删除根节点操作的代码吧:
int Get(){int now, son ,res;res = heap[1];//保存根节点 heap[1] = heap[heap_size --]// 堆尾节点替换根节点 now = 1;//把根节点置为当前节点开始向下与儿子比较 while(now * 2 <= heap_size){//当前节点非叶子节点就继续 son = now * 2;//初始化左儿子 if(son < heap_size && heap[son + 1]< heap[son])son ++;//有右儿子,且右儿子比左儿子削,son指向右儿子 if(heap[now] <= heap[son])//如果你爸没你儿子大,结束 break;swap(heap[now], heap[son]);//不然就交换 now = son;//把你儿子设为你现在的老子,继续向下比 }return res;//取出原堆中的根节点
}
例题一堆排序:
堆排序
一道简单的小根堆,只需要用swapswapswap交换更小的数,最后用一重
nnn次的循环依次取出并删除根结点就行了,即执行getgetget操作即可。
//正常的小根堆
//加上void swap()一个swap
//把那个数放到根节点上
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>using namespace std;
int heap_size,n;
int heap[10000005];
void swap(int &a,int &b){int t=a;a=b;b=t;
}
void put(int d){int now,fa;heap[++heap_size]=d;now=heap_size;while(now>1){fa=now>>1;if(heap[now]>=heap[fa]) return ;swap(heap[now],heap[fa]);now=fa;}
}
int Get(){int now,fa,res;res=heap[1];heap[1]=heap[heap_size--];now=1;while(now*2<=heap_size){fa=now*2;if(fa<heap_size && heap[fa+1]<heap[fa]) fa++;if(heap[now]<=heap[fa]) return res;swap(heap[now],heap[fa]);now=fa;}return res;
}
void work(){int x,y,ans=0;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&x);put(x);}for(int i=1;i<=n;i++){printf("%d ",Get());}
}
int main(){work();return 0;
}
例题二合并果子:
合并果子
这道题与模板没有区别,首先通过nnn次操作putputput建立一个小根堆
进而,不断重复提出两个数并删除,再加起来形成一个新节点再
进行同样的putputput操作,三个为一组,共有n−1n - 1n−1组,最后的答案
是ansansans。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;int heap_size, heap[10005];
int n;
void swap(int &x, int &y){int t = x, x = y, y = t;
}void put(int x){int son, fa;heap[++heap_size] = x;son = heap_size;while(son > 1){fa = son >> 1;if(heap[son] >= heap[fa]) break;swap(heap[son] ,heap[fa]);son = fa;}
}void get(){int fa, son, res;res = heap[1];heap[1] = heap[heap_size --];fa = 1;while(fa * 2 <= heap_size){son = fa *2;if(son < heap_size && heap[son + 1] < heap[son]) son ++;if(heap[fa] <= heap[son]) break;swap(heap[fa], heap[son]);fa = son;}return res;
}int main(){int x, y, ans = 0;scanf("%d",&n);for(int i = 1;i <= n;i++){sacnf("%d",&x);put(x);}for(int i = 1;i <= n;i++){x = get();y = get();ans += x + y;put(x + y);}printf("%d\n",ans);return 0;
}
例题三小猫钓鱼:
小猫钓鱼
本题一看就是某位同学最喜欢做的思维贪心题。
首先可以这么假设,如果知道了取到最大值得情况,当人最后在
第iii个池塘里钓鱼,那么时间至少是固定的,所以尽可能选取
钓到的鱼最多的池塘。所以,暴力枚举nnn次鱼塘的位置,即可。
那么有贪心,必定会有动态规划了,假设opt[t][n]opt[t][n]opt[t][n]表示第ttt分钟
人在第iii个鱼塘里钓到的鱼数。则:
opt[t][n] = maxinum(opt[t - k][n - 1] + s);
暴力枚举kkk,sss为t−k+1t - k + 1t−k+1到ttt之间,除开从第n−1n - 1n−1的鱼塘走到
第nnn个鱼塘的时间,在第nnn个鱼塘中可以钓到的鱼数。
好吧,进入正题。
建立以fishfishfish为键值的大根堆,包括能钓到鱼的数量和池塘的编号
然后借助枚举创造条件。即可得出正解。
#include <iostream>
#include <cstdio>
using namespace std;
struct Data {int fish, lake; //堆中结点的信息
};
Data heap[105];
int t[105], f[105], d[105];
int Max, k, t1;
void maintain(int i){ //维护堆Data a;int next;a = heap[i];next = i * 2;while (next <= k) {if (next < k && heap[next].fish < heap[next + 1].fish)next++;if (a.fish < heap[next].fish) {heap[i] = heap[next];i = next;next *= 2;} elsebreak;}heap[i] = a;
}
void work() {int i, j, m, n;cin >> n;for (i = 1; i <= n; i++) cin >> f[i];for (i = 1; i <= n; i++) cin >> d[i];for (i = 1; i < n; i++) cin >> t[i];cin >> m;for (k = 1; k <= n; k++) {int Time = m - t1;int ans = 0;for (i = 1; i <= k; i++) {heap[i].fish = f[i];heap[i].lake = i;}for (i = 1; i <= k / 2; i++) maintain(i);while (Time > 0 && heap[1].fish > 0) {ans += heap[1].fish;heap[1].fish -= d[heap[1].lake];maintain(1);Time--;}if (ans > Max)Max = ans;t1 += t[k];}cout << Max << endl;
}int main() {work();return 0;
}
这个知识点也是需要将每个函数的具体框架,记牢记熟。
和后面的STLSTLSTL相呼应。
三·STL
WarningWarningWarning:
本章节,请还未食用过栈和队列的无关战斗人员尽快离开战斗现场
强行食用,会引来强烈不适,谨慎食用。
2.vector
什么是vectorvectorvector?
vector直译为“向量”,一般说成“变长数组”,也就是长度根据需要而自动改变的数组.
有什么用?
有些题目需要开很多数组,往往造成内存超限,使用vectorvectorvector简单方便,还可节省空间。
定义:
vector<typename> name;//<变量类型> 变量名
以上定义相当于定义了一个一维数组name[size]name[size]name[size],只是sizesizesize不确
定,其长度可以根据需要而变化。其中,typenametypenametypename可以是任何基
本类型,如intintint、doubledoubledouble、charcharchar、结构体等,也可以是容器
vector<int> a; //定义了一个整型不定长数组a
vector<double> score; //定义了一个双精度浮点型不定长数组score
vector<node> stu; //定义了一个结构体类型的不定长数组stu
注意:
如果typenametypenametypename也是一个STLSTLSTL容器,那么定义时需要在两个“>”符号之间加一个空格,不然会被误认为是位运算的右移符号“<<”
forexamplefor exampleforexample:
vector<vector<int> > a; //定义了一个两个维度都可变的二维整型数组a vector<int> a[100]; //定义了一个第一维长度为100,第二位不限长度的二维数组a
关于vectorvectorvector的访问:
访问vectorvectorvector中的元素一般有两种方式:下标访问 和 迭代器
(iteratoriteratoriterator)访问。
第一种是通过“下标”访问的。
例如,对于容器vector<int>vvector<int> vvector<int>v,可以使用v[index]来访问
它的第indexindexindex个元素。其中,0≤index≤v.size()–10≤index≤v.size() – 10≤index≤v.size()–1,
v.size()v.size()v.size()表示vectorvectorvector中元素的个数。
第二种是通过“迭代器”访问的。
可以将迭代器理解为一种类似指针的变量,使用前需要提前定义,
其定义为:vector<typename>::iteratoritvector<typename>::iterator itvector<typename>::iteratorit,这个ititit就
是一个迭代器,可以通过∗it*it∗it来访问该容器里的元素值
例如:
vector<int>::iterator it = v.begin(); //定义一个迭代器it,初始化为容器v的首元素地址,这是*it相当//于v[0],*(it + i)相当于v[i]。
迭代器还可以进行自加自减操作,如it++it++it++,++it++it++it,it−−it--it−−,−−it--it−−it,
注意:迭代器不支持it<v.end()it<v.end()it<v.end()的写法,
只能是it!=v.end()it != v.end()it!=v.end(), v.end()v.end()v.end()并不是取v容器尾元素地址,
而是尾元素下一个地址。
例如:
for(vector<int>::iterator it = v.begin(); it != v.end(); it ++)
printf(“%d”,*it);
常用函数:
(1) push_back()
解释:push_back(x)将x添加到容器最后,时间复杂度为O(1)。
(2) size()
解释:如果是一维数组,size()用来获得vector中元素个数;如果是二维数组,size()用来获得vector中第二维的元素个数,时间复杂度为O(1),同时,还可以使用resize(n)重设数组大小。
例如输出12300:
vector<int> v;
for(int i = 1; i <= 3; i ++) v.push_back(i);
v.resize(5);
for(int i = 0; i < v.size(); i ++) printf(“%d”,v[i]);
(3) pop_back()
解释:用来删除vector中的尾元素。时间复杂度为O(1),例如以下代码输出12:
vector<int> v;for(int i = 1; i <= 3; i ++) v.push_back(i);v.pop_back();for(int i = 0; i < v.size(); i ++) printf(“%d”,v[i]);
(4) clear()
解释:用来清空vector中的所有元素。时间复杂度为O(n),例如以下代码输出0:
vector<int> v;for(int i = 1; i <= 3; i ++) v.push_back(i);v.clear();printf(“%d”,v.size());
- insert()
解释:insert(it, x)用来向vector任意迭代器it处插入元素x。时间复杂度为O(n),例如以下代码输出1 2 -1 3 4 5:
#include <cstdio>
#include <vector>using namespace std;
int main(){vector<int> v;//注意打空格养成好习惯 for(int i = 1;i <= 5;i ++)v.push_back(i);vector<int> :: iterator it = v.begin();v.insert(it + 2, -1);for(; it != v.end();it ++)printf("%d ",*it);return 0;
}
(6) erase()
解释:erase()erase()erase()用来删除vectorvectorvector中的元素,有两种用法,
一是erase(it)erase(it)erase(it),删除迭代器ititit处的单个元素.
二是erase(first,last)erase(first, last)erase(first,last),删除左闭右开区间[first,last][first, last][first,last]内的所有元素。例如:
#include <cstdio>
#include <vector>using namespace std;
int main(){vector<int> v;for(int i = 1;i <= 5;i++){v.push_back(i);}vector<int> :: irerator it = v.begin();v.erase(it + 1);v.erase(it + 2,i + 4);for(int i = 0;i < v.size();i++)printf("%d ",v[i]);return 0;
}
第一题上网统计:
上网统计
一道版题,只需要处理字符,如果遇到同一个人则合并。
由于用户名和浏览的网页名长度不固定,
用vectorvectorvector解决比较方便,可以分别用“下标”访问和“迭代器”访问。
//下标访问
#include<cstdio>
#include<cstdio>
#include<vector>using namespace std;
class std::vector<int> a;
int main(){for(int i=1;i<=3;i++) a.push_back(i);a.resize(5);for(int i=1;i<=a.size();i++){printf("%d",a[i]);}return 0;
}
#include<bits/std++>using namespace std;
vector<int> a;
int main(){for(int i=1;i<=3;i++) a.push_back(i);for(vector<int>::itrator it=a.begin();it!=a.end();it++){printf("%d",*it);}return 0;
}
#include<cstdio>
#include<vector>using namespace std;
int main(){vector<int> v;for(int i=1;i<=5;i++) v.push_back(i);vector<int>::iterator it=v.begin();v.insert(it+2,-1);for(;it!=v.end();it++)printf("%d ",*it);return 0;
}
//迭代器访问
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<bits/stdc++.h>using namespace std;
struct node{string ID;vector<string> WEB;
};
int main(){string id,web;vector<node> v;int n,m;cin>>n>>m;for(int i=1;i<=m;i++){cin>>id>>web;bool f=0;for(int j=0;j<v.size();j++){if(v[j].ID == id){v[j].WEB.push_back(web);f=1;break;}}if(f==0){node _new;_new.ID=id;_new.WEB.clear();_new.WEB.push_back(web);v.push_back(_new);}}for(int i=0;i<v.size();i++){cout<< v[i].ID;for(int j=0;j<v[i].WEB.size();j++){cout<<" "<<v[i].WEB[j];}cout<<endl;}return 0;
}
2.优先队列priority_queue:
优先队列优先级如何设置:
大根堆优先队列的定义:
priorty_queue<int> q; //默认为大顶堆优先队列
priorty_queue<int,vector<int>,less<int> > q;//好习惯
小根堆优先队列的定义:
priorty_queue<int,vector<int>,greater<int> > q;
例如以下代码输出1:
priorty_queue<int,vector<int>,greater<int> > q;
q.push(3);
q.push(4);
q.push(1);
printf(“%d\n”,q.top());
第二题有序表的最小和
有序表的最小和
如果用枚举出所有和,把它们依次压入小根堆优先队列,取出n个堆顶元素即可,但n的范围是400000,n方的算法的时间复杂度可想而知,直接舍去,但是由于两个序列已经是从小到大排序了的,我们来分析一下下表的数据:
第一行 A[1]+B[1] ≤ A[1]+B[2] ≤ A[1]+B[3] ≤ ······
第二行 A[2]+B[1] ≤ A[2]+B[2] ≤ A[2]+B[3] ≤ ······
······
第n行 A[n]+B[1] ≤ A[n]+B[2] ≤ A[n]+B[3] ≤ ······
显然每一行的第一项和都是这一行最小的和,所以不妨把每一行
的第一项和先压进优先队列里,再取出堆顶元素)并输出,
如果取的是第i行的元素,就把第i行的下一个元素压入,让堆中
始终保持n个元素和。
AC代码:
#include<bits/stdc++.h>using namespace std;
int const maxn=4e5+5;
struct num{int id;int value;bool operator <(const num x) const {return value>x.value ;}
};
int a[maxn],b[maxn],c[maxn];
priority_queue<num> q;
int main() {int n;scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);for(int i=1;i<=n;i++) scanf("%d",&b[i]);for(int i=1;i<=n;i++){c[i]=2;num tmp;tmp.value=a[i]+b[1];tmp.id=i;q.push(tmp);}for(int i=1;i<=n;i++){num tmp=q.top();q.pop();printf("%d\n",tmp.value);tmp.value=a[tmp.id]+b[c[tmp.id]++];q.push(tmp);}return 0;
}
3.映射map:
什么是mapmapmap?
map翻译为映射,其实数组就是一种映射。比如$int a[100];$,就是定义了一个int到int的映射,而$a[5]=25;$是把5映射到25,他们是一一对应的,数组总是把$int$类型映射到其它基本类型,因为数组下标只能是$int$。但有时希望把$string$映射成一个$int$数组就不方便了,这时就可以使用$map$,它可以将任何基本类型(包括容器)映射到任何基本类型。
mapmapmap常用的三种情形:
1.需要建立字符(串)与整数之间的映射,使用map可以减少代码量;2.判断大整数(比如几千位)或者其它类型数据是否存在,可以把
map当bool类型数组使用(哈希表);3.字符串与字符串之间的映射。
定义:
map<typename1, typename2> name;
其中,typename1是映射前的类型(键key),typename2是映射后
的类型(值value),name为映射的名字,例如:
map<int, int> mp;//普通int数组
map<string, int> mp;//字符串到整型的映射
map<set<int>, string> mp;//键和值也可以是容器
mapmapmap的键对值必须唯一,
也就是说键keykeykey必须唯一,值valuevaluevalue不一定。
mapmapmap的访问:
关于mapmapmap的访问,也是分为下标和迭代器两种
下标访问(例):
map<char, int> mp;
用mp[‘c’]mp[‘c’]mp[‘c’] 来访问它对应的元素,如mp[‘c’]=124mp[‘c’] = 124mp[‘c’]=124.
迭代器访问(例):
map<typename1, typename2>::iterator it;
因为mapmapmap的每一对映射都有两个typenametypenametypename,所以使用“it−>firstit -> firstit−>first”来访问键,而使用“it−>secondit -> secondit−>second”来访问值。例如:
map<char, int> mp;
mp[‘m’] = 20; mp[‘r’] = 30; mp[‘a’] = 40;
for(map<char, int>::iterator it = mp.begin(); it != mp.end(); it ++)printf(“%c %d\n”,it -> first, it -> second);
输出答案:
a 40
m 20
r 30
map在建立映射的同时,会自动实现按照键从小到大排序。
因为map内部使用“红黑树”实现,后面set也是。
那什么是红黑树?
红黑树(RedBlackTreeRed Black TreeRedBlackTree) 是一种自平衡二叉查找树,
是在计算机科学中用到的一种数据结构,典型的用途是实现关联
数组。红黑树是一种特定类型的二叉树,它是在计算机科学中用
来组织数据比如数字的块的一种结构。若一棵二叉查找树是红黑
树,则它的任一子树必为红黑树。
map的常用函数:
(1)find() 和 size()
解释:find(key)是返回键为key的映射的迭代器,
时间复杂度为O(log2n),n为map中映射的对数。
size()用来获得map中映射的对数,时间复杂度为O(1)。
例如以下代码输出3 b 30:
map<char, int> mp;
mp[‘a’] = 20;
mp[‘b’] = 30;
mp[‘c’] = 40;
printf(“%d ”, mp.size());
map<char, int>::iterator it = mp.find(‘b’);
printf(“%c %d\n”,it -> first, it -> second);
(2)erase()
删除一个区间内所有元素用erase(first, last)。其中,
first为区间的起始迭代器;
last为区间的末尾迭代器的下一个地址,
也就是左闭右开区间[first, last) 。
时间复杂度为O(first - last)。
下面代码输出aaa 202020:
map<char, int> mp;
mp[‘a’] = 20;
mp[‘b’] = 30;
mp[‘c’] = 40;
map<char, int>::iterator it = mp.find(‘b’);
mp.erase(it, mp.end());
for(map<char, int>::iterator it = mp.begin(); it != mp.end(); it ++)printf(“%c %d\n”,it -> first, it -> second);
(3)clear()
clear()用来清空map。时间复杂度为O(n)。
vectorvectorvector的用法一样
map<char, int> mp;
mp.clear();
第三题查字典:
查字典
这是一道经典的map映射题,这是一道很有趣的题,
由于GMGMGM通过单词来查询页码,即单词是键,页码是值,
查询时输入键,即可在O(1)时间内查询值,也就是对应页码。
这里只需要用到mapmapmap基本函数即可
AC代码:
#include<bits/stdc++.h>using namespace std;
map<string,int> dic;
int main(){int n,m;int page;scanf("%d",&n);for(int i=1;i<=n;i++){string s;cin>>s;scanf("%d",&page);dic[s]=page;}scanf("%d",&m);while(m--){string s; cin>>s;printf("%d\n",dic[s]);}return 0;
}
二元结构体pair
什么是pairpairpair?
pairpairpair是”二元结构体”的替代品,将两个元素捆绑在一起,
节省编码时间。如同定义一个这个:
struct pair {typename1 first;typename2 second;
};
如何定义?
要使用pairpairpair,必须先添加头文件,即#include<utility>
,同时需要using namespace std;
。因为map的内部实现涉及pairpairpair,因此添加mapmapmap头文件时会自动添加utilityutilityutility头文件,此时可以省去utilityutilityutility头文件。
pair<typename1, typename2> name;
如何初始化?
例如:定义一个参数为string和int类型的pair,并同时初始化
在这里我就讲那个最好记的那个:
pair<string, int> p(“haha”, 5);
注意:
pairpairpair可以直接做比较运算,比较的规则是先以firstfirstfirst的大小作为
标准,只有当first相等时才去判断secondsecondsecond的大小。
forexample:for example:forexample:
以下一段代码输出p1 < p3 p1 <= p3 p1 < p2
pair<int, int> p1(5, 10);
pair<int, int> p2(5, 15);
pair<int, int> p3(10, 5);
if(p1 < p3) printf(“p1<p3”);
if(p1 <= p3) printf(“p1<=p3”);
if(p1 < p2) printf(“p1<p2”);
由于mapmapmap可以根据键值自动排序,而pairpairpair又可以比较大小,
所以pairpairpair可插入到同类型的map中并根据it->first排序,
(注意,如果it->first
相同则根据mapmapmap键值唯一的特性,只保留先输入的二元组)
例如输出:
leimu 20
rem 51
#include <cstdio>
#include <map>
#include <iostream>using namespace std;
int main(){map<string, int> mp;mp.insert(make_pair("leimu",20));mp.insert(pair<string, int>("rem", 51));for(map<string, int>iterator it = mp.begin(); it != mp.end();it ++)cout << it -> first <<" "<< it -> second << endl;return 0;
}
第四题Let the Balloon
Let the Balloon Rise
这道题其实我现在都是懵懂的,所以我只能献上GMGMGM所赞助的
PPTPPTPPT的思路了:
AC代码:
#include<bits/stdc++.h>using namespace std;
int main(){int n;while(scanf("%d",&n),n){map<string,int> ma;map<string,int>::iterator x;map<string,int>::iterator y;for(int i=0;i<n;i++){string mm;cin>>mm;x=ma.find(mm);if(x==ma.end()){ma.insert(make_pair(mm,1));}else{ma[mm]++;}}int MAX=0;for(x=ma.begin(); x!= ma.end();x++){if(x -> second>=MAX){MAX=x -> second;y=x;}}cout<<y -> first<<endl;}return 0;
}
set集合
好了最后一个了。
什么是setsetset?
setsetset翻译为集合,是一个内部自动有序且不含重复元素的容器。
setsetset最主要的作用就是自动去重并按升序排序,
因此遇到需要去重但是又不方便直接开数组的情况,
比如元素比较多或者类型不是intintint,可以尝试用setsetset解决。
setsetset中的元素是唯一的,内部同样采用“红黑树”实现。
如何定义:
set<typename> name;
其中,typenametypenametypename可以是任何基本类型或者容器,namenamename是集合的名字,例:
set<int> st;
当然也可以定义set数组,例:
set<int> st[100];
这样st[0]st[0]st[0]~st[99]st[99]st[99]中的每一个元素都是一个setsetset容器。
setsetset的访问方式同上。
setsetset只能通过迭代器访问,即先定义一个迭代器:
set<typename>::iterator it;
注意:
然后使用∗it*it∗it来访问set中的元素。setsetset也不支持(it +i )
和it < st.end()
的访问方式,实际上除了vectorvectorvector和stringstringstring之外的STL容器都不支持。
例如:
set<int> st;
st.insert(3);
st.insert(5);
st.insert(2);
st.insert(3);
for(set<int>::iterator it = st.begin(); it != st.end(); it ++)printf(“%d”, *it);
setsetset常用函数:
(1)insert()insert()insert()和 size()size()size()
解释:insert(x)用来将x插入到set中,并自动递增排序和去重,
时间复杂度为 O(log2n),n为set中元素的个数。
size()用来获得set中的元素个数,时间复杂度为O(1)。
(2)find()find()find()
解释:find(value)是返回set中对应值value的迭代器
(可以把it看成地址,*it看成地址对应的值),
时间复杂度为O(log2n)。
例如以下一段代码输出“3 2”。
set<int> st;
for(int i = 1; i <= 3; i ++) st.insert(i);
printf(“%d”, st.size());
printf(“%d”, *(st.find(2)));
(3)clear()clear()clear()
解释:clear()用来清空set中的所有元素,时间复杂度为O(n)。
(4)erase()
解释:erase()可以删除单个元素,也可以删除一个区间内的所有元素。
删除单个元素可以用erase(it),其中it为要删除元素的迭代器,
时间复杂度为O(1)。也可以用erase(value),value为要删除元素的值,
时间复杂度为O(log2n)。
下面代码输出“1 3 200 ”:
set<int> st;
for(int i = 1; i <= 3; i ++) st.insert(i);
st.erase(st.find(2));
for(set<int>::iterator it = st.begin(); it != st.end(); it ++) printf(“%d ”, *it);
st.clear();
st.insert(100);
st.insert(200);
st.erase(100);
for(set<int>::iterator it = st.begin(); it != st.end(); it ++)
printf(“%d ”, *it);
eraseeraseerase还有一个神奇的用法,
删除一个区间内所有元素用erase(first, last)。
其中,first为区间的起始迭代器;last为区间的末尾迭代器的下一个地
址,也就是左闭右开区间[first, last) 。
时间复杂度为O(first - last)。
下面代码输出“1 2 3 4 5 6 7 8 9 ”:
set<int> st;
for(int i = 1; i <= 100; i ++) st.insert(i);
set<int>::iterator it = st.find(10);
st.erase(it, st.end());
for(it = st.begin(); it != st.end(); it ++)printf(“%d ”, *it);
例题题海战:
因为本人实力有限,只有五十分代码。
等哪天我ACACAC再贴上来。
感谢GM的友情赞助,因为本章节多数是拿来背的,比如这些概念以及重载运算符,所以概念这些,都是借鉴的PPT上的。但是代码是自己亲手码的,勿喷。
四、HASH
前言:对于这个版块,我是真的懵逼,GM交了我们这么化解
HASH冲突的方法,结果LFLFLF一来说,其实HASH主要是
字符串HASH,而且也没有这么多需要化解的冲突的题。
(理解是不可能理解的,这辈子都不可能理解的。机房里个个都是
人才,说话也好听,我超喜欢里面的。)
所以至于化解冲突的方法,我就不贴上来了。
如有需要,请点击
那我来讲一下构造HASH函数的方法:
1.直接定址法
2.除后余数法
3.平方取中法
4.数字分析法
5.折叠法
6. 随机数法
第一种,好处在于以关键码 key 的某个线性函数值为哈希地址
,不会产生冲突。
·····
·····
·····
·····(以上几种省略,不知道该怎么写,也没有什么优化)
第六种,写随机数,(写对拍),这是一个随缘的样例但是
但是却有针对性,如果在短时间想不出来比较复杂的样例
这个就是一个明智之举。
在实际操作中需视不同情况采用不同的哈希函数。
通常考虑的因素:
(1)计算哈希函数所需时间(包括硬件指令的因素);(2)关键字的长度;(3)哈希表的大小;(4)关键字的分布情况;(5)记录的查找频率。
说的概念是真的蒙,因为是优化程序的一种途径
所以说将一些例题吧。
第一题疯狂的搜索
#include <cstdio>
#include <algorithm>
#include <bits/stdc++.h>using namespace std;
bool hash[16000005];
char a[16000005];
int n,nc;
int id[305];
int main(){while(~scanf("%d %d",&n,&nc)){//取反输出memset(hash,0,sizeof(hash));memset(id,-1,sizeof(id));scanf("%s",a);int cnt=0;int len=strlen(a);for(int i=1;i<=len && cnt<nc;i++){if(id[a[i]]!=-1) continue;id[a[i]]=cnt++;}int ans=0;for(int i=0;i<len-n+1;i++){int sum=0;for(int j=i;j<n+i;j++)sum=sum*nc+id[a[j]];//切换进制if(hash[sum]) continue;ans++;hash[sum]=1; }printf("%d\n" ,ans);}
}
第二题兔子与兔子
这道题主要是求出字符串 strstrstr 的所有的前缀字符串的HASH值,那么,strstrstr的所有
子串的HASH值都可以在 O(1)的时间内计算出来
然后就是求子串区间 [L, R]:
pre[R] - pre[L - 1] * p^ (R - L + 1)
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;const int p = 131;
const int Maxn = 1000015;
int n;
int query, l1, r1, l2, r2;
unsigned long long pre[Maxn]; //各个前缀 的哈希值
unsigned long long L, R;
unsigned long long p_pow[Maxn];
char str[Maxn];int main(){scanf("%s", str + 1);n = strlen(str + 1);pre[0] = 0;for(int i = 1; i <= n; i++){pre[i] = pre[i - 1] * p + str[i] - 'a' + 1; }p_pow[0] = 1;for(int i = 1; i < Maxn; i++){p_pow[i] = p_pow[i - 1] * p;}scanf("%d", &query);for(int q = 1; q <= query; q++){scanf("%d %d %d %d", &l1, &r1, &l2, &r2); L = pre[r1] - pre[l1 - 1] * p_pow[r1 - l1 + 1];R = pre[r2] - pre[l2 - 1] * p_pow[r2 - l2 + 1];if(L == R){printf("Yes\n");}else{printf("No\n");}}return 0;
}
五·树状数组
前言:
这章节的代码框架,真就是一个字 背 !
什么是树状数组(又是这个问题哈)
树状数组(Binary Indexed Tree(B.I.T)也称作Fenwick Tree)是一个区间查询和单点
修改复杂度都为log(n)的数据结构。主要用于查询任意两点之间的所有元素之和。
说白了,就是个数组,只是长得像树。
questionquestionquestion:
有一个一维数组长度为n,求区间[L,R]的和,并且可以对原数组某一元素进行修改?
现在就引入LowbitLowbitLowbit的概念了。
LowbitLowbitLowbit:
lowbit(i)lowbit(i)lowbit(i)的意思是将 iii 转化成二进制数之后,只保留最低位的1及其后面的0,
截断前面的内容,然后再转成十进制数,这个数也是树状数组中i号位的子叶数。
forexamplefor exampleforexample:
lowbit(22)lowbit(22)lowbit(22)的意思是将 22 转化成二进制数之后,得到10110,保留末位的1及其
后的0,并截断前面的内容,得到10,转化为十进制数为2,即lowbit(22)lowbit(22)lowbit(22)=2,
证明C[22]的子叶数为2个。
那么LowbitLowbitLowbit有什么用呢?
废话那不构造树状数组C[i]的吗
好了,写一下吧:
#include<cstdio>
int A[10]={0,1,2,3,4,5,6,7,8},C[10];
int lowbit(int x){return x & -x;
}int main(){for(int i = 1; i <= 8; i ++)for(int j = i - lowbit(i) + 1; j <= i; j ++)C[i] += A[j];for(int i = 1; i <= 8; i ++)printf("%d ",C[i]);return 0;
}
那张图我就不拿出来了,实在拿不出来。
这玩意还可以对原数组A[i]A[i]A[i]进行更新(update)操作
void update(int k,int x)// A[k]+x 操作 {for(int i = k; i <= n; i += lowbit(i))C[i] += x;}
说到 UpdateUpdateUpdate 那我顺便就说一下对树状数组C[i]进行初始化操作
#include<cstdio>
int A[10], C[10];
int lowbit( int x ){ //求lowbitreturn x & -x;
}void update(int k , int x){ //更新C[i]for(int i = k; i <= 8; i += lowbit(i))C[i] += x;
}int main(){for(int i = 1; i <= 8; i ++){ //输入时预处理,构造C[i]scanf("%d", &A[i]);update( i, A[i]);}for(int i = 1; i <= 8; i ++) //输出C[i]printf("%d ", C[i]);return 0;
}
除此之外,还第三个作用就是求前缀和(Sum)
解释一下 : 因为这个区间就是前缀和(1 ~ i)所以求是求前缀和。
int Sum(int k){for(int i = k; i > 0; i -= lowbit(i) )B[k] += C[i];return B[k]; }
延伸出来,就是可以求前缀和B[i]B[i]B[i]了
#include<cstdio>int A[10],B[10],C[10];int lowbit(int x){return x & -x;}void update(int k,int x){for(int i = k; i <= 8; i += lowbit(i))C[i] += x;}void Sum(int k){for(int i = k; i > 0; i -= lowbit(i))B[k] += C[i]; }int main(){for(int i = 1; i <= 8; i ++){scanf("%d", &A[i]);update(i , A[i]);Sum(i);}for(int i = 1; i <= 8; i ++)printf("%d ",B[i]);return 0;}
好了,我讲完了现在就来砍几道题吧。
第一题单点修改、区间查询
#include <bits/stdc++.h>using namespace std;
const int MAXN = 1e6 + 5;
int n, m;
long long BIT[MAXN];
int a[MAXN];
int lowbit(int x) { return x & (-x); }void Update(int x, int y) {for (int i = x; i <= n; i += lowbit(i)) BIT[i] += y;
}long long Sum(int x) {long long ans = 0;for (int i = x; i; i -= lowbit(i)) ans += BIT[i];return ans;
}int main() {scanf("%d %d", &n, &m);for (int i = 1; i <= n; i++) {scanf("%d", &a[i]);Update(i, a[i]);}for (int i = 1; i <= m; i++) {int p, l, r;scanf("%d %d %d", &p, &l, &r);if (p == 1) {Update(l, r);} else {printf("%lld\n", Sum(r) - Sum(l - 1));}}return 0;
}
第二题区间修改、单点查询
#include <bits/stdc++.h>
const int maxn = 1e6 + 5;using namespace std;
long long BIT[maxn];
int a[maxn], b[maxn];
int n, m;
int lowbit(int x){return x & (-x);
} void Update(int x,int y){for(int i = x;i <= n;i += lowbit(i))BIT[i] += y;
}long long Sum(int x){long long ans = 0;for(int i = x;i > 0;i -= lowbit(i))ans += BIT[i];return ans;
}int main(){scanf("%d %d", &n, &m);for(int i = 1;i <= n;i++){scanf("%d", &a[i]);b[i]=a[i] - a[i - 1];}for(int i = 1;i <= n;i++){Update(i,b[i]);}for(int i = 1;i <= m;i++){int p;int l,r,k;scanf("%d", &p);if(p == 1){scanf("%d %d %d", &l, &r, &k);Update(l , k);Update(r + 1 , -k);}else if(p == 2){int k;scanf("%d", &k);printf("%lld\n", Sum(k));}}return 0;
}
第三题区间修改、区间查询
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(a) ((a)&(-(a)))
#define ll long long
using namespace std;
const int M=1e6+10;
int n,m,x;
ll bit1[M],bit2[M];
void add(int a,ll b){ll t=a;for(;a<=n;a+=lowbit(a)){bit1[a]+=b;bit2[a]+=b*t;}
}
ll query(int a){ll t1=0,t2=0,ls=a;for(;a;a-=lowbit(a)){t1+=bit1[a];t2+=bit2[a];}t1*=(ls+1);return t1-t2;
}
ll range_query(int l,int r){return query(r)-query(l-1);
}
void range_add(int l,int r,ll v){add(l,v);add(r+1,-v);
}
int l,r,opt;
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%d",&x);range_add(i,i,x);}while(m--){scanf("%d%d%d",&opt,&l,&r);if(opt==1){scanf("%d",&x);range_add(l,r,x);}else{printf("%lld\n",range_query(l,r));}}return 0;
}
那么二维的这几道题只是会有一点点改变其余,没改变。只是思路会变一点。
二维树状数组 3:区间修改,区间查询
#include<bits/stdc++.h>using namespace std;
const int maxn=2100;
long long BIT1[maxn][maxn],BIT2[maxn][maxn],BIT3[maxn][maxn],BIT4[maxn][maxn];
int n,m;
int lowbit(int x){return x&(-x);
}void updata(int x,int y,long long z){for(int i = x;i <= n;i += lowbit(i)){for(int j = y;j <= n;j += lowbit(j)){BIT1[i][j] += z;BIT2[i][j] += x*z;BIT3[i][j] += y*z;BIT4[i][j] += x*y*z;}}
}
long long Sum(int x,int y){long long ans = 0;for(int i = x;i > 0;i -= lowbit(i)){for(int j = y;j > 0;j -= lowbit(j)){ans += (x + 1) * (y + 1) * BIT1[i][j] - (y + 1) * BIT2[i][j]-(x + 1) * BIT3[i][j] + BIT4[i][j];}}return ans;
}
int main()
{while(scanf("%d %d",&n,&m) == 2){memset(BIT1, 0, sizeof(BIT1));memset(BIT2, 0, sizeof(BIT2));memset(BIT3, 0, sizeof(BIT3));memset(BIT4, 0, sizeof(BIT4));int p;while(scanf("%d",&p) == 1){if(p == 1){int a,b,c,d;long long x;scanf("%d %d %d %d %lld",&a,&b,&c,&d,&x);updata(a,b,x);updata(c+1,b,-x);updata(a,d+1,-x);updata(c+1,d+1,x);}else{int x1,y1,x2,y2;scanf("%d %d %d %d",&x1,&y1,&x2,&y2);printf("%lld\n",Sum(x2,y2) - Sum(x1-1,y2) - Sum(x2,y1-1) + Sum(x1-1,y1-1));}}}return 0;
}
暑假集训总结——区间DP,堆的概念及应用,STL(vector、set、pair、map、priority_queue),hash表,树状数组,图论相关推荐
- 树形DP+树状数组 HDU 5877 Weak Pair
1 //树形DP+树状数组 HDU 5877 Weak Pair 2 // 思路:用树状数组每次加k/a[i],每个节点ans+=Sum(a[i]) 表示每次加大于等于a[i]的值 3 // 这道题要 ...
- SPOJ D-query 树状数组离线 求区间内不同数字的个数
Given a sequence of n numbers a1, a2, -, an and a number of d-queries. A d-query is a pair (i, j) (1 ...
- HDU-5542-The Battle of Chibi【树状数组+dp】
HDU-5542-The Battle of Chibi[树状数组+dp] Time Limit: 6000/4000 MS (Java/Others) Memory Limit: 65535/655 ...
- 51nod 1680区间求和 (dp+树状数组/线段树)
不妨考虑已知一个区间[l,r]的k=1.k=2....k=r-l+1这些数的答案ans(只是这一个区间,不包含子区间) 那么如果加入一个新的数字a[i](i = r+1) 则新区间[l, i]的答案为 ...
- 2023牛客寒假算法基础集训营4_20230130「向上取整」「夹逼dp」「lowbit科学+树状数组性质」「搜索」「倍增跳表」「莫队」
6/13 教育场是有被教育到.(预计会鸽几题. 已过非太水的题们 //B //https://ac.nowcoder.com/acm/contest/46812/B//小构造小数学#include & ...
- Educational Codeforces Round 80 (Rated for Div. 2)SZU cf集训round2 C~E(dp,状压+二分,树状数组+逆向思维)
C. Two Arrays 题目大意:就是给定两个整数n和m.计算数组对的数量(a,b),使得: 1 .两个阵列的长度都等于m: 2 .每个数组的每个元素都是1到n(包括1和n)之间的整数: 从1到m ...
- acwing 297. 赤壁之战 树状数组优化DP 寒假集训
题目链接 想要求长度为M的子序列,我们可以拿DP方程来计算,并且这个DP也是比较好看出来的DP[i][j]代表着i后j位置中的所有长度为j的子序列,递推方程为 for(int i=1;i<=n; ...
- NEFU大一暑假集训-树状数组
题集链接 目录: OP A Ultra-QuickSort 题目大意 思路 代码 B Stars 题目大意 思路 代码 C Mobile phones 题目大意 思路 代码 D Cows 题目大意 思 ...
- bzoj 1264: [AHOI2006]基因匹配Match (树状数组优化dp)
链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1264 思路: n大小为20000*5,而一般的dp求最长公共子序列复杂度是 n*n的,所以我 ...
最新文章
- ( KMP 求循环节的个数)Power Strings -- poj -- 2406
- 在android手机上运行PHP
- VC++ MFC中如何将应用程序的配置信息保存到注册表中(一)
- 2017-06-09 问题
- mfc 弹簧_弹簧和线程:事务
- Android BitmapShader 实战 实现圆形、圆角图片
- 动态HTML处理和机器图像识别
- Nature封面引发持续热议,死亡猪脑恢复部分功能可实现永生?官方答疑来了
- windows安装php和mysql
- Object-C中的非正式协议与正式协议
- 人工神经网络可以做什么,人工神经网络有什么用
- 等保三级密码技术应用要求 GM/T 0054-2018
- win10 1050ti anaconda搭建tensorflow-gpu
- 使用Adobe illustrator (AI)快速制作图标
- 无缘中兴(拒绝了offer)
- 每周一品 · 音圈电机(VCM)中的磁性材料
- 克转换成千克怎么算python_如何在Python中将磅转换为千克
- 基于AndroidStudio员工绩效考核评价系统app设计
- 一维数组的创建及使用
- 触角云开发的微信商城系统