洛谷笔记 Day 4(2)
T1
思路
考虑拆开这个式子,把 \(\max\) 式子和 \(\min\) 式子分别处理,接着考虑贡献,一个数能够成为 \(\max\) 或 \(\min\) 当且仅当左右都没有比它小的或者大的,笛卡尔树或者单调栈处理就行。
代码
#include<bits/stdc++.h>
using namespace std;
int a[300005],l[300005],r[300005];
int main(){
int n;cin>>n;
long long ans=0;
vector<int>st;
for (int i=1;i<=n;++i)cin>>a[i];
for (int i=1;i<=n;++i){
while (!st.empty()&&a[st.back()]<a[i])st.pop_back();
l[i]=st.empty()?1:st.back()+1;
st.push_back(i);
}
st.clear();
for (int i=n;i>=1;--i){
while (!st.empty()&&a[st.back()]<=a[i])st.pop_back();
r[i]=st.empty()?n:st.back()-1;
st.push_back(i);
}
for (int i=1;i<=n;++i)ans+=1LL*a[i]*(i-l[i]+1)*(r[i]-i+1);
st.clear();
for (int i=1;i<=n;++i){
while (!st.empty()&&a[st.back()]>a[i])st.pop_back();
l[i]=st.empty()?1:st.back()+1;
st.push_back(i);
}
st.clear();
for (int i=n;i>=1;--i){
while (!st.empty()&&a[st.back()]>=a[i])st.pop_back();
r[i]=st.empty()?n:st.back()-1;
st.push_back(i);
}
for (int i=1;i<=n;++i)ans-=1LL*a[i]*(i-l[i]+1)*(r[i]-i+1);
cout<<ans;
}
T2
题意
一个长度为 \(n\) 的价值序列 \(a\),选出一个子序列,不能有连着的 \(k\) 个,问最后能得到的最大价值是多少。
思路
考虑 DP,定义 \(f_{i}\) 表示前 \(i\) 个你能得到的最大价值,两个情况,\(i\) 不选和 \(i\) 要选,先说选的,贪心的来讲 \(a\) 没有负数,你肯定是要尽可能多选的,但是由于题目限制必须要有一个断点,我们枚举这个断点 \(j\),表示不选 \(j\) 你得到的最大价值,则有转移式 \(f_i=\max f_{j-1}+sum_{i}-sum{j+1-1}=\max f_{j-1}+sum{i}-sum_{j}\),依旧提取定值,\(f_i=\max\{f_{j-1}-sum_{j}\}+sum_{i}\),由于 \(j\) 只能在 \([i-1,i-k-1]\),我们单调队列维护这个值即可。
代码
#include<bits/stdc++.h>
using namespace std;
long long a[100005],f[100005],sum[100005];
long long w(int x){
if(x==0)return 0;
return f[x-1]-sum[x];
}
int main(){
int n,k;cin>>n>>k;
for(int i=1;i<=n;++i)cin>>a[i];
for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
deque<int>st;
st.push_back(0);
for(int i=1;i<=n;++i){
while(!st.empty()&&w(st.back())<=w(i))st.pop_back();
st.push_back(i);
while(!st.empty()&&st.front()<i-k)st.pop_front();
f[i]=max(f[i-1],sum[i]+w(st.front()));
}
cout<<f[n]<<'\n';
}
//f[i]=sum[i]-sum[j]+f[j-1]
T3
开车旅行,不想写了
T4
思路
先考虑小的问题。如果 \(2\times 2\) 的显然是简单的,直接不放公主那里就可以了,我们考虑扩展到 \(4 \times 4\),我们先按照上面的方法处理一个 \(2\times 2\),然后剩下一个 L,我们把中间放上一个,这样每个 \(2\times 2\) 都会缺一个,直接补上即可。分治解决。
代码
毒瘤代码。
#include<bits/stdc++.h>
using namespace std;
int k,x,y,n;
void dfs(int l,int r,int u,int d,int x0,int y0){
if(l==r||u==d)return;
int mx=(l+r)/2,my=(u+d)/2;
if(x0<=mx&&y0<=my){
cout<<mx+1<<' '<<my+1<<" 1\n";
dfs(l,mx,u,my,x0,y0);
dfs(mx+1,r,u,my,mx+1,my);
dfs(l,mx,my+1,d,mx,my+1);
dfs(mx+1,r,my+1,d,mx+1,my+1);
}
else if(x0<=mx&&y0>my){
cout<<mx+1<<' '<<my<<" 2\n";
dfs(l,mx,u,my,mx,my);
dfs(mx+1,r,u,my,mx+1,my);
dfs(l,mx,my+1,d,x0,y0);
dfs(mx+1,r,my+1,d,mx+1,my+1);
}
else if(x0>mx&&y0<=my){
cout<<mx<<' '<<my+1<<" 3\n";
dfs(l,mx,u,my,mx,my);
dfs(mx+1,r,u,my,x0,y0);
dfs(l,mx,my+1,d,mx,my+1);
dfs(mx+1,r,my+1,d,mx+1,my+1);
}
else{
cout<<mx<<' '<<my<<" 4\n";
dfs(l,mx,u,my,mx,my);
dfs(mx+1,r,u,my,mx+1,my);
dfs(l,mx,my+1,d,mx,my+1);
dfs(mx+1,r,my+1,d,x0,y0);
}
}
int main(){
cin>>k>>x>>y;
n=1<<k;
dfs(1,n,1,n,x,y);
}
T5
思路
论文题。我们考虑最优的情况肯定出在边上有点的方案中,那么我们考虑从左往右扫一遍,嵌定第 \(i\) 个点为左端点,然后再去枚举右端点,需要注意的是,我们在遇到点时要动态更新上下界。有一个细节是要把四个角的点也要加进去并且要判空。
代码
#include<bits/stdc++.h>
using namespace std;
struct node{
int x,y;
}a[5005];
bool cmp1(node xx,node yy){
return xx.x<yy.x;
}
bool cmp2(node xx,node yy){
return xx.y<yy.y;
}
int main(){
int L,W;cin>>L>>W;
int n;cin>>n;
a[1].x=0;a[1].y=0;
a[2].x=0;a[2].y=W;
a[3].x=L;a[3].y=0;
a[4].x=L;a[4].y=W;
for (int i=5;i<=n+4;++i){
cin>>a[i].x>>a[i].y;
}
n+=4;
sort(a+1,a+n+1,cmp1);
int ans=0;
for (int i=1;i<=n;++i){
int l=0,r=W;
for (int j=i+1;j<=n;++j){
if(a[j].x==a[i].x)continue;
ans=max(ans,(a[j].x-a[i].x)*(r-l));
if(a[j].y<a[i].y)l=max(l,a[j].y);
else if(a[j].y>a[i].y)r=min(r,a[j].y);
else break;
}
}
for (int i=n;i>=1;--i){
int l=0,r=W;
for (int j=i-1;j>=1;--j){
if(a[j].x==a[i].x)continue;
ans=max(ans,(a[i].x-a[j].x)*(r-l));
if(a[j].y<a[i].y)l=max(l,a[j].y);
else if(a[j].y>a[i].y)r=min(r,a[j].y);
else break;
}
}
sort(a+1,a+n+1,cmp2);
for(int i=1;i<=n-1;++i){
ans=max(ans,L*(a[i+1].y-a[i].y));
}
cout<<ans;
}
T6
题意
给定一棵依次插入元素的 BST,最小化 BST 插入元素序列的字典序。
思路
考虑你的 BST 是以时间戳来作为小根堆性质的 treap,这题的精髓就在于,treap 和笛卡尔树是以值域为小根堆性质的树,那么我们交换时间戳和值,建笛卡尔树即可。
T7
思路
卡了 \(n\) 年的题目。。。
我们看到数据范围能猜出是倍增,但是不知带怎样划分这个倍增的轮数,考虑我们把如下刻画为一轮:从 \(i\) 起跳,回到 \(i\) 再跳,直到离开 \(i\) 这个跳台。定义 \(f_{i,j}\) 表示从 \(i\) 起跳,跳 \(2^j\) 所到的跳台,这个东西是可以倍增处理的,同时再维护这些所需要的时间 \(g_{i,j}\)。
考虑如何处理 \(f_{i,0}\) 和 \(g_{i,0}\),发现一轮你来来回回跳是不会跳出 \(\log n\) 次的,模拟就行了,可以维护当前跳出的距离,然后判断是否到一下个跳台,最后二分。
接着是答案的查询,先找到距离起始点最近的跳台 \(y\),然后判掉一些边界,跳一下,然后把剩下的时间都走了就行,太细节了吓哭了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5,INF=4e18;
int f[N][25],g[N][25],n,x[N],p[N];
int query(int s,int t){
int y=upper_bound(x+1,x+n+1,s)-x-1;
if(y<1||x[y]<=s-t)return s-t;
t-=s-x[y];
for(int j=20;j>=0;--j)if(t>=g[y][j])t-=g[y][j],y=f[y][j];
if(!t)return x[y];
int dis=p[y];
while(dis+1<t)t-=dis+1,dis*=2;
return x[y]+dis-(t-1);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;++i)cin>>x[i]>>p[i];
x[n+1]=INF,f[n][0]=n,g[n][0]=INF;
for(int i=1;i<n;++i){
int dis=p[i];
g[i][0]=0;
while(dis<x[i+1]-x[i])g[i][0]+=dis+1,dis<<=1;
f[i][0]=upper_bound(x+1,x+n+2,x[i]+dis)-x-1;
g[i][0]+=1+x[i]+dis-x[f[i][0]];
}
for(int j=1;j<=20;++j)for(int i=1;i<=n;++i){
f[i][j]=f[f[i][j-1]][j-1];
g[i][j]=min(INF,g[i][j-1]+g[f[i][j-1]][j-1]);
}
int q;cin>>q;
while(q--){
int s,t;cin>>s>>t;
cout<<query(s,t)<<'\n';
}
return 0;
}
T8
思路
枚举每个活动,考虑后面是否有比其更有价值的活动。
- 有,我们就刚好留下 \(E\) 的能量(算上后面新加的),剩下的在这个活动用掉。
- 没有,全在这里用了就行。
可以单调栈处理。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int v[100005],l[100005];
signed main(){
int T;cin>>T;
for(int cas=1;cas<=T;++cas){
int e,r,n;cin>>e>>r>>n;
int ee=e;
long long tot=0;
for(int i=1;i<=n;++i)cin>>v[i];
vector<int>st;
for(int i=n;i>=1;--i){
while(!st.empty()&&v[st.back()]<=v[i])st.pop_back();
if(!st.empty())l[i]=st.back();
else l[i]=0;
st.push_back(i);
}
for(int i=1;i<=n;++i){
int ne;
if(!l[i]){
tot+=1ll*v[i]*e;
e=0;
}
else{
int j=l[i];
if(e+1ll*r*(j-i)>ee){
int use=e+1ll*r*(j-i)-ee;
if(use>e)use=e;
tot+=1ll*v[i]*use;
e-=use;
}
}
e=min(e+r,ee);
}
cout<<"Case #"<<cas<<": "<<tot<<'\n';
}
}
T9
不会咕着吧。

浙公网安备 33010602011771号