1.比赛链接:EOJ Monthly 2021.2
2.比赛情况:A题磕磕碰碰也算是拿了100(看错了从任意起点出发);B题图论蒟蒻选手直接把直链和树都当成树,写一个朴素LCA暴力骗38分闪人;C题直接在10!下找到所有可能的排列去构造线段树,然后判断是否满足所给定的区间骗23分闪人;D一分没得。
3.比赛总结:感觉B题没做出来对不起自己刷的PAT…
A:昔我往矣
这道题我的做法是采用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<bits/stdc++.h>
#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";
}
}
B:杨柳依依
考察最短路问题,但要求解最短路中经过结点
i
i
i的所有路径的条数。
两遍BFS求最短路。第一遍BFS找到起点
s
s
s到点
i
i
i的方案数,第二遍BFS找到终点
t
t
t到起点
i
i
i的方案数,相乘就是总的可能路线。注意写法(第一遍BFS会给一些不在最短路径上的结点也赋值了,第二遍就一定要保证不在最短路径上的点值一定是0)。
#include<bits/stdc++.h>
#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;
}
C:今我来思
参考提交记录中的某一份大佬的代码 。首先,我们不妨构建一棵线段树,这个树所有结点的值都为0,然后我们在建树的时候同时记录最小值的位置(如果有相同的值出现,我们选择靠左的位置)。
然后,我们用给定的限制条件去更新这个树,更新的方式很巧妙,用的是最大值,是因为如果某个区间的最小值是
x
x
x,那一定在这个区间中出现的所有数都必须大于
x
x
x,因此一个区间如果受到多个约束条件的限制,那出现的数一定都比最大的限制条件中的数要大。同时我们还要记录对于相同值的限制区间有没有交集,如果没有,肯定是构造不出来的(一个数只能出现一次)。
最后,我们来从0~n-1放值,我们首先查看这个数有没有被限制出现的区间,有就查询该区间的最小值,没有就查询整个大区间的最小值。假设查询出来的最小值是
m
i
n
n
u
m
minnum
minnum,只有满足
m
i
n
n
u
m
≤
x
minnum\le x
minnum≤x的时候才能安排(因为
[
1
,
x
−
1
]
[1,x-1]
[1,x−1]都已经被安排完毕)。安排完
x
x
x后,我们需要对安排的位置更新成INF,代表当前位置不能再安排别的值。
#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
#define PI pair<int,int>
using namespace std;
const int maxn=1e5+100;
const int INF=0x3f3f3f3f;
struct Segment{
int l,r,judge;
}s[maxn];
int n,q,ans[maxn],tree[maxn*4],mark[maxn*4],loc[maxn*4];
void Output(){cout<<-1;for(int i=2;i<=n;++i) cout<<" -1";}
void push_down(int p)
{
if(mark[p]!=-1){
tree[p<<1]=max(tree[p],tree[p<<1]);tree[p<<1|1]=max(tree[p],tree[p<<1|1]);
mark[p<<1]=max(mark[p],mark[p<<1]);mark[p<<1|1]=max(mark[p],mark[p<<1|1]);
mark[p]=-1;
}
}
void build(int l=1,int r=n,int p=1)
{
if(l==r) mark[p]=-1,loc[p]=l;
else{
int mid=(l+r)/2;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
loc[p]=l,mark[p]=-1;
}
}
void update(int l,int r,int d,int cl=1,int cr=n,int p=1)
{
if(cl>r || cr<l) return;
if(cl>=l && cr<=r){
tree[p]=max(tree[p],d);mark[p]=max(mark[p],d);
return;
}
int mid=(cl+cr)/2;
push_down(p);
update(l,r,d,cl,mid,p<<1);update(l,r,d,mid+1,cr,p<<1|1);
if(tree[p<<1]<=tree[p<<1|1]) tree[p]=tree[p<<1],loc[p]=loc[p<<1];
else tree[p]=tree[p<<1|1],loc[p]=loc[p<<1|1];
}
PI query(int l,int r,int cl=1,int cr=n,int p=1)
{
if(cl>r || cr<l) return PI(INF,-1);
if(cl>=l && cr<=r) return PI(tree[p],loc[p]);
int mid=(cl+cr)/2;
push_down(p);
PI left=query(l,r,cl,mid,p<<1),right=query(l,r,mid+1,cr,p<<1|1);
if(left.first<=right.first) return left;
else return right;
}
int main()
{
close;cin>>n>>q;
for(int i=1;i<=n;++i) s[i].judge=0;
bool ok=true;build();
for(int i=1;i<=q;++i){
int x,y,val;cin>>x>>y>>val;x++;y++;val++;
if(val<1 || val>n) ok=false;
else update(x,y,val);
if(!s[val].judge) {s[val].judge=1;s[val].l=x;s[val].r=y;}
else{
s[val].l=max(x,s[val].l);
s[val].r=min(y,s[val].r);
}
if(s[val].l>s[val].r) ok=false;
}
if(!ok) {Output();return 0;}
for(int i=1;i<=n;++i){
int l=1,r=n;
if(s[i].judge) l=s[i].l,r=s[i].r;
PI cur=query(l,r);
if(cur.first>i) {ok=false;Output();break;}
ans[cur.second]=i-1;
update(cur.second,cur.second,INF);
}
if(!ok) return 0;
cout<<ans[1];for(int i=2;i<=n;++i) cout<<' '<<ans[i];
}
D:雨雪霏霏
(可能没有啥本事更新,先鸽