P1823 [COI2007] Patrik 音乐会的等待
solution
考虑一个人如果被后面的某个人挡住了,那么他肯定不能看到那个人后面的任何人,因此我们考虑用单调栈做,维护这个人的身高和能够看到的对数即可。
正常来说需要二分查找,接下来我们讨论如何去掉二分做到线性。
由于越靠近栈顶的值越小,于是我们每往后扫一个,就将其与栈顶比较,如果大于栈顶就把栈顶减一,全部下压完成之后如果栈非空,那么说明这两个人之间可以看到。
如此就可以去掉二分查找的步骤了。
这样我们可以做到线性,复杂度为 O ( n ) O(n) O(n) 。
code
//P1823 [COI2007] Patrik 音乐会的等待
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int NUM=5e5+5;
struct node
{
int x,num;
}s[NUM];
int n,top,ans;
int a[NUM];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=n;++i)
{
while(top && a[s[top].x]<a[i])
{
ans+=s[top].num;
--top;
}
if(a[s[top].x]==a[i])
{
ans+=s[top].num;
++s[top].num;
if(top>=2)
++ans;
continue;
}
if(top)
++ans;
s[++top].x=i;
s[top].num=1;
}
cout<<ans;
return 0;
}
P2107 小Z的AK计划
solution
直接选择显然不是很好做,于是我们考虑一个反悔贪心。
考虑让小Z一直往右走,能选就选,不能选的时候不断把已经经过的maxn加进时间,如果全加完之后仍不能新选,就结束过程。正确性显然,因为当我们时间不够的时候显然应该丢弃目前时间最长的才能使答案最优。
tips
x i x_i xi 不保证升序排列,要在反悔贪心之前先以 x i x_i xi 为第一关键字从小到大排序。
code
//P2107 小Z的AK计划
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int NUM=1e5+5;
struct node
{
int x,t;
}a[NUM];
priority_queue<int> q;
int n,m,ans,res,cnt=1,cur;
inline bool cmp(node xx,node yy)
{
return xx.x<yy.x;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>a[i].x>>a[i].t;
sort(a+1,a+1+n,cmp);
while(cnt<=n)
{
m-=a[cnt].x-cur;
cur=a[cnt].x;
m-=a[cnt].t;
++res;
q.push(a[cnt].t);
while(m<0 && !q.empty())
{
--res;
m+=q.top();
q.pop();
}
if(m<0)
break;
ans=max(ans,res);
++cnt;
}
cout<<ans;
return 0;
}
P2827 [NOIP2016 提高组] 蚯蚓
solution
我们发现直接用STL的优先队列维护是不可行的,只能拿到80pts。
但是观察切断蚯蚓的过程,不难发现先被切出来的小蚯蚓不会比之后切出来的小蚯蚓短。因此我们可以简略地理解为:切割后的蚯蚓依然具有单调性。
并且由于p为一个固定的比例,所以我们可以将蚯蚓分别丢进三个具有单调性的队列。q1为原队列,q2为 ⌊ p x ⌋ \lfloor px \rfloor ⌊px⌋ 队列,q3为 x − ⌊ p x ⌋ x-\lfloor px \rfloor x−⌊px⌋ 队列,每个队列都保持单调性,每次 g e t m a x getmax getmax 的时候只需要比较三队的队头即可。
线性复杂度。
tips
a i a_i ai 不保证升序排列,要先将 a i a_i ai 从大到小排序。
code
//P2827 [NOIP2016 提高组] 蚯蚓
#include<bits/stdc++.h>
using namespace std;
const int NUM=1e5+5;
const int NUMM=7e6+5;
const int INF=-1e9;
queue<int> q[4];
int n,m,qq,u,v,t,sum,maxn;
double p;
int a[NUM],top[4],ans[NUMM];
inline bool cmp(int a,int b)
{
return a>b;
}
inline void getmax()
{
maxn=INF;
fill(top+1,top+4,INF);
for(int i=1;i<=3;++i)
{
if(!q[i].empty())
top[i]=q[i].front();
maxn=max(maxn,top[i]);
}
for(int i=1;i<=3;++i)
{
if(top[i]==maxn)
{
q[i].pop();
break;
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>qq>>u>>v>>t;
p=(double)u/(double)v;
for(int i=1;i<=n;++i)
cin>>a[i];
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;++i)
q[1].push(a[i]);
for(int i=1;i<=m;++i)
{
getmax();
ans[i]=maxn+qq*(i-1);
int l1=(p*ans[i]),l2=ans[i]-l1;
q[2].push(l1-qq*i);
q[3].push(l2-qq*i);
}
for(int i=t;i<=m;i+=t)
cout<<ans[i]<<" ";
cout<<'\n';
for(int i=1;i<=n+m;++i)
{
getmax();
if(i%t==0)
cout<<maxn+m*qq<<" ";
}
return 0;
}
P2897 [USACO08JAN] Artificial Lake G
solution
刚看到这道题的时候,我认为它更像一个合并平台的问题(也的确可以这样做)。但是注意到各平台高度不同,所以不会出现两个平台同时开始计算的情况,于是我们可以考虑用线性数据结构来做。
考虑用一个单调递减的单调栈维护,每次试图向栈中加入新的平台时就将比它低的平台灌满,并且将这些低的平台的 w i d t h width width 与目前的合并,然后将灌满的平台出栈,加入当前平台即可。
code
//P2897 [USACO08JAN] Artificial Lake G
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int NUM=1e5+5;
const int INF=1e9;
struct node
{
int w,h;
}a[NUM];
stack<int> st;
int n,minn,minpos,lpos,rpos,wid,room;
int ans[NUM];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
a[0].h=a[n+1].h=INF;
minn=INF;
for(int i=1;i<=n;++i)
{
cin>>a[i].w>>a[i].h;
if(minn>a[i].h)
{
minn=a[i].h;
minpos=i;
}
}
lpos=minpos-1,rpos=minpos+1;
st.push(0);
st.push(minpos);
for(int i=1;i<=n;++i)
{
wid=0;
if(a[lpos].h<a[rpos].h)
{
while(!st.empty() && a[lpos].h>a[st.top()].h)
{
a[st.top()].w+=wid;
wid=a[st.top()].w;
ans[st.top()]=room+a[st.top()].w;
int las=st.top();
st.pop();
room+=(min(a[lpos].h,a[st.top()].h)-a[las].h)*a[las].w;
}
a[lpos].w+=wid;
st.push(lpos);
--lpos;
}
else
{
while(!st.empty() && a[rpos].h>a[st.top()].h)
{
a[st.top()].w+=wid;
wid=a[st.top()].w;
ans[st.top()]=room+a[st.top()].w;
int las=st.top();
st.pop();
room+=(min(a[rpos].h,a[st.top()].h)-a[las].h)*a[las].w;
}
a[rpos].w+=wid;
st.push(rpos);
++rpos;
}
}
for(int i=1;i<=n;++i)
cout<<ans[i]<<'\n';
return 0;
}
P4829 kry loves 2048
solution
题目并不难,只需要排序数组,开个队列 q q q ,向 q q q 中 p u s h push push 目前的 m a x n maxn maxn 。持续在数组中选数,当数组中没有更大的数可选时,用 q q q 中数直到数组空。
不难想到 q q q 中剩余的一个数就是答案。
tips
笔者的代码是开 O 2 O2 O2 的,因为本题卡排序方式,必须要 O ( n ) O(n) O(n) 的排序方式,例如桶排,不难写但笔者认为没必要。
code
//P4829 kry loves 2048
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int NUM=1e7+5;
queue<int> q;
int n,m,seed,cnt;
int a[NUM];
void generate_array(int n, int m, int seed) {
unsigned x = seed;
for (int i = 0; i < n; ++i) {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
a[i] = x % m + 1;
}
}
inline int getnum()
{
if(q.empty())
{
++cnt;
return a[cnt-1];
}
if(a[cnt]>q.front() || cnt==n)
{
int temp=q.front();
q.pop();
return temp;
}
++cnt;
return a[cnt-1];
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>seed;
generate_array(n,m,seed);
sort(a,a+n);
for(int i=1;i<n;++i)
{
int t1=getnum(),t2=getnum();
q.push(max(t1<<1,t2));
}
cout<<q.front();
return 0;
}
P3253 [JLOI2013] 删除物品
solution
手玩的时候可以观察到,这两堆物品的相对位置始终不变,因此容易想到将堆顶接在一起,用指针在中间滑动,指向出堆的物品。
第一下从堆顶相接处到第一个出堆物品的贡献可以特判,因为并不好定义堆顶相接处的具体位置。
关于计算每次移动几步,我们考虑未出堆的物品为 1 1 1 ,已出堆的物品为 0 0 0 ,用树状数组维护区间和即可。
code
//P3253 [JLOI2013] 删除物品
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int NUM=1e5+5;
struct node
{
int num,idx;
}a[NUM];
int n1,n2,n,ans;
int c[NUM],tr[NUM<<1];
inline bool cmp(node xx,node yy)
{
return xx.num>yy.num;
}
inline int lowbit(int x)
{
return x&(-x);
}
inline void add(int x,int k)
{
while(x<=n)
{
tr[x]+=k;
x+=lowbit(x);
}
}
inline int query(int x)
{
int res=0;
while(x)
{
res+=tr[x];
x-=lowbit(x);
}
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n1>>n2;
for(int i=1;i<=n1;++i)
cin>>c[i];
int maxn=0,pos;
for(int i=n1;i;--i)
{
a[++n].num=c[i];
a[n].idx=n;
if(c[i]>maxn)
{
maxn=c[i];
pos=n;
}
}
for(int i=1;i<=n2;++i)
{
int tt;
cin>>tt;
a[++n].num=tt;
a[n].idx=n;
if(a[n].num>maxn)
{
maxn=a[n].num;
pos=n;
}
}
if(pos>n1)
ans+=pos-n1-1;
else
ans+=n1-pos;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;++i)
add(i,1);
add(a[1].idx,-1);
for(int i=2;i<=n;++i)
{
if(a[i].idx>a[i-1].idx)
ans+=query(a[i].idx-1)-query(a[i-1].idx);
else
ans+=query(a[i-1].idx-1)-query(a[i].idx);
add(a[i].idx,-1);
}
cout<<ans;
return 0;
}
P3620 [APIO/CTSC2007] 数据备份
solution
好题。
由于选的两个建筑之间如果有其他建筑肯定不会更优,因此我们先将每两个建筑之间的距离求出来,抽象成处理 n − 1 n-1 n−1 个点的问题。
注意这里 n − 1 n-1 n−1 个点的问题较原问题增加了一个限制:如果选择第 i i i 个点,则不能选择第 i + 1 i+1 i+1 和 i − 1 i-1 i−1 个点。
首先考虑最简单的贪心,每次选择值最小的点,并将相邻两点删去。这里考虑用优先队列选点,用双向链表删点。
当然,有了上面的限制条件,这个贪心显然不对,反例显然。
我们思考如何把这个贪心加上反悔:每从 q q q 中弹出一个点 x i x_i xi ,就再向 q q q 中加入一个值为 x i − 1 + x i + 1 − x i x_{i-1}+x_{i+1}-x_i xi−1+xi+1−xi 的新点即可。
code
//P3620 [APIO/CTSC2007] 数据备份
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define mpi make_pair
const int NUM=1e5+5;
struct aa
{
int p,l,r;
}a[NUM];
struct node
{
int val,idx;
bool operator < (const node b)const{
return val>b.val;
}
};
priority_queue<node> q;
int n,k,ans;
int s[NUM],vis[NUM];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;++i)
cin>>s[i];
a[0].p=a[n].p=1e9;
for(int i=2;i<=n;++i)
{
a[i-1].p=s[i]-s[i-1];
a[i-1].l=i-2;
a[i-1].r=i;
q.push(node{a[i-1].p,i-1});
}
for(int i=1;i<=k;++i)
{
while(!q.empty() && vis[q.top().idx])
q.pop();
node tp=q.top();
q.pop();
ans+=tp.val;
vis[a[tp.idx].l]=vis[a[tp.idx].r]=1;
a[tp.idx].p=a[a[tp.idx].l].p+a[a[tp.idx].r].p-a[tp.idx].p;
q.push(node{a[tp.idx].p,tp.idx});
a[tp.idx].l=a[a[tp.idx].l].l;
a[tp.idx].r=a[a[tp.idx].r].r;
a[a[tp.idx].l].r=a[a[tp.idx].r].l=tp.idx;
}
cout<<ans;
return 0;
}
P3668 [USACO17OPEN] Modern Art 2 G
solution
由于每种颜色总共都只能涂一次,所以能够很容易判断颜色相交而不包含是不合法状态。
剩下的情况只有包含和不相交,包含可以用栈处理,而不相交其实不用管,可以同时涂色所以事实上答案仅与栈的最高颜色层数有关。
代码十分简单。
tips
似乎翻译有点小问题,建议直接看英文防止理解错误。
code
//P3668 [USACO17OPEN] Modern Art 2 G
#include<bits/stdc++.h>
using namespace std;
const int NUM=1e5+5;
stack<int> s;
int n,maxn,ans;
int a[NUM],x[NUM],y[NUM],st[NUM],ed[NUM];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>a[i];
if(!a[i])
continue;
y[a[i]]=i;
if(!x[a[i]])
x[a[i]]=i;
}
for(int i=1;i<=n;++i)
{
if(x[i])
st[x[i]]=i;
if(y[i])
ed[y[i]]=i;
}
for(int i=1;i<=n;++i)
{
if(st[i])
{
s.push(st[i]);
++ans;
maxn=max(maxn,ans);
}
if(!s.empty() && s.top()!=a[i])
{
cout<<"-1";
return 0;
}
if(ed[i])
{
s.pop();
--ans;
}
}
cout<<maxn;
return 0;
}
P2278 [HNOI2003] 操作系统
solution
在线处理,每次做优先队列(第一关键字是优先级,第二关键字是到达时间)里的堆顶即可。
code
//P2278 [HNOI2003] 操作系统
#include<bits/stdc++.h>
using namespace std;
struct node
{
int idx,arr,val,pri;
bool operator < (const node &b)const{
if(pri==b.pri)
return arr>b.arr;
return pri<b.pri;
}
}a;
priority_queue<node> q;
int cur;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>a.idx && a.idx!=EOF)
{
cin>>a.arr>>a.val>>a.pri;
if(q.empty())
{
q.push(a);
cur=a.arr;
continue;
}
while(!q.empty() && cur+q.top().val<=a.arr)
{
cur+=q.top().val;
cout<<q.top().idx<<" "<<cur<<'\n';
q.pop();
}
if(!q.empty())
{
node temp=q.top();
q.pop();
temp.val-=a.arr-cur;
q.push(temp);
}
q.push(a);
cur=a.arr;
}
while(!q.empty())
{
cur+=q.top().val;
cout<<q.top().idx<<" "<<cur<<'\n';
q.pop();
}
return 0;
}
P3697 开心派对小火车
solution
显然是先坐B到一些站,然后转C,C站显然是建在下B之后A最远第一个到不了的地方,然后下C之后再转A,把A能走到最远的地方与下一个B站取 m i n min min ,丢进优先队列再算最大贡献即可。
code
//P3697 开心派对小火车
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define mpi make_pair
#define fir first
#define sec second
const int NUM=3005;
priority_queue<pii> q;
int sid,T,n,m,k,a,b,c,t,ans;
int s[NUM],d[NUM];
inline void push(int i)
{
int tim=s[i]*b+c*(d[i]-s[i]);
if(t<tim)
{
q.push(mpi(0,i));
return ;
}
int temp=d[i];
d[i]=min(s[i+1],d[i]+(t-tim)/a+1);
q.push(mpi(d[i]-temp,i));
}
signed main()
{
cin>>n>>m>>k>>a>>b>>c>>t;
ans=-1;
while(!q.empty())
q.pop();
for(int i=1;i<=m;++i)
{
cin>>s[i];
--s[i];
}
s[m+1]=n;
for(int i=1;(i<=m) && (s[i]*b<=t);++i)
{
d[i]=min(s[i]+(t-s[i]*b)/a+1,s[i+1]);
ans+=d[i]-s[i];
push(i);
}
for(int i=k-m;i>=1;--i)
{
ans+=q.top().fir;
push(q.top().sec);
q.pop();
}
cout<<ans<<'\n';
return 0;
}
P1160 队列安排
solution
本题既可以当作基础链表 STL_list 的应用,也可以用静态结构体双向链表写,记得加上一个寻找头尾的编号 0 0 0 节点即可。
code
//P1160 队列安排
#include<bits/stdc++.h>
using namespace std;
const int NUM=1e5+5;
list<int> lst;
list<int>::iterator pos[NUM];
int n,m;
int era[NUM];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
lst.push_front(1);
pos[1]=lst.begin();
for(int i=2;i<=n;++i)
{
int k,p;
cin>>k>>p;
if(!p)
pos[i]=lst.insert(pos[k],i);
else
{
auto iter=next(pos[k]);
pos[i]=lst.insert(iter,i);
}
}
cin>>m;
for(int i=1;i<=m;++i)
{
int x;
cin>>x;
if(!era[x])
{
lst.erase(pos[x]);
era[x]=1;
}
}
for(auto it=lst.begin();it!=lst.end();++it)
cout<<*it<<" ";
return 0;
}
P2168 [NOI2015] 荷马史诗
solution
不难发现题目中 s i s_i si 与 s j s_j sj 互相不为前缀,于是想到哈夫曼编码。更严谨地说,本题的要求可以抽象成构造一个 k k k 进制的哈夫曼编码。我们需要维护最短的带权路径长度和与该哈夫曼树的高度。按照哈夫曼树的构造方式,不断将当前最小的 k k k 个节点合并成为 1 1 1 个父节点,直至只有 1 1 1 个父节点。考虑我们要动态的选出最小的 k k k 个节点,这个操作我们可以使用优先队列来实现。
最后,由于我们需要保证每次合并都有 k k k 个节点,就需要加入一些空节点填充进去,且将有值的节点挤到更优的位置上。不难发现若 ( n − 1 ) % ( k − 1 ) ! = 0 (n-1)\%(k-1)!=0 (n−1)%(k−1)!=0 ,则最后一次合并不足 k k k 个。于是我们加入 k − 1 − ( n − 1 ) % ( k − 1 ) k-1-(n-1)\%(k-1) k−1−(n−1)%(k−1) 个空节点即可。
具体实现看代码。
code
//P2168 [NOI2015] 荷马史诗
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node
{
int len,hei;
};
bool operator < (node a,node b)
{
if(a.len==b.len)
return a.hei>b.hei;
return a.len>b.len;
}
priority_queue<node> q;
int n,k,cnt,maxn,temp,ans;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;++i)
{
int wi;
cin>>wi;
q.push(node{wi,1});
}
if((n-1)%(k-1))
cnt=k-1-(n-1)%(k-1);
for(int i=1;i<=cnt;++i)
q.push(node{0,1});
cnt+=n;
while(cnt>1)
{
maxn=temp=0;
for(int i=1;i<=k;++i)
{
temp+=q.top().len;
maxn=max(maxn,q.top().hei);
q.pop();
}
ans+=temp;
q.push({temp,maxn+1});
cnt-=k-1;
}
cout<<ans<<'\n'<<q.top().hei-1;
return 0;
}