拓扑AC NOIP模拟赛1
题出的不错,但是测试时限与下发时限不同,怎么会是呢?
T1
先考虑弱化版。
对于答案,一定有其中一个位置的高度被用满。
所以枚举这个位置,在考虑这个位置被占满时的最大答案。
发现只需要找出这个位置之前第一个 \(<\) 它的高度的位置和它之后第一个 \(<\) 它的高度的位置即可。
这个直接两遍单调栈就做完了。
强化版中可以将一个位置变为 \(1\sim H\) 中的任意高度。
先不使用,做一遍弱化版求出目前最大值。
如果使用的话,变成 \(H\) 一定不劣。
发现答案中还是一定有一个位置高度被用满,接着枚举这个位置。
但是由于可以将一个位置变为 \(H\) ,所以我们还需要知道上上个 \(<\) 当前高度的位置和下下个 \(<\) 当前高度的位置。
把这些查询都离线下来,扫描线+权值树状数组即可。
时间复杂度 \(O(n\log n)\) ,跑的没暴力跳快。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,inf=1e18;
int n,H,ans,h[N],w[N],s[N],st[N],top,pre1[N],pre2[N],suf1[N],suf2[N];
int cnt,c1[N],c2[N];
struct node{
int pos,id;
bool operator<(const node&x)const{
return pos<x.pos;
}
}qr[N];
int lowbit(int x){
return x&-x;
};
void add1(int x,int k){
while(x<N){
c1[x]=max(c1[x],k);
x+=lowbit(x);
}
}
int query1(int x){
int res=0;
while(x){
res=max(res,c1[x]);
x-=lowbit(x);
}
return res;
}
void add2(int x,int k){
while(x<N){
c2[x]=min(c2[x],k);
x+=lowbit(x);
}
}
int query2(int x){
int res=inf;
while(x){
res=min(res,c2[x]);
x-=lowbit(x);
}
return res;
}
bool cmp(node x,node y){
return x.pos>y.pos;
}
signed main(){
freopen("poster.in","r",stdin);
freopen("poster.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>H;
for(int i=1;i<=n;i++){
cin>>h[i]>>w[i];
ans=max(ans,H*w[i]);
s[i]=s[i-1]+w[i];
}
s[n+1]=s[n];
for(int i=1;i<=n;i++){
while(top&&h[st[top]]>=h[i]) top--;
pre1[i]=st[top];
if(pre1[i]>1) qr[++cnt]=(node){pre1[i]-1,i};
else pre2[i]=0;
st[++top]=i;
}
sort(qr+1,qr+cnt+1);
int j=0;
for(int i=1;i<=cnt;i++){
while(j<qr[i].pos){
j++;
add1(h[j],j);
}
pre2[qr[i].id]=query1(h[qr[i].id]-1);
}
top=0;
cnt=0;
st[++top]=n+1;
for(int i=n;i>=1;i--){
while(top&&h[st[top]]>=h[i]) top--;
suf1[i]=st[top];
if(suf1[i]<n) qr[++cnt]=(node){suf1[i]+1,i};
else suf2[i]=n+1;
st[++top]=i;
}
memset(c2,0x3f,sizeof c2);
sort(qr+1,qr+cnt+1,cmp);
j=n+1;
add2(1,n+1);
for(int i=1;i<=cnt;i++){
while(j>qr[i].pos){
j--;
add2(h[j],j);
}
suf2[qr[i].id]=query2(h[qr[i].id]-1);
}
for(int i=1;i<=n;i++) ans=max(ans,(s[suf1[i]-1]-s[pre1[i]])*h[i]);
for(int i=1;i<=n;i++) ans=max(ans,max((s[suf2[i]-1]-s[pre1[i]])*min(h[i],H),(s[suf1[i]-1]-s[pre2[i]])*min(h[i],H)));
cout<<ans<<'\n';
}
T2
场上想到了正解,但是由于 \(O(nq\log n)\) 跑大样例只跑了 \(1.5s\) 于是没有进一步优化,喜提 \(50pts\)。
先思考一下一个区间的最小代价到底是什么。
首先对区间排序,然后分奇偶讨论一下。
若区间长度为偶数,那就是将 \(i\) 和 \(n-i+1\) 配对后的最大的和。
若区间长度为奇数,取出最大数单独分组,剩下的按偶数的操作,最后取最大值。
现在的问题是求一个序列的所有代价 \(\leq x\) 的区间的第一个数减去最后一个数的差的和。
题目中保证了给定的序列是不降的。
发现固定左端点后,区间代价随右端点的增加而单调不降。
于是只要对于每个左端点求出第一个不合法的右端点即可。
称这个答案为 \(res\) 数组。
直接枚举左端点然后去算右端点是不好做的,怎么办?
每个区间的代价是被其中和 \(>x\) 的限制了,所以我们可以用和 \(>x\) 的组来限制每个左端点的答案。
具体的,枚举组中的左端点 \(i\),求出与它匹配的第一个不合法右端点 \(b_i\),然后可以做以下限制:
设 \(len=\min(i,n-b_i+1)\), 则对 \(i-len+1\sim i\) 的 \(res\) 分别对 \(b_i+len-1\sim b_i\) 取 \(\min\)。
注意要保证这两个位置是匹配的,即保证区间奇偶性。
我不会用数据结构维护这个东西,但是发现将所有斜线都补到 \(1\) 的位置不影响答案,而且补齐后桶排扫一遍就行了。
还需要考虑一下奇数长度区间的限制,即对所有 \(>x\) 的位置做一遍后缀 \(\min\) 即可。
前面算 \(b\) 数组可以用双指针实现。
于是在 \(O(nq)\) 内做完了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,inf=1e18;
int n,q,b[N],res[N],cnt;
int a[N],s[N],e[N<<1];
signed main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
while(q--){
int x;
cin>>x;
for(int i=1;i<=n;i++){
b[i]=0;
res[i]=n+1;
e[i]=0;
}
for(int i=n+1;i<=(n<<1);i++) e[i]=0;
cnt=0;
int j=n;
for(int i=1;i<=n;i++){
while(j&&a[i]+a[j-1]>x) j--;
if(a[i]+a[j]>x) b[i]=j;
if(j<i) b[i]=i+1;
}
for(int i=1;i<=n;i++){
if(!b[i]) continue;
if((b[i]-i+1)&1) b[i]++;
if(b[i]>n) continue;
e[b[i]+i-1]=max(e[b[i]+i-1],i);
}
j=0;
for(int i=1;i<=(n<<1);i++){
if(e[i]>j){
for(int k=j+1;k<=e[i];k++) res[k]=min(res[k],i-k+1);
j=e[i];
}
}
int tmp=inf;
for(int i=n;i>=1;i--){
if(a[i]>x) tmp=i;
res[i]=min(res[i],tmp);
}
int ans=0;
for(int i=1;i<=n;i++){
if(res[i]<=i) continue;
ans+=s[res[i]-1]-s[i-1];
ans-=(res[i]-i)*a[i];
}
cout<<ans<<'\n';
}
}
T3
目前不会
T4
目前不会

浙公网安备 33010602011771号