11月3日考试 题解 (背包+单调栈+树链剖分+贪心)
写错两个freopen,230->30……
T1 软件
原题:洛谷P1800
DP方程不难想到。设$f_{i,j}$表示前$i$个人做了第一个软件的$j$个模块的情况下最多能做多少第二个软件模块。发现直接转移复杂度太高,考虑二分答案。于是就变成了可行性问题,每次只需看在规定天数下是否能完成任务即可。
时间复杂度$O(n^3\log n)$。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int N=105; int f[N][N],a[N],b[N],n,m; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline bool check(int x) { memset(f,0,sizeof(f)); f[0][0]=1; for (int i=1;i<=n;i++) for (int j=0;j<=m;j++) for (int k=j;k>=0;k--) { int now=x-a[i]*(j-k); if (now<0) break; if (f[i-1][k]>0) f[i][j]=max(f[i][j],f[i-1][k]+now/b[i]); } return f[n][m]-1>=m; } int main() { n=read();m=read(); for (int i=1;i<=n;i++) a[i]=read(),b[i]=read(); int l=0,r=20000,ans; while(l<=r) { int mid=(l+r)>>1; if (check(mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%d",ans); return 0; }
T2 最大后缀值个数
题目大意:一棵根节点为$1$的树,点有点权,求出根节点到每个点的有向链上的后缀最大值的个数。后缀最大值指从目标节点开始到此节点其值为路径上最大值。
不难想到单调栈。但关键点在于回溯。
我们可以在单调栈上二分出它插入的节点,然后将此时栈的大小和此位置的元素用临时变量保存下来,将当前权值插入栈中,并改变栈大小;等到回溯的时候再用临时变量回溯即可。
时间复杂度$O(n\log n)$。
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e6+5; int n; int val[N],f[N],head[N],tnt=0; struct edge{ int link,v; }q[N<<1]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } void put(int x,int y){ q[++tnt].v=y; q[tnt].link=head[x]; head[x]=tnt; } int ans[N],st[N],cnt; int find(int l,int r,int v){ while(l<r){ int mid=(l+r)>>1; if(st[mid]>=v){ l=mid+1; } else r=mid; } return l; } void dfs(int s,int fa){ for(int i=head[s];i;i=q[i].link){ int v=q[i].v; if(v==fa) continue; int tp=cnt,tp2=-1,id=-1; if(val[v]>st[cnt]){ id=find(1,cnt,val[v]); tp2=st[id]; st[id]=val[v]; cnt=id; } else { id=cnt+1,tp2=st[cnt+1]; st[++cnt]=val[v]; } dfs(v,s); if(tp2!=-1&&id!=-1){st[id]=tp2;} cnt=tp; } ans[s]=cnt; } int main(){ n=read(); for(int i=2;i<=n;i++){ f[i]=read(); put(f[i],i); } for(int i=1;i<=n;i++){ val[i]=read(); } st[1]=val[1],cnt=1; dfs(1,0); for(int i=1;i<=n;i++){ printf("%d ",ans[i]); } }
T3 树
题目大意:给定一棵$n$个节点的树,有$q$次操作,分别为:1.新增一个关键点2.将所有点重置为非关键点3.询问一个点是否为关键点。关键点每秒钟会将与它相邻的非关键点置为关键点,每次操作占一秒。
根据题意,有$dep_v+dep_x-2\times dep_{lca}\leq now-tim_v$。移项,有$tim_v+dep_v-2\times dep_{lca}\leq now-dep_x$。然后我们可以把询问挂在lca上,就可以树链剖分区间修改区间查询了。
此题的套路跟[LNOI2014]LCA这道题相似,都是把修改挂在lca上然后查询。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int N=100005; const int inf=0x3f3f3f3f; int size[N],son[N],fa[N],dep[N],n,q; int dfn[N],top[N],idx[N],tot; int minn[N<<2],tag[N<<2],clr[N<<2]; int head[N],cnt; struct node{ int next,to; }edge[N*2]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to){ edge[++cnt]=(node){head[from],to}; head[from]=cnt; } inline void dfs_son(int now,int f) { size[now]=1; fa[now]=f;dep[now]=dep[f]+1; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (to==f) continue; dfs_son(to,now); size[now]+=size[to]; if (size[to]>size[son[now]]) son[now]=to; } } inline void dfs_chain(int now,int topf) { top[now]=topf; dfn[now]=++tot;idx[tot]=now; if (!son[now]) return; dfs_chain(son[now],topf); for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (dfn[to]) continue; dfs_chain(to,to); } } inline void pushdown(int x,int l,int r) { int mid=(l+r)>>1; if (clr[x]) { minn[x<<1]=minn[x<<1|1]=inf; clr[x<<1]=clr[x<<1|1]=inf; tag[x<<1]=tag[x<<1|1]=0; clr[x]=0; } if (tag[x]) { minn[x<<1]=min(minn[x<<1],tag[x]-2*dep[idx[mid]]); minn[x<<1|1]=min(minn[x<<1|1],tag[x]-2*dep[idx[r]]); if (!tag[x<<1]) tag[x<<1]=tag[x]; else tag[x<<1]=min(tag[x<<1],tag[x]); if (!tag[x<<1|1]) tag[x<<1|1]=tag[x]; else tag[x<<1|1]=min(tag[x<<1|1],tag[x]); tag[x]=0; } } inline void update(int x,int l,int r,int ql,int qr,int k) { if (ql<=l&&r<=qr) { if (k!=inf) { minn[x]=min(minn[x],k-2*dep[idx[r]]); if (!tag[x]) tag[x]=k; else tag[x]=min(tag[x],k); } else clr[x]=minn[x]=inf,tag[x]=0; return; } pushdown(x,l,r); int mid=(l+r)>>1; if (ql<=mid) update(x<<1,l,mid,ql,qr,k); if (qr>mid) update(x<<1|1,mid+1,r,ql,qr,k); minn[x]=min(minn[x<<1],minn[x<<1|1]); } inline int query(int x,int l,int r,int ql,int qr) { if (ql<=l&&r<=qr) return minn[x]; pushdown(x,l,r); int mid=(l+r)>>1; if (qr<=mid) return query(x<<1,l,mid,ql,qr); else if (ql>mid) return query(x<<1|1,mid+1,r,ql,qr); else return min(query(x<<1,l,mid,ql,qr),query(x<<1|1,mid+1,r,ql,qr)); } inline void updrange(int x,int v) { int t=dep[x]+v; while(top[x]!=1) { update(1,1,n,dfn[top[x]],dfn[x],t); x=fa[top[x]]; } update(1,1,n,1,dfn[x],t); } inline void qrange(int x,int v) { int t=v-dep[x]; while(top[x]!=1) { int tmp=query(1,1,n,dfn[top[x]],dfn[x]); if (tmp<=t){ puts("wrxcsd");return; } x=fa[top[x]]; } int tmp=query(1,1,n,1,dfn[x]); if (tmp<=t) puts("wrxcsd"); else puts("orzFsYo"); } int main() { memset(minn,0x3f,sizeof(minn)); n=read();q=read(); for (int i=1;i<n;i++) { int x=read(),y=read(); add(x,y);add(y,x); } dfs_son(1,0); dfs_chain(1,1); for (int i=1;i<=q;i++) { int opt=read(),x=read(); if (opt==1) updrange(x,i); if (opt==2) update(1,1,n,1,n,inf); if (opt==3) qrange(x,i); } return 0; }
T4 魔塔
题目大意:给定一棵$n$个节点的树,$1$为根节点。人在根节点。除根节点外每个点有怪,分别有血量,攻击,防御,蓝宝石四个属性。对于角色造成的伤害为攻击值减去防御值。当角色的血量小于等于0时就会死亡。蓝宝石可以增加相应的防御力。打过怪的地方不会再有新的怪。问人如何打怪使得最后血量最大。
设性价比为蓝宝石数量与打怪次数之比,显然我们要先取大的(因为我们要尽量提升自己防御力同时掉血尽量少)。扔到大根堆里,每次取最大的。如果暂时无法访问到(因为是树形结构,有访问的先后顺序)就跟它的父亲合并。然后把更新过后的父亲(未被访问)扔进堆里即可。
代码:
#include<queue> #include<cstdio> #include<iostream> #define int long long #define ll long long using namespace std; const int N=100005; priority_queue< pair<double,int> > q; int fa[N],f[N],n; ll Hp[N],Ad[N],Def[N],Num[N],tim[N]; bool vis[N]; int head[N],cnt; struct node{ int next,to; }edge[N*2]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to){ edge[++cnt]=(node){head[from],to}; head[from]=cnt; } inline void dfs(int now,int father) { for (int i=head[now];i;i=edge[i].next) if (edge[i].to!=father){ fa[edge[i].to]=now; dfs(edge[i].to,now); } } inline int find(int x) { if (f[x]==x) return x; return f[x]=find(f[x]); } signed main() { n=read(); for (int i=1;i<n;i++){ int x=read(),y=read(); add(x,y);add(y,x); } Hp[1]=read(),Ad[1]=read(),Def[1]=read();f[1]=1; for (int i=2;i<=n;i++) { Hp[i]=read(),Ad[i]=read(),Def[i]=read();Num[i]=read();f[i]=i; tim[i]=Hp[i]/(Ad[1]-Def[i]); if (Hp[i]%(Ad[1]-Def[i])==0) tim[i]--; Hp[1]-=(Ad[i]-Def[1])*tim[i]; q.push(make_pair(1.0*Num[i]/tim[i],i)); } dfs(1,0); vis[1]=1; while (!q.empty()) { int now=q.top().second; q.pop(); if (vis[now]) continue; vis[now]=1; int X=find(fa[now]); Hp[1]+=tim[now]*Num[X]; tim[X]+=tim[now]; Num[X]+=Num[now]; if (!vis[X]) q.push(make_pair(1.0*Num[X]/tim[X],X)); f[now]=X; } printf("%lld",Hp[1]); return 0; }