2023ICPC杭州题解
文章目录
- M. V图(签到)
- J. 神秘树(交互)
- G. 控制贪吃蛇(最短路)
- D. 运算符优先级(构造)
- H. 甜蜜的修噶 II(概率)
- B. 节日装饰(bitset+根号分治)
- F. Top Cluster(LCA+动态维护树的直径)
题目链接
M. V图(签到)
int cmp(int x1,int y1,int x2,int y2){int x=x1*y2,y=x2*y1;if(x>y)return 1;if(x<y)return -1;return 0;
}
void solve(){int n;cin>>n;vector<int> a(n+1);int x=-1,s=0;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=2;i<n;i++){if(a[i]<a[i-1]&&a[i]<a[i+1]){x=i;break;}}int fz=0,fm=0;int ans=0,res=0;for(int i=1;i<=x;i++)s+=a[i];fz=s+a[x+1],fm=x+1;for(int i=x+2;i<=n;i++){s+=a[i];if(cmp(fz,fm,s,i)<0)fz=s,fm=i;}ans=fz,res=fm;s=0;for(int i=x-1;i<=n;i++)s+=a[i];fz=s,fm=n-x+2;int j=n-x+2;for(int i=x-2;i>=1;i--){j++;s+=a[i];if(cmp(fz,fm,s,j)<0)fz=s,fm=j;}if(cmp(ans,res,fz,fm)<0)ans=fz,res=fm;cout<<setprecision(12)<<fixed<<1.0*ans/res<<"\n";
}
J. 神秘树(交互)
首先询问 ( 1 , 2 ) ( 3 , 4 ) , . . . , ( n − 1 , n ) (1,2)(3,4),...,(n-1,n) (1,2)(3,4),...,(n−1,n)这 n 2 \frac{n}{2} 2n种,如果都不存在,那么必然不是菊花(一个菊花的中心点必然与对应的另一个点有一条边,对应着上面询问至少有一条边存在)
然后需要 3 3 3次操作内问出来
假设存在边 ( u , v ) (u,v) (u,v),因为 n ≥ 4 n \geq 4 n≥4,假设另外两个点为 a , b a,b a,b,询问 ( u , a ) , ( v , a ) (u,a),(v,a) (u,a),(v,a),这两条边只有一条存在;如果都不存在必然是链
假设存在的是 ( u , a ) (u,a) (u,a),那么询问 ( u , b ) (u,b) (u,b),如果存在就是菊花,否则为链
G. 控制贪吃蛇(最短路)
- 算出初始蛇身的每个点,蛇尾恰好离开该点的时刻
- 那么跑dijk的时候,只要到当前点的时刻大于等于蛇尾离开的时刻,就是可达的
- 统计答案用unsigned long long即可
- 复杂度 O ( n m log ( n m ) ) O(nm \log (nm)) O(nmlog(nm))
int n,m,k;
char a[N][N];
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};
void solve(){cin>>n>>m>>k;vector<vector<int>> dis(n+1,vector<int>(m+1,INF)),len(n+1,vector<int>(m+1));int sx=0,sy=0;for(int i=1;i<=k;i++){int x,y;cin>>x>>y;len[x][y]=i;if(i==1)sx=x,sy=y;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)cin>>a[i][j];}priority_queue<array<int,3>,vector<array<int,3>>,greater<>> pq;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)dis[i][j]=INF;}vector<vector<int>> vis(n+1,vector<int>(m+1));pq.push({0,sx,sy});dis[sx][sy]=0;while(!pq.empty()){auto [d,x,y]=pq.top();pq.pop();if(vis[x][y])continue;vis[x][y]=1;for(int i=0;i<4;i++){int tx=x+dx[i],ty=y+dy[i];if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty]!='#'){int dd=d+1;if(len[tx][ty])dd=max(dd,k-len[tx][ty]+1);if(dis[tx][ty]>dd){dis[tx][ty]=dd;pq.push({dis[tx][ty],tx,ty});}}}}unsigned long long ans=0;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(dis[i][j]==INF)continue;// cout<<i<<" "<<j<<" "<<dis[i][j]<<"\n";ans+=dis[i][j]*dis[i][j];}}cout<<ans<<"\n";
}
D. 运算符优先级(构造)
中间项用-1 2
构造即可
void solve(){int n;cin>>n;n*=2;vector<int> a(n+1);for(int i=2;i<n;i++){if(i&1)a[i]=-1;else a[i]=2;}a[n]=1;int s=0;for(int i=3;i<=n;i+=2)s+=a[i]*a[i+1];a[1]=-s;for(int i=1;i<=n;i++)cout<<a[i]<<" \n"[i==n];
}
H. 甜蜜的修噶 II(概率)
- 如果 a i < a b i a_i \lt a_{b_i} ai<abi事件必然发生
- 如果 a i ≤ a b i + w b i a_i \leq a_{b_i}+w_{b_i} ai≤abi+wbi事件必然不发生
- 否则设距离它最近的可能发生的点的距离为 L i L_i Li,则概率为 1 L i ! \frac{1}{L_i!} Li!1,可以看作全排列中的一个排列的概率
- 首先拓扑判环,如果有环,环内部事件都不可能发生
- 复杂度 O ( n ) O(n) O(n)
int qpow(int a,int b){int ans=1;for(;b;b>>=1){if(b&1)ans=ans*a%mod;a=a*a%mod;}return ans;
}
void solve(){int n;cin>>n;vector<int> a(n+1),b(n+1),w(n+1),dep(n+1),adj(n+1),din(n+1),fac(n+1),p(n+1,-1);fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)cin>>b[i];for(int i=1;i<=n;i++)cin>>w[i];for(int i=1;i<=n;i++){if(a[i]<a[b[i]])dep[i]=1,p[i]=1;else if(a[i]>=a[b[i]]+w[b[i]])dep[i]=p[i]=0;else{adj[i]=b[i];din[b[i]]++;}}queue<int> q;for(int i=1;i<=n;i++){if(!din[i])q.push(i);} while(!q.empty()){auto u=q.front();q.pop();int v=adj[u];if(--din[v]==0)q.push(v);}for(int i=1;i<=n;i++){if(din[i]){dep[i]=p[i]=0;}}function<void(int)> dfs=[&](int u){if(p[u]!=-1)return;int v=adj[u];if(!v)return;dfs(v);if(p[v]>0){dep[u]=dep[v]+1;p[u]=qpow(fac[dep[u]],mod-2);}else{p[u]=0;}};for(int i=1;i<=n;i++){dfs(i);}for(int i=1;i<=n;i++)cout<<(a[i]+p[i]*w[i]%mod)%mod<<" \n"[i==n];
}
B. 节日装饰(bitset+根号分治)
- 首先有一个
bitset
的暴力做法 - 对每个颜色开一个
bitset
记录对应的位置 - 然后枚举每个位置,对于每个位置,我们可以用总的
bitset
异或当前颜色的bitset
求出与当前颜色不同的位置,然后 O ( N ω ) O(\frac{N}{\omega}) O(ωN)枚举这些位置判断 - 但是这样空间复杂度是 O ( 500 3 ) O(500^3) O(5003)会爆空间
- 因为题目中每个位置颜色不一样,设 c n t i cnt_i cnti为颜色个数,则 ∑ c n t i = n \sum cnt_i=n ∑cnti=n
- 考虑根号分治,
- 对于颜色位置数小于等于 n \sqrt n n的,直接暴力
- 对于大于 n \sqrt n n的,这样的颜色数量小于 n \sqrt n n,预处理出来
- 这样时间复杂度为 O ( n ( n + n ω ) ) O(n(\sqrt n+\frac{n}{\omega})) O(n(n+ωn)),空间复杂度为 O ( n n ω ) O(\frac{n\sqrt n}{\omega}) O(ωnn)
- 在更新答案时,我们需要剪枝,每个位置只更新一次答案,
bitset
数组开在外面
const int N=250010,sq=510;
bitset<N> bs[501],all,tmp,que;
void solve(){int n,q;cin>>n>>q;vector<int> pos[n+1],id(n+1,-1),X(n+1),C(n+1);for(int i=1;i<=n;i++){cin>>X[i]>>C[i];pos[C[i]].push_back(X[i]);all.set(X[i]);}int cnt=0;int idx=0;for(int i=1;i<=n;i++){if(pos[i].size()>=sq){for(auto x:pos[i])bs[idx].set(x);id[i]=idx++;}}vector<int> ans(N+1),D(q+1);for(int i=1;i<=q;i++){cin>>D[i];que.set(D[i]);}for(int i=1;i<=n;i++){if(id[C[i]]==-1)for(auto x:pos[C[i]])all.flip(x);else all^=bs[id[C[i]]];tmp=que&(all>>X[i]);for(int j=tmp._Find_first();j<N;j=tmp._Find_next(j)){ans[j]=i;que.reset(j);}if(id[C[i]]==-1)for(auto x:pos[C[i]])all.flip(x);else all^=bs[id[C[i]]];}for(int i=1;i<=q;i++){cout<<ans[D[i]]<<"\n";}
}
F. Top Cluster(LCA+动态维护树的直径)
- 看到mex想到二分答案,可以先对w排个序,可以预处理出前缀mex,二分出来的答案就是当前前缀的mex,check函数转化为求[1-mid]的点到达x的距离<=k
- 那么只要最大值小于等于k,一个经典的trick,一个点到达子树的最大值一定是该子树直径的其中一个端点
- 另一个经典的trick就是,假设当前直径为 u , v u,v u,v,新加入一个点 x x x,直径如果改变,只会是 u , x u,x u,x或者 v , x v,x v,x,实际上和上面的结论本质是一个道理
- 因此我们预处理出前缀直径,查询的时候check, d i s t ( u , x ) ≤ k dist(u,x) \leq k dist(u,x)≤k且 d i s t ( v , x ) ≤ k dist(v,x) \leq k dist(v,x)≤k即可
- 查询dist的时候可以用欧拉序预处理,达到 O ( 1 ) O(1) O(1)查询,减少一个log
- 复杂度 O ( ( n + q ) log n ) O((n+q)\log n) O((n+q)logn)
int dep[N],seq[N<<1],fir[N],dfc;
vector<PII> adj[N];
void dfs(int u,int fa){seq[++dfc]=dep[u];fir[u]=dfc;for(auto [v,c]:adj[u]){if(v==fa)continue;dep[v]=dep[u]+c;dfs(v,u);seq[++dfc]=dep[u];}
}
int st[N<<1][30];
void init(){for(int i=1;i<=dfc;i++)st[i][0]=seq[i];for(int j=1;j<30;j++){for(int i=1;i+(1<<j)-1<=dfc;i++)st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);}
}
int query(int l,int r){int k=__lg(r-l+1);return min(st[l][k],st[r-(1<<k)+1][k]);
}
int dist(int u,int v){if(fir[u]>fir[v])swap(u,v);return dep[u]+dep[v]-2*query(fir[u],fir[v]);
}
void solve(){ int n,q;cin>>n>>q;vector<PII> w(n+1);vector<int> ans(n+1);for(int i=1;i<=n;i++){cin>>w[i].F;w[i].S=i;}for(int i=1;i<n;i++){int u,v,w;cin>>u>>v>>w;adj[u].push_back({v,w});adj[v].push_back({u,w});}sort(w.begin()+1,w.end());dfs(1,0);init();vector<PII> D(n+1);map<int,int> vis;int mex=0;for(int i=1;i<=n;i++){vis[w[i].F]=i;while(vis.count(mex))mex++;ans[i]=mex;if(i==1)D[i]={w[i].S,w[i].S};else{D[i]=D[i-1];auto [x,y]=D[i];auto xx=w[i].S;int dis1=dist(x,xx),dis2=dist(y,xx),dis3=dist(x,y);if(dis1>=dis2&&dis1>=dis3)D[i]={x,xx};else if(dis2>=dis1&&dis2>=dis3)D[i]={y,xx};else D[i]={x,y};}}while(q--){int x,k;cin>>x>>k;int l=0,r=n;auto check=[&](int mid){auto [u,v]=D[mid];return dist(u,x)<=k&&dist(v,x)<=k;};while(l<r){int mid=l+r+1>>1;if(check(mid))l=mid;else r=mid-1;}cout<<ans[l]<<"\n";}
}