[57] (多校聯訓) A層衝刺NOIP2024模擬賽15

HaneDaniko發表於2024-10-29

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);
}

相關文章