【EOJ Monthly】2021.2

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]2dist[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 i1个结点在以 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 minnumx的时候才能安排(因为 [ 1 , x − 1 ] [1,x-1] [1,x1]都已经被安排完毕)。安排完 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:雨雪霏霏

(可能没有啥本事更新,先鸽

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值