2024河北cpc题解



题目链接

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)(rl+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 1S,n500

思路:

  • 对于一个给定的字符串,如何计算贡献?
  • 以 c c p c 为例,字符串有 n u m c 个 c ,枚举所有 p 的位置,假设前面的 c 有 c i 个 以ccpc为例,字符串有numc个c,枚举所有p的位置,假设前面的c有c_i个 ccpc为例,字符串有numcc,枚举所有p的位置,假设前面的cci
  • 此时贡献为 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(ci1)(numcci)
  • 假设操作后字符从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=1numpaibi
  • 方法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=1npre1ipre2i
  • 回到问题,考虑dp求解 定义 f i , j , k 表示考虑了前 i 位,选了 j 个 c , 操作了 k 次的最大贡献 定义f_{i,j,k}表示考虑了前i位,选了j个c,操作了k次的最大贡献 定义fi,j,k表示考虑了前i位,选了jc,操作了k次的最大贡献
  • 对于第 i + 1 位,如果选择 c ,前面选择的 p 的数量 l p = i − j 对于第i+1位,如果选择c,前面选择的p的数量lp=i-j 对于第i+1位,如果选择c,前面选择的p的数量lp=ij
  • 则 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(lp1)(numplp))
  • 如果选择 p ,记 n k 为 k 加上第 i − j + 1 个 p 的位置和 i 的位置差 如果选择p,记nk为k加上第i-j+1个p的位置和i的位置差 如果选择p,记nkk加上第ij+1p的位置和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(j1)(numcj))
  • 注意状态转移的边界即可,需要初始化负无穷,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 s106

思路:

  • 添加一个珠子不会影响位置的奇偶性
  • 从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 n5×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,yi=3di+i+disx,y
  • 设 d e p i 为 i 到根节点的距离 设dep_i为i到根节点的距离 depii到根节点的距离
  • 如果 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) 如果vxlca(x,y)上,则i=depxdepi,耗时为disx,y+depx+(3didepi)
  • 如果 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) 如果vlca(x,y)y上,则i=disx,y(depydepi),耗时为2disx,ydepy+(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,3didepi即可
  • 对于 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

签到

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值