本周也是迎来了大一暑期集训的第一周,整体学下来感觉很忙但是也做不出几道题,考试也只能切两三道题(悲)。争取在休息天整理一下当周所学吧,不要让自己白白浪费这段时间。
第一周主要学的是数据结构和动态规划。数据结构方面先是讲了一些作用和本质(?大概是吧对不起学长我当时真的太困了想睡觉),主要集中单调栈,单调队列,优先队列,树状数组以及线段树。单调栈和单调队列都比较基础就直接贴代码吧
单调栈:找序列中离自己最近的符合条件的元素
while(n--){
int x;
cin>>x;
while(!s.empty()&&s.top()>=x)s.pop();
if(s.empty())cout<<"-1"<<' ';
else cout<<s.top()<<' ';
s.push(x);
}
单调队列:滑动窗口,还有滑动窗口plus,特点就是在一个区间内找答案
deque<int>q;
for(int i=0;i<n;i++){
while(!q.empty()&&a[i]<a[q.back()])q.pop_back();
q.push_back(i);
if(!q.empty()&&q.front()+k<=i)q.pop_front();
if(i>=k-1)cout<<a[q.front()]<<" ";
}
优先队列:可以用来求中位数(用两个优先队列)
priority_queue<int,vector<int>,less<int> >q;//但一般都写成priority_queue<int>q(默认大根堆desu)
priority_queue<int,vector<int>,greater<int> >q;
接下来就是最让我头疼的两个了,虽然其实也挺基础的(痛)。
先从树状数组讲起,其实无论是树状数组还是线段树都是分治的思想,树状数组就是讲一个区间通过二进制快速划分成多个子区间,同时将区间查询和单点修改的时间复杂度化成\(\log n\),核心就是分治(说了跟没说差不多),优点:修改和查询操作复杂度于线段树一样都是$\log n&,但是常数比线段树小,并且实现比线段树简单,缺点:扩展性弱,线段树能解决的问题,树状数组不一定能解决。下面是树状数组的核心代码。
ll c[maxn];
int n;
inline int lowbit(int x){//取最高位的1,有兴趣可以自己推一下(?
return x&-x;
}
void add(int x,int k){
while(x<=n){
c[x]+=k;
x=x+lowbit(x);
}
}
ll getsum(int x){
ll res=0;
while(x>=1){
res+=c[x];
x=x-lowbit(x);
}
return res;
}
线段树相对而言要稍微复杂一点,但是从功能上来说可以实现更多的功能,比如说区间更新加区间查询,以及维护更复杂的信息都可以用线段树,下面是线段树的核心代码
ll a[N];
struct SGT{
ll val;
ll lazy;
}sgt[N<<2];//这边数组一定记得要开4*N,你也不希望你的代码re吧(
void pushup(int u){
sgt[u].val=sgt[u<<1].val+sgt[u<<1|1].val;
}
void pushdown(int u,int l,int r){
int mid=l+r>>1;
sgt[u<<1].lazy+=sgt[u].lazy;
sgt[u<<1].val+=sgt[u].lazy*(mid-l+1);
sgt[u<<1|1].lazy+=sgt[u].lazy;
sgt[u<<1|1].val+=sgt[u].lazy*(r-mid);
sgt[u].lazy=0;
}
void build(int u,int l,int r){
if(l==r){
sgt[u].val=a[l];
return;
}
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void update(int u,int l,int r,int x,int y,ll k){
if(l>=x&&r<=y){
sgt[u].val+=(r-l+1)*k;
sgt[u].lazy+=k;
return;
}
pushdown(u,l,r);
int mid=l+r>>1;
if(x<=mid)update(u<<1,l,mid,x,y,k);
if(y>mid)update(u<<1|1,mid+1,r,x,y,k);
pushup(u);
}
ll query(int u,int l,int r,int x,int y){
if(l>=x&&r<=y)return sgt[u].val;
pushdown(u,l,r);
ll ret=0;
int mid=l+r>>1;
if(x<=mid)ret+=query(u<<1,l,mid,x,y);
if(y>mid)ret+=query(u<<1|1,mid+1,r,x,y);
return ret;
}
突然想起来还有个list没提到,为什么会突然想到这个呢,是因为集训有一道题虽然名字是双端队列模板,但是最后得用list替代(至于为什么要换这里就不多阐述了),当时也是给小小的主播整的有点红了。
第二个周期讲的是动态规划,这个周期真的把我整的又累又不会几道题,失去了一切力气与手段,然后小测用尽一切气力,拼死拼活做出来两道题aaaa(当时在考场思路都是对的但是一直不知道怎么优化我那1e9范围的数组,完全没想到前几天学的线段树,虽然想到了也不一定会做),dp还是太吃操作了,还得慢慢做题,接下来就是对dp(至少是目前)的一点小小的总结。
一.背包dp
1.1 01背包
朴素版:d[i][j]=max(d[i-1][j],d[i-1][j-w[i]]+v[i]) 从前往后
滚动数组(用滚动数组并且从后往前更新数组)d[j]=max(d[j],d[j-w[i]]+v[i])
1.2 完全背包
f[j]=max(f[j],f[j-v[i]]+w[i]);从前往后,同滚动数组版
1.3 多重背包(类似于有限制的完全背包)
将数量用二进制划分,相当于把一个大的组分成了单个物品,然后再用01背包
1.4 分组背包
三重循环即可,转移方程:f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
1.5 大背包(考场上遇到的,甚至卡了半天)
当没办法用数组下标存背包总容量m的时候,可以进行反向思考,用数组下标表示存的价值,f[i]就表示装i价值的东西需要的最少体积,思路还是一样的
二.线性dp
例题:数字三角形、最长上升子序列(靠,这个二分怎么看不懂)、LCIS(转移方程:f[i, j] = max(f[i - 1, j], f[i, j - 1], f[i - 1, j - 1] + 1),后面的情况只有当a[i]==b[j]时才会出现,最短编辑距离(转移方程,f[i][j] = f[i - 1][j - 1] a[i]=b[j], f[i][j] = min(f[i][j - 1] + 1, f[i - 1][j] + 1, f[i - 1][j - 1] + 1))
三.区间dp(沟槽的区间dp难死我了)
例题:石子合并(先计算前缀和,再中间插点)关路灯,Kaavi and Magic Spell(不是div1 c真不是我能做出来的吧,我一个cf1200何德何能做这种题)
四.计数dp
例题:整数划分
五.数位dp
六.状态压缩
七.记忆化搜索
八.树形dp
这些大概就是知识点罢,后面找时间再把那些之前不会做的题再梳理一遍(虽然dp题单大部分题都不会哈哈)。落笔止于此,新的一周还在继续,希望自己能训练赛进个前十吧(一次就好)。————7-27 23:22 落安
浙公网安备 33010602011771号