【MX-S7】梦熊 NOIP 2024 模拟赛 3 & SMOI Round 2(同步赛)
【MX-S7】梦熊 NOIP 2024 模拟赛 3 & SMOI Round 2(同步赛)
\(T1\) luogu P11323 【MX-S7-T1】「SMOI-R2」Happy Card \(20pts\)
-
发现可以把「炸弹」也看做「三带一」。
-
先使用「三带一」带走原用于出「单牌」的牌,若「三带一」还有剩余则尝试带走原用于出「对子」的牌,否则直接出「单牌」和「对子」。
点击查看代码
ll a[300010],r[4]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll t,n,ans,cnt,i,j; scanf("%lld",&t); for(j=1;j<=t;j++) { ans=cnt=0; memset(r,0,sizeof(r)); scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); r[a[i]%3]++; cnt+=a[i]/3; } if(cnt>=r[1]) { cnt-=r[1]; ans+=r[1]; if(cnt>=2*r[2]) { cnt-=2*r[2]; ans+=2*r[2]; ans+=cnt/4*3+((cnt%4==3)?3:(2*(cnt%4!=0))); } else { r[2]-=cnt/2; ans+=cnt; ans+=r[2]; } } else { r[1]-=cnt; ans+=cnt+r[1]+r[2]; } printf("%lld\n",ans); } return 0; }
\(T2\) luogu P11324 【MX-S7-T2】「SMOI-R2」Speaker \(40pts\)
-
部分分
- \(40pts\)
- 设 \(h_{i}\) 表示从 \(i\) 出发不经过 \(x \to y\) 路径上的其他点所能到达的点的最大贡献 \(c-2dis\) 。则等价于询问 \(c_{x}+c_{y}-dis_{x,y}+\max\limits_{z \in (x \to y)} \{ h_{z} \}\) 。
点击查看代码
// #define Isaac namespace IO{ #ifdef Isaac FILE*Fin(fopen("in.in","r")),*Fout(fopen("out.out","w")); #else FILE*Fin(stdin),*Fout(stdout); #endif class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin); class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout); } #define cin IO::qcin #define cout IO::qcout struct node { int nxt,to,w; }e[400010]; int head[400010],fa[400010],siz[400010],dep[400010],son[400010],top[400010],cnt=0; ll dis[400010],c[400010],maxx; bool vis[400010]; queue<int>q; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dis[e[i].to]=dis[x]+e[i].w; dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } void dfs(int x,int fa,ll dis) { maxx=max(maxx,-2*dis+c[x]); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { dfs(e[i].to,x,dis+e[i].w); } } } ll query(int u,int v,int n) { int rt=lca(u,v); ll ans=c[u]+c[v]-(dis[u]+dis[v]-2*dis[rt]); for(int i=1;i<=n;i++) { vis[i]=0; } maxx=0; vis[rt]=1; q.push(rt); for(;u!=rt;u=fa[u]) { vis[u]=1; q.push(u); } for(;v!=rt;v=fa[v]) { vis[v]=1; q.push(v); } while(q.empty()==0) { dfs(q.front(),0,0); q.pop(); } return ans+maxx; } int main() { int n,m,u,v,w,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs1(1,0); dfs2(1,1); for(i=1;i<=m;i++) { cin>>u>>v; cout<<query(u,v,n)<<endl; } return 0; }
- \(40pts\)
-
正解
- 发现“不经过 \(x \to y\) 路径上的其他点”这个限制没什么用,去掉后并不会对答案产生影响,推导过程同 luogu P2491 [SDOI2011] 消防 | luogu P1099 [NOIP2007 提高组] 树网的核 。
- 现在需要快捷地求出 \(h_{i}\) 表示从 \(i\) 出发所能到达的点的最大贡献 \(c-2dis\) ,赛时以为会和 luogu P6419 [COCI2014-2015#1] Kamp 一样换根分讨最长链、次长链的多种更新情况遂没写,但实际上因为当固定一些东西后,只有距离这一个变量(其实是距离的差值)所以还算比较好写。
- 设 \(f_{x}\) 表示从 \(x\) 出发到达以 \(x\) 为根的子树内的节点的最大贡献,状态转移方程为 \(f_{x}=\max(c_{x},\max\limits_{y \in Son(x)}\{ f_{y}-2w_{x \to y} \})\) ,边界为 \(g_{1}=f_{1}\) 。
- 然后进行换根,设 \(g_{x}\) 表示从 \(x\) 出发所能到达的点的最大贡献。状态转移方程为 \(g_{y}=\max(f_{y},g_{x}-2w_{x \to y}),y \in Son(x)\) 。
- 树剖加 \(ST\) 表或倍增暴力跳即可。
点击查看代码
struct node { ll nxt,to,w; }e[400010]; ll head[200010],c[200010],dis[200010],dep[200010],siz[200010],fa[200010],top[200010],son[200010],pos[200010],dfn[200010],f[200010],g[200010],cnt=0,tot=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(ll x,ll father) { f[x]=c[x]; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dis[e[i].to]=dis[x]+e[i].w; dfs1(e[i].to,x); f[x]=max(f[x],f[e[i].to]-2*e[i].w); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void reroot(ll x) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]) { g[e[i].to]=max(f[e[i].to],g[x]-2*e[i].w); reroot(e[i].to); } } } void dfs2(ll x,ll id) { top[x]=id; tot++; dfn[x]=tot; pos[tot]=x; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } struct ST { ll fmaxx[200010][25]; void init(ll n) { memset(fmaxx,-0x3f,sizeof(fmaxx)); for(ll i=1;i<=n;i++) { fmaxx[i][0]=g[pos[i]]; } for(ll j=1;j<=log2(n);j++) { for(ll i=1;i+(1<<j)-1<=n;i++) { fmaxx[i][j]=max(fmaxx[i][j-1],fmaxx[i+(1<<(j-1))][j-1]); } } } ll query(ll l,ll r) { ll t=log2(r-l+1); return max(fmaxx[l][t],fmaxx[r-(1<<t)+1][t]); } }T; ll query(ll u,ll v) { ll ans=c[u]+c[v]-dis[u]-dis[v],maxx=-0x3f3f3f3f; while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { maxx=max(maxx,T.query(dfn[top[u]],dfn[u])); u=fa[top[u]]; } else { maxx=max(maxx,T.query(dfn[top[v]],dfn[v])); v=fa[top[v]]; } } if(dep[u]<dep[v]) { maxx=max(maxx,T.query(dfn[u],dfn[v])); return ans+2*dis[u]+maxx; } else { maxx=max(maxx,T.query(dfn[v],dfn[u])); return ans+2*dis[v]+maxx; } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,u,v,w,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs1(1,0); g[1]=f[1]; reroot(1); dfs2(1,1); T.init(n); for(i=1;i<=m;i++) { cin>>u>>v; cout<<query(u,v)<<endl; } return 0; }
\(T3\) luogu P11325 【MX-S7-T3】「SMOI-R2」Monotonic Queue \(0pts\)
-
设 \(R_{i}\) 表示 \(i\) 右侧第一个比 \(a_{i}\) 大的数(若没有则为 \(n+1\) ),那么 \(c_{i}\) 能对答案产生贡献当且仅当存在一个 \(j\) 使得 \(l_{j} \le i \land R_{i} \le r_{j+1}\) 。
-
进一步观察性质,得到若 \(l_{j}=l_{j+1}\) 则 \([l_{j+1},r_{j+1}]\) 这一询问区间对答案无用,不妨直接钦定 \(l_{i}=i,r_{i} \ge i\) 。此时限制条件只剩下了 \(R_{i} \le r_{i+1}\) 。
-
不妨设 \(r_{i}'=r_{i+1} \in [i+1,n]\) ,此时就只需要考虑 \(r_{1 \sim n-1}'\) 的取值了。
-
设 \(f_{i,j}\) 表示处理到 \(i\) 时 \(r_{i}'=j\) 的最大值,状态转移方程为 \(f_{i,j}=\max\limits_{k=i}^{j}\{ f_{i-1,k} \}+[j \ge R_{i}] \times c_{i}\) 。
-
因为前面钦定 \(l_{i}=i\) 的存在,我们可以不考虑所有数,最终有 \(\max\limits_{i=1}^{n-1}\{ f_{i,i+1} \}\) 即为所求。
-
因随着 \(i\) 的增长合法的 \(j\) 会不断减少,考虑先对 \(\{ f \}\) 做前缀 \(\max\) 优化,及时清除不合法状态,然后线段树进行进一步优化。
-
当 \(c_{i} \ge 0\) 时直接区间加即可;否则区间加后还需要重新维护单调性,具体地,在 \([R_{i},n]\) 中找到第一个 \(\ge f_{i,R_{i}-1}\) 的位置 \(p\) (找不到就则为 \(n+1\)),然后用 \(f_{i,R_{i}-1}\) 覆盖 \([R_{i},p-1]\) 。
点击查看代码
const ll inf=0x7f7f7f7f7f7f7f7f; ll c[500010],a[500010],R[500010],n; stack<ll>s; struct SMT { struct SegmentTree { ll maxx,lazy,col; }tree[2000010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx); } void build(ll rt,ll l,ll r) { tree[rt].maxx=tree[rt].lazy=0; tree[rt].col=inf; if(l==r) { return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushdown(ll rt) { if(tree[rt].col!=inf) { tree[lson(rt)].maxx=tree[rson(rt)].maxx=tree[rt].col; tree[lson(rt)].col=tree[rson(rt)].col=tree[rt].col; tree[lson(rt)].lazy=tree[rson(rt)].lazy=0; tree[rt].col=inf; } if(tree[rt].lazy!=0) { tree[lson(rt)].maxx+=tree[rt].lazy; tree[rson(rt)].maxx+=tree[rt].lazy; tree[lson(rt)].lazy+=tree[rt].lazy; tree[rson(rt)].lazy+=tree[rt].lazy; tree[rt].lazy=0; } } void update1(ll rt,ll l,ll r,ll x,ll y,ll val) { if(x<=l&&r<=y) { tree[rt].maxx+=val; tree[rt].lazy+=val; return; } pushdown(rt); int mid=(l+r)/2; if(x<=mid) { update1(lson(rt),l,mid,x,y,val); } if(y>mid) { update1(rson(rt),mid+1,r,x,y,val); } pushup(rt); } void update2(ll rt,ll l,ll r,ll x,ll y,ll val) { if(x<=l&&r<=y) { tree[rt].maxx=tree[rt].col=val; tree[rt].lazy=0; return; } pushdown(rt); int mid=(l+r)/2; if(x<=mid) { update2(lson(rt),l,mid,x,y,val); } if(y>mid) { update2(rson(rt),mid+1,r,x,y,val); } pushup(rt); } ll query1(ll rt,ll l,ll r,ll pos) { if(l==r) { return tree[rt].maxx; } pushdown(rt); ll mid=(l+r)/2; if(pos<=mid) { return query1(lson(rt),l,mid,pos); } else { return query1(rson(rt),mid+1,r,pos); } } ll query2(ll rt,ll l,ll r,ll x,ll y,ll k) { if(tree[rt].maxx<k) { return n+1; } if(l==r) { return l; } pushdown(rt); ll mid=(l+r)/2,tmp; if(y<=mid) { return query2(lson(rt),l,mid,x,y,k); } if(x>mid) { return query2(rson(rt),mid+1,r,x,y,k); } tmp=query2(lson(rt),l,mid,x,y,k); return (tmp==n+1)?query2(rson(rt),mid+1,r,x,y,k):tmp; } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll ans=0,tmp,pos,i; cin>>n; for(i=1;i<=n;i++) { cin>>c[i]; } for(i=1;i<=n;i++) { cin>>a[i]; } for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]<a[i]) { s.pop(); } R[i]=(s.empty()==0)?s.top():n+1; s.push(i); } T.build(1,1,n); for(i=1;i<=n-1;i++) { T.update2(1,1,n,i,i,-inf); if(R[i]<=n) { T.update1(1,1,n,R[i],n,c[i]); if(c[i]<0) { tmp=T.query1(1,1,n,R[i]-1); pos=T.query2(1,1,n,R[i],n,tmp); if(pos-1>=R[i]) { T.update2(1,1,n,R[i],pos-1,tmp); } } ans=max(ans,T.query1(1,1,n,i+1)); } } cout<<ans<<endl; return 0; }
\(T4\) luogu P11326 【MX-S7-T4】「SMOI-R2」XA-Game \(0pts\)
- 建议去看 luogu 题解 ,不补了。
总结
- 整场都在瞪 \(T1\) 。
- 写 \(T2\) 的过程中死了两次机,以为 \(O(nq)\) 可以直接冲 \(60pts\) ,然后发现自己写的常数太大了。
- \(T3\) 赛时没看见保证 \(\{ l \},\{ r \}\) 单调的限制,遂以为给出的代码是假的。基本没有进行进一步转化题意。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18566323,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。