2021.2 月赛补题A-C
2021.2 月赛补题A-C
我一直觉得EOJ的题解写的可以,所以思路和题解基本参考Eoj官方题解。
连接:https://acm.ecnu.edu.cn/blog/entry/1089/
这场真不愧是oi爷出题,我还没摸键盘,只是在地铁上看了看A,知道是个lca,没有想出来dfs序,就开始自闭了。
但我觉得这场题目很好,值得一写。
A.
/*
首先要知道两个结论:
1.就是n个点的lca就是dfs序差最大的一组数的lca,所以先要对这5个点进行dfs序排序,求出5个点的lca
2.其余的点u对已经有的点合并:u对已存在的点求lca,深度最深的lca就是离这个点u最近的公共节点
复杂度O(10qlogn)
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define debug(x) cout<<#x<<':'<<x<<endl;
#define u first
#define w second
const int maxn=5e4+60;
int n,q,u,v,f,cnt;
int father[maxn][32],cost[maxn][32],depth[maxn],a[6],dis[maxn],ls[maxn],rs[maxn];
vector<pair<int,int>>G[maxn];
void dfs(int root,int b){
father[root][0]=b;ls[root]=++cnt;
depth[root]=depth[father[root][0]]+1;
for(int i=1;i<32;i++){
father[root][i]=father[father[root][i-1]][i-1];
}
int sz=G[root].size();
for(int i=0;i<sz;i++){
if(G[root][i].u==b) continue;
dis[G[root][i].u]=dis[root]+G[root][i].w;
dfs(G[root][i].u,root);
}
rs[root]=cnt;
}
int lca(int x,int y){
if(depth[x]<depth[y]) swap(x,y);
for(int i=31;i>=0;i--){//无限逼近接近二分,把x,y调整到同一深度
if(depth[father[x][i]]>=depth[y])x=father[x][i];
}
if(x==y) return x;
for(int i=31;i>=0;i--){
if(father[x][i]!=father[y][i]){
x=father[x][i];
y=father[y][i];
}
}
return father[x][0];
}
bool cmp(int a,int b){
return ls[a]<ls[b];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) G[i].clear();
for(int i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&f);u++,v++;//确保节点数从1开始
G[v].push_back({u,f}); G[u].push_back({v,f});
}
dfs(1,0);
scanf("%d",&q);
for(int i=1;i<=q;i++){
for(int j=1;j<=5;j++)
scanf("%d",&a[j]),a[j]++;
sort(a+1,a+6,cmp);
int qwq=lca(a[1],a[5]);
int ans=dis[a[1]]+dis[a[5]]-2*dis[qwq];
swap(a[2],a[5]);
for(int j=3;j<=5;j++){//并到之前的点上
int bst_lca=0;
for(int k=1;k<j;++k){
int now_lca=lca(a[j],a[k]);
if(bst_lca==0||depth[now_lca]>depth[bst_lca]){//找一个深度最深的lca
bst_lca=now_lca;
}
}
ans+=dis[a[j]]-dis[bst_lca];
}
printf("%d\n",ans);
}
return 0;
}
B.
/*
对每个人起终点bfs求单源最短路,然后算通过每点的概率
pre数组表示到该点最短路的数量,1表示从起点出发,0表示从终点出发
复杂度O(n*k)
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+100;
const int inf =0x3f3f3f3f;
double sum[maxn];
vector<int>G[maxn];
int n,m,u,v,k;
int a[maxn],b[maxn],pre[maxn][2],d[maxn][2],vis[maxn];
#define debug(x) cout<<#x<<':'<<x<<endl;
void bfs(int s,int f){
queue<int>q;
for(int i=1;i<=n;i++) vis[i]=0,d[i][f]=inf;
q.push(s);d[s][f]=0;pre[s][f]=1;
while(!q.empty()){
int u=q.front();
q.pop();
if(vis[u]) continue;
else vis[u]=1;
for(auto v: G[u]){
if(vis[v]) continue;
if(d[v][f]>d[u][f]+1){
d[v][f]=d[u][f]+1;
pre[v][f]=pre[u][f];
q.push(v);
}else if(d[v][f]==d[u][f]+1){
pre[v][f]+=pre[u][f];
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
while(m--){
scanf("%d%d",&u,&v);u++,v++;
G[u].push_back(v);
G[v].push_back(u);
}
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d%d",&a[i],&b[i]);a[i]++,b[i]++;
for(int i=1;i<=n;i++) pre[i][1]=pre[i][0];
bfs(a[i],1),bfs(b[i],0);
for(int j=1;j<=n;j++){
if(j==a[i]||j==b[i]) sum[j]+=1;//起点和终点的概率是100%
/*经过该点的概率=起点到该点的方法数*该点到终点的方法数/起点到终点的方法数*/
else if(d[j][1]+d[j][0]==d[b[i]][1]) sum[j]+=1.0*pre[j][1]*pre[j][0]/pre[b[i]][1];
}
}
int ans=1;
double dmax=0;
for(int i=1;i<=n;i++){
if(sum[i]>dmax)dmax=sum[i],ans=i;
}
printf("%d",ans-1);
}
C.
/*
这题我一开始就觉得是一道lazy线段树,但以下的代码是根据EOJ说的题解补的,我觉得这个写法更简单
核心思想是:牢牢把握住每段区间最小的值,也就意味着对于当前这个区间[mini[w].first,mini[w].second]必然有i,
而绝没有比它小的数,那就意味着可以随意取这个区间之内的一个位置来放这个值
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+100;
int ans[maxn],vis[maxn],n,q;
//#define debug(x) cout<<#x<<':'<<x<<endl;
pair<int,int>mini[maxn],maxi[maxn];
void deal(int n) {
for (int i = 1; i <= n; ++i) cout << -1 << ' ';
cout << endl;
exit(0);//这个写法很好,很干净,值得借鉴
}
int main(){
set<int>s;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
mini[i]={1,n},maxi[i]={n,1};
s.insert(i);
}
while(q--){
int l,r,w;
scanf("%d%d%d",&l,&r,&w);l++,r++,w++;
mini[w].first=max(mini[w].first,l);
mini[w].second=min(mini[w].second,r);
maxi[w].first=min(maxi[w].first,l);
maxi[w].second=max(maxi[w].second,r);
vis[w]=1;
}
queue<int>Q;
for(int i=n;i>=1;i--){//枚举值找位置
if(!vis[i])Q.push(i);
else{
/* 关键在于 ,[mini[w].first,min[w].second]区间里绝不可能有比他更小的值*/
auto to=s.upper_bound(mini[i].second);--to;
//if (to == s.end() or *to != mini[i].second) --to;//没找到
if (*to < mini[i].first)deal(n);//在set中找不到符合要求的位置
ans[*to] = i;//否则这个位置是值,因为可以随意指定,
s.erase(to);
int l = maxi[i].first, r = maxi[i].second;//对于这个区间中剩下的位置把比当前的数大的数安排进去
auto st=s.lower_bound(l),ed = s.upper_bound(r);
/* 这里值得注意的是,这些位置的数必然是大于当前的i,如果位置多于值,就说明构造不出来*/
for (auto it = st; it != ed; ++it) {//枚举这个位置区间,把值安排进这个区间里
if ((int)Q.size()==0) deal(n);
ans[*it] = Q.front(), Q.pop();
}
s.erase(st, ed);
}
}
for (auto to : s) {//把Q中剩余的数安排去处//非常容易忽视的细节,一定要写
if ((int)Q.size()==0){ deal(n);}
ans[to] = Q.front();Q.pop();
}
for (int i = 1; i <= n; ++i) {
cout << ans[i] - 1 << " ";
}
//system("pause");
return 0;
}