2021.10.19 CSP 模擬賽 總結
阿新 • • 發佈:2021-10-20
T1
題意: \(n\) 個人摘蘋果,跳起高度為 \(a_i\),蘋果高度為 \(h_i\),高度小的先摘,摘了就沒了
直接排序+雙指標,複雜度 \(O(n+m)\)
T2
題意:要轟炸一個有向圖的所有點,如果存在兩個不同點 \(i\) 和 \(j\) ,並且 \(i\)、\(j\) 連通
那麼 \(i\) 和 \(j\) 不能在同一次轟炸,問最少要多少次轟炸
tarjan 縮點 + 找最長路。考試時沒有想到要最長路,縮點不是很會處理重邊
複雜度 \(O(n+m)\)
#include<bits/stdc++.h> using namespace std; typedef long double LD; typedef long long LL; typedef double db; const int N=1000005; int n,m,sz[N],ans,rd[N],f[N],q[N],hd,tl; int low[N],dfn[N],clk,s[N],top,cl[N],tot; int lst1[N],nxt1[N],to1[N],cnt1; int lst2[N],nxt2[N],to2[N],cnt2; inline void Ae1(int fr,int go) { to1[++cnt1]=go,nxt1[cnt1]=lst1[fr],lst1[fr]=cnt1; } inline void Ae2(int fr,int go) { to2[++cnt2]=go,nxt2[cnt2]=lst2[fr],lst2[fr]=cnt2; } void tarjan(int u) { dfn[u]=low[u]=++clk,s[++top]=u; for(int i=lst1[u],v;i;i=nxt1[i]) { if(!dfn[v=to1[i]]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(!cl[v]) { low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]) { ++tot,sz[tot]=1; while(s[top]!=u) ++sz[tot],cl[s[top]]=tot,--top; cl[u]=tot,--top; } } int main() { // freopen("bomb.in","r",stdin); // freopen("bomb.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1,u,v;i<=m;i++) { scanf("%d%d",&u,&v); Ae1(u,v); } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=n;i++) for(int j=lst1[i];j;j=nxt1[j]) if(cl[i]!=cl[to1[j]]) Ae2(cl[i],cl[to1[j]]),++rd[cl[to1[j]]]; for(int i=1;i<=tot;i++)if(!rd[i])q[++tl]=i,f[i]=sz[i]; for(int u;hd<tl;) { u=q[++hd],ans=max(ans,f[u]); for(int i=lst2[u],v;i;i=nxt2[i]) { --rd[v=to2[i]]; f[v]=max(f[v],f[u]+sz[v]); if(!rd[v])q[++tl]=v; } } printf("%d",ans); }
T3
題意:一個無向圖,一條路徑長度是所有邊權的最大值,兩點距離是兩點路徑長度的最小值
操作 1 是加邊,操作 2 是給 \(i,j,p,q\) ,算出 \(d1=dis(i,j),d2=dis(p,q)\) ,取出個數為 \(d1,d2\) 的兩堆石子玩 \(\text{NIM}\) 博弈
然後問每次操作 2 誰贏
顯然的 Kruskal + LCA ,查詢距離解決
博弈的話直接異或即可
操作 1 個數 \(\le5000\)
所以直接暴力加邊,重跑一遍生成樹即可
複雜度 \(O(5000n+T\log n)\)
\(n\le5000\) ,卡過
#include<bits/stdc++.h> using namespace std; typedef long double LD; typedef long long LL; typedef double db; const int N=5005,M=105005; int n,m,T,ff[N],lst[N],nxt[N<<1],to[N<<1],cnt,dep[N],fa[N][15]; LL qz[N<<1],mx[N][15],Ra,Rb; char op[5]; struct Ed { int u,v; LL w; }e[M]; inline bool cmp(Ed A,Ed B) { return A.w<B.w; } int fd(int x) { return ff[x]==x?x:ff[x]=fd(ff[x]); } inline void Ae(int fr,int go,LL vl) { to[++cnt]=go,qz[cnt]=vl; nxt[cnt]=lst[fr],lst[fr]=cnt; } void dfs(int u,int f) { dep[u]=dep[f]+1,fa[u][0]=f; for(int i=1;i<=13;i++) { fa[u][i]=fa[fa[u][i-1]][i-1]; mx[u][i]=max(mx[u][i-1],mx[fa[u][i-1]][i-1]); } for(int i=lst[u],v;i;i=nxt[i]) if((v=to[i])^f) mx[v][0]=qz[i],dfs(v,u); } inline void kru() { cnt=1; for(int i=1;i<=n;i++) lst[i]=0,ff[i]=i,dep[i]=0; for(int i=1,tt=0,p,q;i<=m;i++) { p=fd(e[i].u),q=fd(e[i].v); if(p==q)continue; ff[q]=p,++tt; Ae(e[i].u,e[i].v,e[i].w); Ae(e[i].v,e[i].u,e[i].w); if(tt==n-1)break; } dfs(1,1); } inline LL LCA(int x,int y) { if(dep[x]<dep[y])x^=y^=x^=y; register LL res=0; for(int i=13;~i;i--) if(dep[fa[x][i]]>=dep[y]) res=max(res,mx[x][i]),x=fa[x][i]; if(x==y)return res; for(int i=13;~i;i--) if(fa[x][i]!=fa[y][i]) { res=max(res,max(mx[x][i],mx[y][i])); x=fa[x][i],y=fa[y][i]; } return max(res,max(mx[x][0],mx[y][0])); } int main() { // freopen("game.in","r",stdin); // freopen("game.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%lld",&e[i].u,&e[i].v,&e[i].w); } sort(e+1,e+m+1,cmp); kru(); scanf("%d",&T); for(int a,b,c,d;T--;) { scanf("%s",op); if(op[0]=='a') { ++m; scanf("%d%d%lld",&e[m].u,&e[m].v,&e[m].w); for(int i=m;i>1;i--) { if(cmp(e[i],e[i-1])) swap(e[i],e[i-1]); else break; } kru(); } else { scanf("%d%d%d%d",&a,&b,&c,&d); Ra=LCA(a,b); Rb=LCA(c,d); // printf("%lld %lld\n",Ra,Rb); if(Ra!=Rb)puts("madoka"); else puts("Baozika"); } } }
T4
題意:一個序列的美觀度 = 序列的數的和 / 序列被劃分成的極長單調區間的個數(第一個區間必須單調遞增)
求一個序列的所有子序列中美觀度的最大值
重要性質:所選要麼是一個單調上升序列,要麼是一個上升序列和一個下降序列
因為這兩個如果選出最大值,其他都是小的,只會讓答案減小
就正著、反著分別求一次最大遞增序列,然後取最大值即可
求的話直接離散化+樹狀陣列
#include<bits/stdc++.h> using namespace std; typedef long double LD; typedef long long LL; typedef double db; const int N=100005; int n,m; LL x[N],y[N],f[N],g[N],t[N]; db ans; inline void mdy(int p,LL v) { for(;p<=m;p+=p&-p)t[p]=max(t[p],v); } inline LL ask(int p) { register LL res=0; for(;p;p-=p&-p)res=max(res,t[p]); return res; } int main() { // freopen("seq.in","r",stdin); // freopen("seq.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lld",&x[i]),y[i]=x[i]; sort(y+1,y+n+1); m=unique(y+1,y+n+1)-y-1; y[++m]=1e9; for(int i=1,v;i<=n;i++) { v=lower_bound(y+1,y+m+1,x[i])-y; f[i]=ask(v-1)+x[i]; mdy(v,f[i]); } memset(t,0,sizeof(t)); for(int i=n,v;i;i--) { v=lower_bound(y+1,y+m+1,x[i])-y; g[i]=ask(v-1)+x[i]; mdy(v,g[i]); } for(int i=1;i<=n;i++) { ans=max(ans,1.0*f[i]); ans=max(ans,1.0*(f[i]+g[i]-x[i])/2.0); } printf("%.3f",ans); }
總結
- T1:**
- T2:第一次打 tarjan 縮點,以後要多角度思考縮完後的 DAG 如何求答案
- T3:告訴我們相信暴力能騙分
- T4:考慮答案最優性要滿足什麼條件