[57] (多校联训) A层冲刺NOIP2024模拟赛15

A.追逐游戏

一个非常暴力的想法是直接求出最短路径 \(S\),然后对 \(S\) 上的点,比较 \(dis_{s,S_i}\)\(dis_{s',S_i}\) 的大小,如果抓捕的人先到就符合条件

实际上,这个符合条件的路径是单调的,即在最短路径上存在一个断点,断点靠近起点的一侧总不可达,靠近终点的一侧总是可达的

证明:如果 \(p\) 可达,考虑由 \(p\) 在最短路径上走一步,被追捕者也走一步,因此仍然是可达的

所以可以对内层二分答案,复杂度单次询问是二分套 LCA 的,总复杂度为 \(q\log^2 n\)

比较好的一个 trick 是用树剖求树上路径上的第 \(k\) 个点(特定点的第 \(k\) 级祖先),比较目标点是否在当前链上即可,知道这个就能快速求最短路径上的特定点了,因为这个最短路径本质是 \(x,lca,y\) 的树上简单路径,只需要维护一下深度差然后做分讨就行了

还有一个比较好的 trick

int dis(int x,int y){
	int p=lca(x,y);
	return deep[x]+deep[y]-2*deep[p];
} 

此外这个题可以通过分讨砍掉二分

如果追捕者更晚到达目标,那么答案一定是终态

否则,直接考虑两人路径的重合点,抓捕者直接在此段开始时选择向着被抓捕者走,需要的时间除二,在中点处相遇,需要处理一些特殊情况

#include<bits/stdc++.h>
using namespace std;
vector<int>e[200001];
int dfn[200001];
int size[200001],deep[200001],top[200001],fa[200001],maxson[200001];
int dfsorder[200001];
int cnt;
int n,q;
void dfs1(int now,int last){
	size[now]=1;
	deep[now]=deep[last]+1;
	fa[now]=last;
	for(auto i:e[now]){
		if(i!=last){
			dfs1(i,now);
			size[now]+=size[i];
			if(size[i]>size[maxson[now]]){
				maxson[now]=i;
			}
		}
	}
}
void dfs2(int now,int nowtop){
	dfn[now]=++cnt;
	dfsorder[cnt]=now;
	top[now]=nowtop;
	if(maxson[now]){
		dfs2(maxson[now],nowtop);
	}
	for(auto i:e[now]){
		if(i!=fa[now] and i!=maxson[now]){
			dfs2(i,i);
		}
	}
}
int lca(int a,int b){
	while(top[a]!=top[b]){
		if(deep[top[a]]<deep[top[b]]) swap(a,b);
		a=fa[top[a]];
	}
	if(deep[a]<deep[b]) swap(a,b);
	return b;
}
int lca(int a,int b,int c){
	int x1=lca(a,b),x2=lca(a,c),x3=lca(b,c);
	if(deep[x1]<deep[x2]) swap(x1,x2);
	if(deep[x1]<deep[x3]) swap(x1,x3);
	return x1;
}
int dis(int x,int y){
	int p=lca(x,y);
	return deep[x]+deep[y]-2*deep[p];
} 
int getfa(int u,int k){
	int D=deep[u]-k;
	while(deep[fa[top[u]]]>=D) u=fa[top[u]];
	return dfsorder[dfn[u]-(deep[u]-D)];
}
int getfa(int a,int b,int k){
	int p=lca(a,b);
	int d1=deep[a]-deep[p],d2=deep[b]-deep[p];
	if(d1>=k) return getfa(a,k);
	return getfa(b,d1+d2-k);
}
signed main(){
	freopen("chase.in","r",stdin);
	freopen("chase.out","w",stdout);
	cin>>n>>q;
	for(int i=1;i<=n-1;i++){
		int a,b;cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	dfs1(1,0);dfs2(1,1);
	while(q--){
		int u,v,x;
		cin>>u>>v>>x;
		int p=lca(u,v,x);
		int d1=dis(x,p),d2=dis(u,p),d3=dis(p,v);
		if(d2<d1){
			cout<<d1+d3<<" "<<v<<"\n"; 
		}
		else{
			cout<<(d1+d2+1)/2<<" "<<getfa(u,x,(d1+d2+1)/2)<<"\n";
		}
	}
}

C.软件工程

发动了从暑假传到现在的传统艺能,打一堆假做法然后取 \(\max\)

赛后发现自己打的并不是假做法,而是分讨中的其中一种情况

首先有一种非常好想的,就是你对区间长度排序,然后考虑到对每个线段放一个集合里非常省事,这样它的贡献就是它自己,因此挑出最长的 \(k-1\) 个区间,每个分配一个集合,然后剩下的全丢到最后一个集合里

这个做法适用的情况是存在完全没有交集的线段时,此时假设线段 \(a,b,c\) 互相无交集,那么把它们都堆到一起是最优情况,否则总是可能会占用更多的集合导致答案变小

另一种

首先发现题目里的不超过 \(k\) 完全没有用,因为当你从划分 \(k-1\) 段改到划分 \(k\) 段的时候,要么把两个有交的线段拆开了,要么无变化,总之贡献一定是单调不减的,所以你只需要考虑划分 \(k\) 段时候的答案就行了

一种比较正常的转移一定是形如 \(f_i=\max_j (f_j+cost)\) 的,因此你直接去维护这个 \(f\)

换一种思路,设 \(f_{i,k}\) 是枚举前 \(i\) 位,第 \(i\) 位放 \(k\) 集合时 \(k\) 这单个集合的的贡献,按题意枚举最值暴力转移,复杂度 \(nk\),同时第一维可以压掉

说一下这个转移,我定义 \(cost\) 是加入这个线段对区间长度的贡献,比如加入 \([1,3]\) 到空集贡献为 \(2\)\([1,2]\) 加入 \([1,3]\) 贡献为 \(-1\),通过这个来进行转移,写起来挺像贪心的

不知道能不能再优化,我赛后就没试了

封了个类用来转移集合,应该挺好懂的

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<typename T>void ignored(T x){}
struct line{
    int l,r;
    inline bool operator >(const line&A)const{
        return r-l>A.r-A.l;
    }
};
struct area{
    int l,r;
    area(){
        l=-1,r=-1;
    }
    inline int len()const{
        return l==-1ll?0ll:max(0ll,r-l);
    }
    inline area operator+(line a){
        area ans;
        if(l==-1){
            ans.l=a.l;
            ans.r=a.r;
        }
        else{
            ans.l=max(l,a.l);
            ans.r=min(r,a.r);
        }
        return ans;
    }
};
int n,m;
line a[5001];
area p[5001];
area np[5001];
signed main(){
    ignored(freopen("se.in","r",stdin));
    ignored(freopen("se.out","w",stdout));
    ignored(scanf("%lld %lld",&n,&m));
    for(int i=1;i<=n;++i){
        ignored(scanf("%lld %lld",&a[i].l,&a[i].r));
    }
    sort(a+1,a+n+1,greater<line>());
    int ans2=0;
    for(int i=1;i<=m;++i){  //策略1
        np[i]=np[i]+a[i];
    }
    for(int i=m+1;i<=n;++i){
        np[m]=np[m]+a[i];
    }
    for(int i=1;i<=m;++i){
        ans2+=np[i].len();
    }
    for(int i=1;i<=n;++i){   //策略2
        int mindx=0x3f3f3f3f,mindxid=1;
        for(int j=1;j<=m;++j){
            int tmp=p[j].len()-(p[j]+a[i]).len();//这里找的是对贡献减小量最少的
            if(tmp<mindx){
                mindx=tmp;
                mindxid=j;
            }
            else if(tmp==mindx){
                if(p[mindxid].len()>p[j].len()){
                    mindxid=j;
                }
            }//寻找最值
        }
        p[mindxid]=p[mindxid]+a[i];
    }
    int ans=0;
    for(int i=1;i<=m;++i){
        ans+=p[i].len();
    }
    cout<<max(ans,ans2);
}
posted @ 2024-10-29 19:25  HaneDaniko  阅读(39)  评论(0编辑  收藏  举报