【考试反思】联赛模拟测试14
震惊!我居然有这么多 To be continued(咕咕咕)
T3 耍聪明拉斯维加斯剪枝,结果设的时间直接挂掉(早知道直接 puts("-1")
打死)
奶 油 小 生 老 姚
T1:虎
贪心,和昨天 T3 类似(所以我为什么没看动动大神的题解啊 = =)
可以发现,当没有无所谓的边的时候,对于一个点,答案是(与他相连的需要修改的边+1)/2。当我们遍历到一条需要修改的边时,就以这个边开始 dfs 一条链,一直遍历标记直到遇到不可修改的边,这样就不会重复,一条链一起修改了。
(所以我 60 pts 写的各种大力分类讨论向上传递,没想到居然折磨水)
当有无所谓的边的时候,其实这种边是没什么用的,直接把用这种边相连的儿子节点和祖先节点中需要修改的边连起来即可,这个应该很好维护,用数组记一下向上的最近满足要求的祖先即可。记得一开始都初始化成自己。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int n,ans;
int link[maxn];
bool vis[maxn<<1];
struct Edge{
int from,to,w,nxt;
}e[maxn<<1];
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
int head[maxn],cnt=-1;
inline int add(int u,int v,int w){
e[++cnt].from=u;
e[cnt].to=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int u){
for(int i=head[u];~i;i=e[i].nxt){
if(vis[i]||e[i].w==1)continue;
vis[i]=vis[i^1]=1;e[i].w=1;
int v=e[i].to;
dfs(v);
return;
}
}
int main(){
#ifndef LOCAL
freopen("tiger.in","r",stdin);
freopen("tiger.out","w",stdout);
#endif
memset(head,-1,sizeof(head));
n=read();
for(int i=1;i<=n;i++)
link[i]=i;
for(int i=2;i<=n;i++){
int v=read(),w=read(),flag=read();
if(!flag){
link[i]=link[v];
}else{
add(i,link[v],w);
add(link[v],i,w);
}
}
for(int u=1;u<=n;u++){
int cnt=0;
for(int i=head[u];~i;i=e[i].nxt){
if(!vis[i]&&e[i].w==0){
vis[i]=vis[i^1]=1;
int v=e[i].to;
dfs(v);
cnt++;
}
}
ans+=(cnt+1)>>1;
}
printf("%d\n",ans);
return 0;
}
T2:陶陶摘苹果
线段树维护单调栈,好像全机房除我都会?人傻了。
震惊,线段树边界判反都能有 10 pts。
首先,这里只讲线段树维护单调严格递减栈的方法。至于本题,只要把序列反着储存就满足单调递增栈了
由于 pushup
函数的特殊性,本题适合结构体线段树。
我们维护一个区间最大值线段树,即 tree[rt]
表示以 rt
为结点的区间最大值。令 query(rt,l,r,v)
表示当前为 rt
结点,区间 \([l,r]\) 形成的单调栈在加入 \(v\) 之后的单调栈栈内个数最多还能有多少个。
我们要获得答案只需要执行 query(1,1,n,0)
即可。因为题目中的 \(h[i]\) 都大于 0,所以加入 0 不会弹出任何元素,而是直接加在最后了。
build
和 modify
函数显然。和普通的线段树一样即可。
我们先看 query
函数,分以下几种情况:
-
首先,如果当前区间还未包括询问区间,直接递归,这个后面会提及。
-
其次,如果区间最大值都小于等于 \(v\)(等不等于看严不严格),那么接着递归也没什么必要了,直接
return 0
。 -
如果已经包含:
-
如果右儿子的最大值小于等于 \(v\),那只需要递归左儿子即可。
-
否则,在加入 \(v\) 的同时,会使右区间最大值一并加入栈中。而加入这个最大值,可能会使左儿子中的一些元素弹出。所以我们需要
query(lson,l,r,tree[rson].w)+query(rson,l,r,v)
。
-
可以看出 query(lson,l,r,tree[rson].w)
与 \(v\) 无关。所以我们可以在线段树节点里另建一个值预处理出来。只要在 pushup
的时候更新一下即可。我起的名字是 uptr
。
在当前区间还未包括询问区间,向下递归的时候:
首先,我们在询问的时候,如果我们查询了右儿子,那么一定会使 tree[rson].w
加入到栈中,那么左儿子中小于该值的一定都需要弹出。所以我们必须先递归右区间。
所以,我们需要维护一个全局变量 \(Maxr\),查询右儿子的时候总是将其更新成最大的值(包括叶子节点)。这样当把右边所有节点都递归完之后,查询左儿子就只需要 query(lson,l,r,Maxr)
即可。
对于本题,只要把序列翻转之后,套个板子就可以了。时间复杂度 \(O(n\log^2 n)\)。
有其他方法可以做到一个 \(\log\),大家可以去 orz longdie
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
int n,m,Maxr;
int a[maxn];
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
#define lson (rt<<1)
#define rson (rt<<1|1)
struct Node{
int w,l,r,uptr;
}tree[maxn<<2];
int query(int rt,int l,int r,int v){
if(v>=tree[rt].w)return 0;
if(tree[rt].l==tree[rt].r){
Maxr=max(Maxr,tree[rt].w);
return 1;
}
if(l<=tree[rt].l&&tree[rt].r<=r){
if(tree[rson].w<=v)return query(lson,l,r,v);
else{
int res=query(rson,l,r,v)+tree[rt].uptr;
Maxr=max(Maxr,tree[rt].w);
return res;
}
}
int res=0;
if(r>tree[lson].r)res+=query(rson,l,r,v);//一定要先递归右儿子
if(l<=tree[lson].r)res+=query(lson,l,r,Maxr);
return res;
}
inline void pushup(int rt){
tree[rt].w=max(tree[lson].w,tree[rson].w);
tree[rt].uptr=query(lson,tree[lson].l,tree[lson].r,tree[rson].w);
}
void modify(int rt,int p,int v){
if(tree[rt].l==tree[rt].r)return tree[rt].w=v,void();
if(p<=tree[lson].r)modify(lson,p,v);
else modify(rson,p,v);
pushup(rt);
}
void build(int rt,int l,int r){
tree[rt].l=l;tree[rt].r=r;
if(l==r)return tree[rt].w=a[l],void();
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(rt);
}
int main(){
#ifndef LOCAL
freopen("taopapp.in","r",stdin);
freopen("taopapp.out","w",stdout);
#endif
n=read();m=read();
for(int i=1;i<=n;i++)
a[n-i+1]=read();
build(1,1,n);
for(register int i=1;i<=m;i++){
int p=n-read()+1,h=read();
modify(1,p,h);
Maxr=0;
printf("%d\n",query(1,1,n,0));
modify(1,p,a[p]);
}
return 0;
}
T3:开心的金明
这个题折磨资本,DarthVictor 狂怒。
神仙贪心。
\(\Huge必须吐槽本题奇怪的难以分辨的变量名\ 严重差评\)
首先把每天的电脑价格和材料价格预处理出来。
利用 multiset
,存 pair
,分别储存当天的电脑的总价格和生产量,放入 set
中。
-
售出电脑,取价格小的电脑,直至取够当天的需求量,否则无解。
-
存储电脑,能屯就屯。用一个变量记存了多少电脑,当仓库不够大时,直接把价格大的电脑扔掉。
值得注意的是每天售出电脑的成本是随时间变化的(存储也需要花费)。如何处理?
我们可以用一个变量 \(sumE\) 记录从第一天开始到今天 \(E\) 值的总和。当插入电脑的时候,插入 \(m_i-sumE\),取出的时候,价格为 \(first+sumE\)。这样差值就是储存的这几天的 \(E\) 值的总和。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e4+10;
const int INF=0x3f3f3f3f;
int K,ans;
multiset<pair<int,int> > s;
struct Node{
int c,d,m,p,e,R,E;
}a[maxn];
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
signed main(){
#ifndef LOCAL
freopen("happy.in","r",stdin);
freopen("happy.out","w",stdout);
#endif
K=read();
for(int i=1;i<=K;i++){
a[i].c=read();
a[i].d=read();
a[i].m=read();
a[i].p=read();
}
for(int i=1;i<K;i++){
a[i].e=read();
a[i].R=read();
a[i].E=read();
}
for(int i=1;i<=K;i++){
if(i>1)a[i].c=min(a[i].c,a[i-1].c+a[i-1].R);
a[i].m+=a[i].c;
}
//c:原材料价格 d:需求 p:生产量 m:电脑价格 e:仓库大小 R:储存原材料 E:储存电脑
int tot=0,sumE=0;
for(int i=1;i<=K;i++){
s.insert(make_pair(a[i].m-sumE,a[i].p));
tot+=a[i].p;
while(a[i].d){
if(s.empty())return !puts("-1");
pair<int,int> u=*s.begin();s.erase(s.begin());
tot-=u.second;
if(a[i].d<u.second){
ans+=(u.first+sumE)*a[i].d;
u.second-=a[i].d;a[i].d=0;
tot+=u.second;
s.insert(u);
}else{
ans+=(u.first+sumE)*u.second;
a[i].d-=u.second;
}
}
while(a[i].e<tot){
pair<int,int> u=*--s.end();s.erase(--s.end());
tot-=u.second;
if(tot<a[i].e){
u.second=a[i].e-tot;
tot+=u.second;
s.insert(u);
}
}
sumE+=a[i].E;
}
printf("%lld\n",ans);
return 0;
}
T4:笨小猴
思维题。(数据过水)
首先一定不会有 \(-1\)。因为即使最坏的情况所有值都相等,因为选了 \(n+1\) 个也会使选的更多。
所有物品按照 \(A\) 排序,选出每一对里 \(B\) 较大的一个,再选出最后一个。这样 \(A\) 和 \(B\) 就都大了。