文章目录
题目链接
The 8th Hebei Collegiate Programming Contest
A.Update
签到
B.Sequence II
题意:
定义区间 [ l , r ] 的值为 m a x ( a l , . . . a r ) ⋅ m i n ( a l , . . . , a r ) ⋅ ( r − l + 1 ) , 找第 k 大的值 定义区间 [l,r]的值为max(a_l ,...a_r) \cdot min(a_l,...,a_r) \cdot (r-l+1),找第k大的值 定义区间[l,r]的值为max(al,...ar)⋅min(al,...,ar)⋅(r−l+1),找第k大的值
思路:
- 二分答案,然后计算>=mid的区间有多少个
- 考虑以i下标的值为最小值时,最左和最右能到达的最远距离,可以用单调栈或者悬线法预处理出来
- 然后对于每个点,会有左右两个区间,枚举较小长度的区间作为其中一个端点,对于较长长度的区间,因为区间的值会随长度增大而增大,可以二分出另外一边能到的最远距离,计算答案即可
- 还需要一个st表,在二分区间最远端时来查询区间最值
- 时间复杂度: O ( n l o g 3 n ) O(nlog^3n) O(nlog3n)
- 做法二:直接建笛卡尔树,做笛卡尔树分治
- 两个做法实际上都基于启发式合并的分治,是启发式合并的逆过程,相关习题可以搜索启发式分裂或者笛卡尔树分治,可以搜出许多类似的题目
单调栈做法,参考自jiangly,据他分析,在二分区间另外一个端点时,先倍增再二分可以做到两个log
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
struct RMQ{
vector<vector<int>> st;
int n;
void init(vector<int> &a,int n){
this->n=n;
st=vector<vector<int>> (n+1,vector<int> (30,1e9));
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;j<=25;j++){
for(int i=1;i+(1<<j)-1<=n;i++)st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
int query(int l,int r){
int len=__lg(r-l+1);
return max(st[l][len],st[r-(1<<len)+1][len]);
}
} rmq;
void solve(){
int n,k;
cin>>n>>k;
vector<int> a(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
vector<int> l(n+1),r(n+1,n+1);
vector<int> st;
iota(l.begin(),l.end(),0);
iota(r.begin(),r.end(),0);
for(int i=2;i<=n;i++){
while(l[i]>1&&a[i]<a[l[i]-1])
l[i]=l[l[i]-1];
}
for(int i=n-1;i>=1;i--){
while(r[i]<n&&a[i]<=a[r[i]+1])r[i]=r[r[i]+1];
}
rmq.init(a,n);
auto get=[&](int l,int r,int mn){
if(r>n||l<1)return INF*INF;
return rmq.query(l,r)*mn*(r-l+1);
};
auto count_l=[&](int l,int r,int L,int R,int mn,int mid){
int ans=0;
// for(int i=l,j=L;i<=r;i++){
// while(j<=R&&get(i,j,mn)<mid)j++;
// ans+=R-j+1;
// }
for(int i=l,j=L;i<=r;i++){
// int t=1;
// while(j+t<=R&&get(i,j+t,mn)<mid)t*=2;
int lo=j,hi=R+1;
while(lo<hi){
int m=lo+hi>>1;
if(get(i,m,mn)>=mid)hi=m;
else lo=m+1;
}
j=lo;
ans+=R-j+1;
if(j==R+1)break;
}
return ans;
};
auto count_r=[&](int l,int r,int L,int R,int mn,int mid){
int ans=0;
// for(int i=R,j=r;i>=L;i--){
// while(j>=l&&get(j,i,mn)<mid)j--;
// ans+=j-l+1;
// }
for(int i=R,j=r;i>=L;i--){
// int t=1;
// while(j-t>=l&&get(j-t,i,mn)<mid)t*=2;
int lo=l-1,hi=j;
while(lo<hi){
int m=lo+hi+1>>1;
if(get(m,i,mn)>=mid)lo=m;
else hi=m-1;
}
j=lo;
ans+=j-l+1;
if(j==l-1)break;
}
return ans;
};
auto check=[&](int mid){
int ans=0;
for(int i=1;i<=n;i++){
if(i-l[i]+1<=r[i]-i+1)ans+=count_l(l[i],i,i,r[i],a[i],mid);
else ans+=count_r(l[i],i,i,r[i],a[i],mid);
}
return ans>=k;
};
int L=1,R=1e18;
while(L<R){
int mid=L+R+1>>1;
if(check(mid))L=mid;
else R=mid-1;
}
cout<<L<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
笛卡尔树分治写法,大差不差,但是需要注意剪枝,不然会tle
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5e4+10,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
struct RMQ{
vector<vector<int>> st;
int n;
void init(int a[],int n){
this->n=n;
st=vector<vector<int>> (n+1,vector<int> (30,1e9));
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;j<=25;j++){
for(int i=1;i+(1<<j)-1<=n;i++)st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
int query(int l,int r){
int len=__lg(r-l+1);
return max(st[l][len],st[r-(1<<len)+1][len]);
}
} rmq;
int stk[N],ls[N],rs[N],top,cur;
int n,k,a[N];
int get(int l,int r,int mn){
if(l<1||r>n)return INF*INF;
return rmq.query(l,r)*mn*(r-l+1);
}
void dfs(int u,int l,int r,int x,int &ans){
if(u-l<r-u){
for(int i=l;i<=u;i++){
int lo=u,hi=r+1;
while(lo<hi){
int mid=lo+hi>>1;
if(get(i,mid,a[u])>=x)hi=mid;
else lo=mid+1;
}
ans+=r+1-lo;
if(lo==r+1)break;
}
}
else{
for(int i=r;i>=u;i--){
int lo=l-1,hi=u;
while(lo<hi){
int mid=lo+hi+1>>1;
if(get(mid,i,a[u])>=x)lo=mid;
else hi=mid-1;
}
ans+=lo-l+1;
if(lo==l-1)break;
}
}
if(ls[u])dfs(ls[u],l,u-1,x,ans);
if(rs[u])dfs(rs[u],u+1,r,x,ans);
}
bool check(int mid){
int ans=0;
dfs(stk[1],1,n,mid,ans);
return ans>=k;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
rmq.init(a,n);
for(int i=1;i<=n;i++){
cur=top;
while(cur&&a[stk[cur]]>a[i])cur--;
if(cur)rs[stk[cur]]=i;
if(cur<top)ls[i]=stk[cur+1];
stk[++cur]=i;
top=cur;
}
int l=1,r=1e18;
while(l<r){
int mid=l+r+1>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
C .Goose Goose Duck
思路:
贪心把当前能选的人加进来,然后选择r最小的,用堆维护
卡long long的sb题
#include<bits/stdc++.h>
#define LL long long
using namespace std;
typedef pair<int,int> PII;
const int N=1<<11;
const int INF=1e9;
const int mod=1e9+7;
void solve(){
int n;
vector<array<int,3>> a;
cin>>n;
for(int i=1;i<=n;i++){
int l,r;
cin>>l>>r;
a.push_back({l,r,i});
}
sort(a.begin(),a.end());
priority_queue<array<int,2>,vector<array<int,2>>,greater<array<int,2>>> q;
vector<int> nums(n+1);
int i=0,cnt=0;
while(true){
while(i<n&&a[i][0]<=cnt)q.push({a[i][1],a[i][2]}),i++;
if(q.empty())break;
while(!q.empty()){
auto [r,id]=q.top();
q.pop();
if(cnt>r)continue;
nums[id]=++cnt;
break;
}
}
vector<array<int,2>> tmp;
for(int i=1;i<=n;i++){
if(nums[i]!=0)tmp.push_back({nums[i],i});
}
sort(tmp.begin(),tmp.end());
cout<<tmp.size()<<"\n";
for(auto [x,y]:tmp)cout<<y<<" ";
cout<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
D. CCPC
题意:
给定只含有c和p两种字符的串,进行恰好m次操作,使得子序列为ccpc或者ppcp的字符最多?
每次操作可以交换相邻字符
数据范围: 1 ≤ ∣ S ∣ , n ≤ 500 1 \leq |S|,n \leq 500 1≤∣S∣,n≤500
思路:
- 对于一个给定的字符串,如何计算贡献?
- 以 c c p c 为例,字符串有 n u m c 个 c ,枚举所有 p 的位置,假设前面的 c 有 c i 个 以ccpc为例,字符串有numc个c,枚举所有p的位置,假设前面的c有c_i个 以ccpc为例,字符串有numc个c,枚举所有p的位置,假设前面的c有ci个
- 此时贡献为 c i ⋅ ( c i − 1 ) 2 ⋅ ( n u m c − c i ) 此时贡献为\frac{c_i \cdot (c_i-1)}{2} \cdot (numc-c_i) 此时贡献为2ci⋅(ci−1)⋅(numc−ci)
- 假设操作后字符从s变成t,如何计算最小操作数?
- 这里有两种方法
- 方法1:求出原字符串和新字符串所有p对应的下标,分别为a和b数组,然后最小操作数为 ∑ i = 1 n u m p ∣ a i − b i ∣ \sum_{i=1}^{nump} |a_i-b_i| i=1∑nump∣ai−bi∣
- 方法2:求出两个字符串的c的数量前缀和,记为pre1和pre2,最小操作数为 ∑ i = 1 n ∣ p r e 1 i − p r e 2 i ∣ \sum_{i=1}^n|pre1_i-pre2_i| i=1∑n∣pre1i−pre2i∣
- 回到问题,考虑dp求解 定义 f i , j , k 表示考虑了前 i 位,选了 j 个 c , 操作了 k 次的最大贡献 定义f_{i,j,k}表示考虑了前i位,选了j个c,操作了k次的最大贡献 定义fi,j,k表示考虑了前i位,选了j个c,操作了k次的最大贡献
- 对于第 i + 1 位,如果选择 c ,前面选择的 p 的数量 l p = i − j 对于第i+1位,如果选择c,前面选择的p的数量lp=i-j 对于第i+1位,如果选择c,前面选择的p的数量lp=i−j
- 则 f i + 1 , j + 1 , k = m a x ( f i + 1 , j + 1 , k , f i , j , k + l p ⋅ ( l p − 1 ) 2 ⋅ ( n u m p − l p ) ) 则f_{i+1,j+1,k}=max(f_{i+1,j+1,k},f_{i,j,k}+\frac{lp \cdot (lp-1)}{2}\cdot (nump-lp)) 则fi+1,j+1,k=max(fi+1,j+1,k,fi,j,k+2lp⋅(lp−1)⋅(nump−lp))
- 如果选择 p ,记 n k 为 k 加上第 i − j + 1 个 p 的位置和 i 的位置差 如果选择p,记nk为k加上第i-j+1个p的位置和i的位置差 如果选择p,记nk为k加上第i−j+1个p的位置和i的位置差
- f i + 1 , j , n k = m a x ( f i + 1 , j , n k , f i , j , k + j ⋅ ( j − 1 ) 2 ⋅ ( n u m c − j ) ) f_{i+1,j,nk}=max(f_{i+1,j,nk},f_{i,j,k}+\frac{j\cdot (j-1)}{2}\cdot (numc-j)) fi+1,j,nk=max(fi+1,j,nk,fi,j,k+2j⋅(j−1)⋅(numc−j))
- 注意状态转移的边界即可,需要初始化负无穷,f[0][0][0]=0
- 时间复杂度: O ( n 2 m ) O(n^2m) O(n2m)
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
int f[N][N][N],pos[N];
void solve(){
int n,m;
string s;
cin>>s>>m;
n=s.size();
s=" "+s;
int numc=0,nump=0;
for(int i=1;i<=n;i++){
if(s[i]=='c')numc++;
else pos[++nump]=i;
}
memset(f,-0x3f,sizeof f);
f[0][0][0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<=min(numc,i);j++){
for(int k=0;k<=m;k++){
if(f[i][j][k]<0)continue;
if(j<numc){
int lp=i-j;
if(nump>=lp)f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+lp*(lp-1)/2*(nump-lp));
}
int tmp=nump-(i-j);
if(tmp&&k+abs(pos[i-j+1]-i-1)<=m){
f[i+1][j][k+abs(pos[i-j+1]-i-1)]=max(f[i+1][j][k+abs(pos[i-j+1]-i-1)],f[i][j][k]+j*(j-1)/2*(numc-j));
}
}
}
}
int ans=0;
for(int i=m;i>=0;i-=2){
ans=max(ans,f[n][numc][i]);
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
方法二的解法,状态转移差不多
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
void solve(){
int n,m;
string s;
cin>>s>>m;
n=s.size();
s=" "+s;
vector<int> pre(n+1);
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+(s[i]=='c');
vector dp(2,vector(pre[n]+1,vector(m+1,-INF)));
dp[0][0][0]=0;
for(int i=0;i<n;i++){
swap(dp[0],dp[1]);
dp[0].assign(pre[n]+1,vector(m+1,-INF));
for(int x=0;x<=pre[n];x++){
for(int y=0;y<=m;y++){
if(dp[1][x][y]<0)continue;
int ny=y+abs(x-pre[i+1]);
if(ny<=m){
dp[0][x][ny]=max(dp[0][x][ny],dp[1][x][y]+x*(x-1)/2*(pre[n]-x));//选p
}
ny=y+abs(x+1-pre[i+1]);
if(ny<=m&&x+1<=pre[n])dp[0][x+1][ny]=max(dp[0][x+1][ny],dp[1][x][y]+(i-x)*(i-x-1)/2*(n-pre[n]-(i-x)));//选c
}
}
}
int ans=0;
for(int i=m;i>=0;i-=2)ans=max(ans,dp[0][pre[n]][i]);
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
E.Breakfast II
题意:
给定三个食堂的坐标,n个学生的坐标和办公室坐标,以及包子和鸡蛋限购数
量和总共需要采购的数量,求所有学生总路程最少是多少。
思路:
- 需要去的食堂次数是固定的
- 由于只有3个食堂,因此可以暴力dfs计算出每个学生去多少个食堂以及怎么去的方案,预处理出去多少个食堂的最短距离
- 然后就是一个简单的背包dp
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3010;
const int INF=1e9;
const int mod=1e9+7;
long double dp[2][N],w[N][1<<3];
pair<long double,long double> c[4];
vector<pair<long double,long double>> tmp;
int h[10],vis[10];
int n,m,k,b,e;
long double dis(long double x1,long double y1,long double x2,long double y2){
return sqrtl((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
void dfs(int x,int y){
if(x==y+1){
int state=0;
for(int i=1;i<x;i++)state|=1<<h[i];
for(int i=1;i<=k;i++){
long double ans=0;
long double xx=tmp[i].first,yy=tmp[i].second;
for(int j=1;j<x;j++){
auto [x,y]=c[h[j]];
ans+=dis(xx,yy,x,y);
xx=x,yy=y;
}
if(y!=0)ans+=dis(xx,yy,c[3].first,c[3].second);
w[i][state]=min(w[i][state],ans);
}
return;
}
for(int i=0;i<3;i++){
if(vis[i])continue;
vis[i]=1;
h[x]=i;
dfs(x+1,y);
vis[i]=0;
h[x]=-1;
}
}
int get(int st){
int cnt=0;
for(int i=0;i<3;i++)cnt+=(st>>i&1);
return cnt;
}
void solve(){
cin>>n>>m>>k>>b>>e;
int mx=max((n+b-1)/b,(m+e-1)/e);
for(int i=0;i<4;i++)cin>>c[i].first>>c[i].second;
tmp.emplace_back();
for(int i=1;i<=k;i++){
long double x,y;
cin>>x>>y;
tmp.push_back({x,y});
}
memset(h,-1,sizeof h);
for(int i=1;i<=k;i++){
for(int j=0;j<1<<3;j++)w[i][j]=1e18;
}
for(int i=0;i<2;i++){
for(int u=0;u<=mx;u++)dp[i][u]=1e18;
}
dp[0][0]=0;
for(int i=0;i<4;i++)dfs(1,i);
// for(int i=0;i<8;i++)cout<<w[1][i]<<"\n";
for(int i=1;i<=k;i++){
for(int j=0;j<=mx;j++)dp[i&1][mx]=1e18;
for(int st=0;st<1<<3;st++){
int val=get(st);
for(int j=mx;j>=val;j--)dp[i&1][j]=min(dp[i&1][j],dp[i-1&1][j-val]+w[i][st]);
}
}
cout<<setprecision(15)<<fixed;
cout<<dp[k&1][mx]<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
F. 3 Spilt
题意:
把一个竞赛图中的点分成A、B、C三个非空集合,要求所有的点对满足:
若x∈A,y∈B,则连边顺序为x→y;
若x∈B,y∈C,则连边顺序为x→y;
若x∈C,y∈A,则连边顺序为x→y。
构造一个合法划分方法,或者输出无解。
思路:
- 可以像官方题解那样直接构造唯一解
- 这里写写2-sat的做法
- 考虑先将1归为A集合,那么根据与1的连边,2-n可以分成两种点,这些点有两种情况(要么属于A,要么不属于A(这种情况必然属于B或C的其中一种))
- x表示属于A,x+n表示不属于A
- 可以分以下四种情况建图
- 建完图后跑2sat即可,但本题有个问题,A,B,C集合都不能为空,使用tarjan只能判断是否有解,但是不能构造三个集合都不为空的解,注意本题数据范围为500,因此可以暴力dfs寻找解,因为A已经有一个元素1,因此其他元素优先选择不属于集合A为真
- 点数为n,边数为n*n,因此最坏时间复杂度: O ( n 3 ) O(n^3) O(n3)
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=1010,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
int h[N],e[M],ne[M],idx,n;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool mark[N];
int stk[N],top;
int rev(int u){
if(u>n)return u-n;
return u+n;
}
bool dfs(int u){
if(mark[rev(u)])return false;
if(mark[u])return true;
mark[u]=true;
stk[++top]=u;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(!dfs(v))return false;
}
return true;
}
int a[510][510];
void solve(){
cin>>n;
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++)cin>>a[i][j];
}
vector<int> type(n+1);//0属于集合C,1属于集合B
for(int i=2;i<=n;i++){
type[i]=a[1][i];
}
auto cal=[&](){
memset(h,-1,sizeof h);
for(int i=2;i<n;i++){
for(int j=i+1;j<=n;j++){
int x=i,y=j;
if(a[x][y]==0)swap(x,y);
if(type[x]==1&&type[y]==1){
add(x+n,y+n);
add(y,x);
}
else if(type[x]==0&&type[y]==0){
add(x,y);
add(y+n,x+n);
}
else if(type[x]==1&&type[y]==0){
add(x,y);
add(y+n,x+n);
add(x+n,y+n);
add(y,x);
}
else{
add(x+n,y);
add(y+n,x);
}
}
}
for(int i=2;i<=n;i++){
if(!mark[i]&&!mark[rev(i)]){
top=0;
if(!dfs(rev(i))){
while(top>0)mark[stk[top--]]=0;
if(!dfs(i))return false;
}
}
}
vector<int> SET[3];
for(int i=1;i<=n;i++){
if(i==1||mark[i])SET[0].push_back(i);
else if(type[i]==1)SET[1].push_back(i);
else SET[2].push_back(i);
}
for(int i=0;i<3;i++){
if(SET[i].size()==0)return false;
}
for(int i=0;i<3;i++)cout<<SET[i].size()<<" \n"[i==2];
for(int i=0;i<3;i++){
for(auto it:SET[i])cout<<it<<" ";
cout<<"\n";
}
return true;
};
if(cal())return;
cout<<"0 0 0\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
G.Bracelet
题意:
给定00,01,11珠子的个数,以及一个目标字符串(首尾相接),问能最长串
出多长的区间(01可以翻转成10)。
∣
s
∣
≤
1
0
6
|s|≤10^6
∣s∣≤106
思路:
- 添加一个珠子不会影响位置的奇偶性
- 从0,1开始分别跑双指针即可
#include<bits/stdc++.h>
#define LL long long
using namespace std;
typedef pair<int,int> PII;
const int N=1<<11;
const int INF=1e9;
const int mod=1e9+7;
void solve(){
vector<int> c(3);
for(int i=0;i<3;i++)cin>>c[i];
string s;
cin>>s;
int n=s.size();
s+=s;
int ans=0;
for(int t=0;t<2;t++){
vector cnt=c;
for(int i=t,j=t;i<n;i+=2){
while(j+2<=i+n&&cnt[s[j]-'0'+s[j+1]-'0']>0){
cnt[s[j]-'0'+s[j+1]-'0']--;
j+=2;
}
ans=max(ans,j-i);
cnt[s[i]-'0'+s[i+1]-'0']++;
}
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
H.Missing Iris
题意:
在一棵树上有一些点有单车,经过有单车的点速度就可以从1变成2,多次询
问从树上点x到点y的最短耗时。
n
≤
5
×
1
0
5
n≤5×10^5
n≤5×105
思路:
- 假设不找单车,答案为2*dis(x,y)
- 对于有单车的点称为特殊点,记录每个点到最近特殊点的距离d
- 假设在x到y的简单路径上的某个点v去最近的特殊点取单车,x与v的距离为i,则找单车去总耗时最小为: 2 i + 2 d i + d i + d i s x , y − i = 3 d i + i + d i s x , y 2i+2d_i+d_i+dis_{x,y}-i=3d_i+i+dis_{x,y} 2i+2di+di+disx,y−i=3di+i+disx,y
- 设 d e p i 为 i 到根节点的距离 设dep_i为i到根节点的距离 设depi为i到根节点的距离
- 如果 v 在 x 到 l c a ( x , y ) 上,则 i = d e p x − d e p i , 耗时为 d i s x , y + d e p x + ( 3 d i − d e p i ) 如果v在x到lca(x,y)上,则i=dep_x-dep_i,耗时为dis_{x,y}+dep_x+(3d_i-dep_i) 如果v在x到lca(x,y)上,则i=depx−depi,耗时为disx,y+depx+(3di−depi)
- 如果 v 在 l c a ( x , y ) 到 y 上,则 i = d i s x , y − ( d e p y − d e p i ) , 耗时为 2 d i s x , y − d e p y + ( 3 d i + d e p i ) 如果v在lca(x,y)到y上,则i=dis_{x,y}-(dep_y-dep_i),耗时为2dis_{x,y}-dep_y+(3d_i+dep_i) 如果v在lca(x,y)到y上,则i=disx,y−(depy−depi),耗时为2disx,y−depy+(3di+depi)
- 倍增求 l c a 时,分别倍增维护 3 d i + d e p i , 3 d i − d e p i 即可 倍增求lca时,分别倍增维护3d_i+dep_i,3d_i-dep_i即可 倍增求lca时,分别倍增维护3di+depi,3di−depi即可
- 对于 d i , 一遍 b f s 求出来 对于d_i,一遍bfs求出来 对于di,一遍bfs求出来
- 询问的时候也是倍增跳到lca取最小值,分别对x,y都取一次最小
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5e5+10;
const int INF=1e9;
const int mod=1e9+7;
vector<int> adj[N];
int d[N],par[N][30],mnl[N][30],mnr[N][30],dep[N];
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
par[u][0]=fa;
mnl[u][0]=3*d[fa]-dep[fa];
mnr[u][0]=3*d[fa]+dep[fa];
for(int i=1;i<=__lg(dep[u]);i++){
par[u][i]=par[par[u][i-1]][i-1];
mnl[u][i]=min(mnl[u][i-1],mnl[par[u][i-1]][i-1]);
mnr[u][i]=min(mnr[u][i-1],mnr[par[u][i-1]][i-1]);
}
for(auto v:adj[u]){
if(v==fa)continue;
dfs(v,u);
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
while(dep[x]>dep[y]){
x=par[x][(int)__lg(dep[x]-dep[y])];
}
if(x==y)return x;
for(int i=__lg(dep[x]);i>=0;i--){
if(par[x][i]!=par[y][i]){
x=par[x][i];
y=par[y][i];
}
}
return par[x][0];
}
int dis(int x,int y){
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
//3d[i]+dis[x][y]+(dep[x]-dep[i])=dis[x][y]+dep[x]+(3d[i]-dep[i])
int getl(int x,int y){
int len=dep[x]-dep[y];
int mn=3*d[x]-dep[x];
for(int i=30;i>=0;i--){
if(len>>i&1){
mn=min(mn,mnl[x][i]);
x=par[x][i];
}
}
return mn;
}
//3d[i]+dis[x][y]+(dis[x][y]-(dep[y]-dep[i]))=2dis[x][y]-dep[y]+(3d[i]+dep[i])
int getr(int x,int y){
int len=dep[x]-dep[y];
int mn=3*d[x]+dep[x];
for(int i=30;i>=0;i--){
if(len>>i&1){
mn=min(mn,mnr[x][i]);
x=par[x][i];
}
}
return mn;
}
void solve(){
int n,k;
cin>>n>>k;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
}
queue<int> q;
for(int i=1;i<=n;i++)d[i]=-1;
for(int i=1;i<=k;i++){
int x;
cin>>x;
d[x]=0;
q.push(x);
}
while(!q.empty()){
int u=q.front();
q.pop();
for(auto v:adj[u]){
if(d[v]==-1){
d[v]=d[u]+1;
q.push(v);
}
}
}
dfs(1,0);
int Q;
cin>>Q;
while(Q--){
int x,y;
cin>>x>>y;
int LCA=lca(x,y);
int D=dis(x,y);
int L=D+dep[x]+getl(x,LCA);
int R=2*D-dep[y]+getr(y,LCA);
cout<<min({L,R,2*D})<<"\n";
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
I.Subnet
签到
J.Iris’ Food
签到,贪心选,快速幂+等比数列求和优化
K.Welcome
签到