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