1.比赛链接:EOJ Monthly 2020.2
2.比赛情况:A题磕磕碰碰也算是拿了100(看错了从任意起点出发);B题图论蒟蒻选手直接把直链和树都当成树,写一个朴素LCA暴力骗38分闪人;C题直接在10!下找到所有可能的排列去构造线段树,然后判断是否满足所给定的区间骗23分闪人;D一分没得。
3.比赛总结:感觉B题没做出来对不起自己刷的PAT…
这道题我的做法是采用LCA先找两个结点的最近公共祖先,然后不断地往里面加入新的点。①很明显,第一轮两个结点需要走过的距离是 d i s t [ A ] + d i s t [ B ] − 2 ∗ d i s t [ l c a ( A , B ) ] dist[A]+dist[B]-2*dist[lca(A,B)] dist[A]+dist[B]−2∗dist[lca(A,B)]; ②但我们找到了前 i i i个结点的LCA时,考虑如何加入第 i + 1 i+1 i+1个结点。我们找前 i i i个结点的最近公共祖先结点 l c a i lca_i lcai和第 i + 1 i+1 i+1个结点的最近公共祖先 l c a i + 1 lca_{i+1} lcai+1,然后判断:(i)如果 l c a i = l c a i + 1 lca_i=lca_{i+1} lcai=lcai+1,那说明第 i − 1 i-1 i−1个结点在以 l c a i lca_i lcai结点的子树当中,我们要往前搜索所有的结点,找到与当前第 i + 1 i+1 i+1个结点的所有最近公共祖先结点中,层次最深的那一个 t m p tmp tmp,然后贡献加上 d i s t [ a [ i + 1 ] ] − d i s t [ t m p ] dist[a[i+1]]-dist[tmp] dist[a[i+1]]−dist[tmp](这样才不会造成路径的重复计数);(ii)如果 l c a i ≠ l a c i + 1 lca_i\ne lac_{i+1} lcai=laci+1,说明第 i + 1 i+1 i+1个结点不在以 l c a i lca_i lcai为根节点的子树中,直接计算路径即可。
#include
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=5e4+5;
int n,m;
struct Edge{
int to,next,weight;
}e[maxn<<1];
int head[maxn],tot=0,a[10],dis[maxn];
void addedge(int x,int y,int w)
{
e[++tot].to=y;
e[tot].weight=w;
e[tot].next=head[x];
head[x]=tot;
}
int fa[maxn][25],lg[maxn],dep[maxn];
void DFS(int now,int father,int dist)
{
fa[now][0]=father,dep[now]=dep[father]+1;
dis[now]=dist;
for(int i=1;(1<<i)<=dep[now];++i)
fa[now][i]=fa[fa[now][i-1]][i-1];
for(int i=head[now];i;i=e[i].next)
if(e[i].to!=father) DFS(e[i].to,now,dist+e[i].weight);
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) x=fa[x][lg[dep[x]-dep[y]]-1];
if(x==y) return x;
for(int k=lg[dep[x]]-1;k>=0;--k)
if(fa[x][k]!=fa[y][k]) x=fa[x][k],y=fa[y][k];
return fa[x][0];
}
void init_lg(){
for(int i=1;i<=n;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);}
int main()
{
close;cin>>n;
for(int i=0;i<n-1;++i)
{
int x,y,w;cin>>x>>y>>w;
addedge(x,y,w);addedge(y,x,w);
}
init_lg();DFS(0,-1,0);cin>>m;
while(m--)
{
for(int i=0;i<5;++i) cin>>a[i];
int A=a[0],B,minnum=0;
for(int i=1;i<=4;++i)
{
B=a[i];
int tmp=LCA(A,B);
if(tmp==A){
int now=A;
for(int j=0;j<i;++j){
int cur_num=LCA(a[i],a[j]);
if(dep[cur_num]>dep[now]) now=cur_num;
}
minnum+=dis[B]-dis[now];
}
else{
minnum+=dis[A]+dis[B]-dis[tmp]*2;
A=tmp;
}
}
cout<<minnum<<"\n";
}
}
考察最短路问题,但要求解最短路中经过结点 i i i的所有路径的条数。
两遍BFS求最短路。第一遍BFS找到起点 s s s到点 i i i的方案数,第二遍BFS找到终点 t t t到起点 i i i的方案数,相乘就是总的可能路线。注意写法(第一遍BFS会给一些不在最短路径上的结点也赋值了,第二遍就一定要保证不在最短路径上的点值一定是0)。
#include
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=5e3+100;
const double eps=1e-6;
vector<int> G[maxn];
int num1[maxn],num2[maxn],d[maxn];
double ans[maxn];
int main()
{
close;int n,m;cin>>n>>m;
for(int i=1;i<=m;++i)
{
int x,y;cin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
int k;cin>>k;
for(int i=1;i<=k;++i)
{
int x,y;cin>>x>>y;
memset(num1,0,sizeof(num1));
memset(num2,0,sizeof(num2));
memset(d,0,sizeof(d));
queue<int> q;
d[x]=0,num1[x]=1;q.push(x);
while(!q.empty()){
int cur=q.front();q.pop();
if(cur==y) break;
for(auto tmp:G[cur]){
if(num1[tmp]==0) q.push(tmp),d[tmp]=d[cur]+1;
if(d[tmp]==d[cur]+1) num1[tmp]+=num1[cur];
}
}
while(!q.empty()) q.pop();
num2[y]=1;q.push(y);
while(!q.empty()){
int cur=q.front();q.pop();
if(cur==x) break;
for(auto tmp:G[cur]){
if(d[tmp]==d[cur]-1){
if(num2[tmp]==0) q.push(tmp);
num2[tmp]+=num2[cur];
}
}
}
for(int i=0;i<n;++i) ans[i]+=1.0*num2[i]*num1[i]/num2[x];
}
int pos=0;
for(int i=1;i<n;++i) if(ans[i]-ans[pos]>=eps) pos=i;
cout<<pos;
}