10.10 联考题解

T2
考点在于贪心匹配后检验合法性。

首先要观察到,每一个右下角取当前最靠右的左上角匹配就是最佳方案。交换法可以证明。

但是即使是最佳匹配也不一定合法。考虑对于已知的 \(n\) 个矩形怎么检验其合法性。离散化坐标后,考虑扫描线,扫行坐标,查列坐标,在一个矩形的上下行位置给两个列坐标单点加,查询这两列的区间和,若不等于 \(2\),则说明还有别的矩形和这一行相交了,不合法。r如果是包含关系则不会被判掉,因为在小的列区间里查不到大的列坐标。在每一个矩形的下方一行把这个矩形的两点减去即可。

时间复杂度 \(O(n\log n)\)。

#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n;
const int N = 2e5+10;
struct node{
    int x,y,id;
    bool operator <(const node &a)const{
        return x<a.x;
    }
}a[N],b[N];
int lx[2*N],len,ly[2*N],cnt,c[N],mac[N];
set<pii>s;
vector<node>v[N];
vector<pii>q[N];
int sum[N<<2],res;
#define ls p<<1
#define rs p<<1|1
void modify(int p,int l,int r,int x,int v){
    if(l==r){
        sum[p]+=v;
        return;
    }
    int mid=(l+r)/2;
    if(x<=mid)modify(ls,l,mid,x,v);
    else modify(rs,mid+1,r,x,v);
    sum[p]=sum[ls]+sum[rs];
}
int query(int p,int l,int r,int ql,int qr){
    if(ql>qr)return 0;
    if(ql<=l&&r<=qr)return sum[p];
    int mid=(l+r)/2,res=0;
    if(ql<=mid)res+=query(ls,l,mid,ql,qr);
    if(qr>mid)res+=query(rs,mid+1,r,ql,qr);
    return res;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].y;
        a[i].id=i;
        lx[++len]=a[i].x;ly[++cnt]=a[i].y;
    }
    for(int i=1;i<=n;i++){
        cin>>b[i].x>>b[i].y;
        b[i].id=i;
        lx[++len]=b[i].x;ly[++cnt]=b[i].y;
    }
    sort(lx+1,lx+1+len);sort(ly+1,ly+1+cnt);
    len=unique(lx+1,lx+1+len)-lx-1;
    cnt=unique(ly+1,ly+1+cnt)-ly-1;
    for(int i=1;i<=n;i++){
        a[i].x=lower_bound(lx+1,lx+1+len,a[i].x)-lx;
        a[i].y=lower_bound(ly+1,ly+1+cnt,a[i].y)-ly;
        b[i].x=lower_bound(lx+1,lx+1+len,b[i].x)-lx;
        b[i].y=lower_bound(ly+1,ly+1+cnt,b[i].y)-ly;
    }
    sort(a+1,a+1+n);sort(b+1,b+1+n);
    int cur=1;
    for(int i=1;i<=n;i++){
        while(cur<=n&&a[cur].x<=b[i].x){
            s.insert({a[cur].y,cur});cur++;
        }
        auto it=s.upper_bound({b[i].y,n});
        if(it==s.begin()){
            cout<<"syntax error"<<endl;
            return 0;
        }
        --it;
        c[i]=(*it).scd;s.erase(it);
    }
    bool flag=1;
    for(int i=1;i<=n;i++){
        q[a[c[i]].x].push_back({a[c[i]].y,b[i].y});
        q[b[i].x].push_back({a[c[i]].y,b[i].y});
        v[a[c[i]].x].push_back({a[c[i]].y,b[i].y,1});
        v[b[i].x+1].push_back({a[c[i]].y,b[i].y,-1});
    }
    for(int i=1;i<=len;i++){
        for(auto [x,y,id]:v[i]){
            modify(1,1,cnt,x,id);
            modify(1,1,cnt,y,id);
        }
        for(auto x:q[i]){
            flag&=(query(1,1,cnt,x.fst,x.scd)==2);
            if(!flag)break;
        }
        if(!flag)break;
    }
    if(!flag){
        cout<<"syntax error"<<endl;
        return 0;
    }
    for(int i=1;i<=n;i++){
        mac[a[c[i]].id]=b[i].id;
    }
    for(int i=1;i<=n;i++)cout<<mac[i]<<endl;
    return 0;
}

T3

贪心题,其实证明还是比其他按位贪心要复杂的。

首先要观察到,一定是先做完所有按位与操作才来做加的操作,因为 \((a + b)\land c\le c\le (a\land b)+c\),所以是最后再来做求和。

转化题意为,把所有数划分为 \(k\) 个集合,求每个集合内数按位与的和的最大值。

考虑按位贪心,尽量的使当前最高位的值更大,设当前第 \(i\) 位为 \(1\) 的数有 \(cnt\) 个,分情况讨论。

若 \(cnt \ge k\),则我们要分给 \(k\) 个集合还有多,注意一个集合内只要有一个该位为零的数就没有贡献,所以我们可能只能有 \((k-1)\times 2^i\) 的贡献,这里我们把所有为 \(0\) 的数都分在同一个集合里,直接把他当成一个一个数即他们的按位与加入数组里就可以了。注意如果没有为零的数还要再加一个 \(2^i\)。然后注意把为一的数这一位清掉。

为什么是对的?如果不全合并的话,不是有可能还能产生进位吗?假设拿出一个为零的数 \(x\),\(x < 2^i\),拿出一个就要合并到别的集合,损失 \(2^i\),而把它放进全零组,损失 \(x|y<2^i\),所以一定是合并到全零集合里最优。

若 \(cnt<k\),则放不够,这时候的贪心策略是尽量一个数自己一组,这样最高位为 \(1\) 的组最多,那此时我们相当于直接确定了其集合,让 \(k\) 减少,\(ans\) 直接加上其完整的数说明贡献完了。

代码时间复杂度 \(O(n\log V)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define endl '\n'
#define popcount(x) __builtin_popcount(x)
int T,n,k;
const int N = 1e6+10;
int a[N];
vector<int>p;
signed main(){
    cin>>T;
    while(T--){
        cin>>n>>k;
        p.clear();
        for(int i=1;i<=n;i++){
            cin>>a[i];
            p.push_back(a[i]);
        }
        int ans=0;
        for(int w=29;w>=0;w--){
            int cnt=0;
            for(auto v:p){
                if((v>>w)&1){
                    ++cnt;
                }
            }
            vector<int>t;
            if(cnt>=k){
                ans+=(k-1)*(1<<w);
                int val=(1<<31)-1;
                for(auto v:p){
                    if(!((v>>w)&1))val&=v;
                    else t.push_back(v^(1<<w));
                }
                if(val!=(1<<31)-1)t.push_back(val);
                else ans+=(1<<w);
            }else{
                for(auto v:p){
                    if((v>>w)&1){
                        ans+=v;
                        k--;
                    }else{
                        t.push_back(v);
                    }
                }
            }
            p=t;
        }
        cout<<ans<<endl;
    }
   
    return 0;
}

T4

给定一棵树和 \(k\) 条树上的路径,询问交集最大的是哪两条路径。\(n,k\le 2\times 10^5\)。

设一条路径 lca 为 \(w_i\)。分类讨论树上路径的公共部分的形态。

1.当公共部分为直链时:此时枚举公共部分最深的节点 \(x\),则 \((u_i,v_i)\) 路径的 \(u_i\) 应在 \(x\) 子树内,则其在 \(x\) 位置往上最多能覆盖到 \(w_i\) 位置,那两条路径就能覆盖到 \(\min(dep_{w_i},dep_{w_j})\) 的深度。

对于一个 \(x\),求出有一个节点在其子树内的路径的 \(dep_{w_i}\) 最小的两条,这可以用一个 dfs 合并子树内信息。设 \(f_{x,0/1}\) 表示最小和次小的值,\(g_{x,0/1}\) 表示最小和次小的编号。

2.当链公共部分有拐点时:此时两条路径必须具有相同的 lca,枚举公共部分的一条单链的最深点 \(x\),就是要求在另一侧深度最大的最深点 \(y\),且在左侧 \(x\) 子树内的路径节点 \(u_i,u_j\),他们的另一侧节点 \(v_i,v_j\) 的 lca 也必须是 \(y\)。有一个经典的结论是,LCA 深度最深的点一定是所有候选点按照 DFS 序排序后在相邻点对中选出。所以我们目前要做的是,获取端点在 \(x\) 子树内的所有路径,用一个 set 存储这些路径,按照另一侧的 dfn 序排序,每加入一个就和相邻的前驱后继更新答案。

合并子树 set,只要启发式合并即可。

时间复杂度 \(O(n\log^2 n)\)。

#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,k;
const int N = 2e5+10,inf=1e9;
int fa[N];
vector<int>e[N];
int u[N],v[N];
int dep[N],tot,dfn[N],rdfn[N],st[N][21],w[N];
int Min(int x,int y){
    return (dfn[x]<dfn[y])?x:y;
}
inline int lca(int x,int y){
    if(x==y)return rdfn[x];
    if(x>y)swap(x,y);
    int k=__lg(y-x);
    x++;
    return Min(st[x][k],st[y-(1<<k)+1][k]);
}
inline void dfs(int x){
    dep[x]=dep[fa[x]]+1;
    dfn[x]=++tot;rdfn[tot]=x;
    st[tot][0]=fa[x];
    for(auto v:e[x]){
        dfs(v);
    }
}
bitset<N>vis;
vector<int>p[N];
int ans=-1,a=0,b=0;
namespace chain{
    int f[N][2],g[N][2];
    inline void upd(int x,int val,int id){
        if(!id)return;
        if(g[x][0]==id){
            f[x][0]=min(f[x][0],val);
            return;
        }
        if(g[x][1]==id){
            f[x][1]=min(f[x][1],val);
            if(f[x][1]<f[x][0]){
                swap(f[x][1],f[x][0]);
                swap(g[x][1],g[x][0]);
            }
            return;
        }
        if(val<f[x][0]){
            f[x][1]=f[x][0];g[x][1]=g[x][0];
            f[x][0]=val;g[x][0]=id;
        }else if(val<f[x][1]){
            f[x][1]=val;g[x][1]=id;
        }
    }
    inline void dfs(int u){
        f[u][0]=f[u][1]=inf;
        g[u][0]=g[u][1]=0;
        for(auto v:p[u]){
            upd(u,dep[w[v]],v);
        }
        for(auto v:e[u]){
            dfs(v);
            upd(u,f[v][0],g[v][0]);
            upd(u,f[v][1],g[v][1]);
        }
        if(g[u][0]&&g[u][1]){
            int res=dep[u]-f[u][1];
            if(res>ans){
                ans=res;
                a=g[u][0],b=g[u][1];
            }
        }
    }
}
namespace inflex{
    int oth(int x,int id){
        return u[id]==x?v[id]:u[id];
    }
    struct node{
        int x,id;
        bool operator <(const node &a)const{
            int p=oth(a.x,a.id),q=oth(x,id);
            if(dfn[p]!=dfn[q])return dfn[q]<dfn[p];
            else return (id<a.id||((id==a.id)&&x<a.x));
        }
    };
    multiset<node>s[N];
    void chk(int x,node l,node r){
        if(l.id==r.id)return;
        if(w[l.id]!=w[r.id])return;
        if(lca(dfn[l.x],dfn[r.x])!=x)return;
        int p=oth(l.x,l.id),q=oth(r.x,r.id);
        int y=lca(dfn[p],dfn[q]);
        int res=dep[x]+dep[y]-2*dep[w[l.id]];
        if(res>ans){
            ans=res;
            a=l.id;b=r.id;
        }
    }
    void ins(int x,node l){
        auto it=s[x].lower_bound(l);
        if(it!=s[x].end()){
            chk(x,l,*it);
        }
        if(it!=s[x].begin()){
            chk(x,l,*prev(it));
        }
        s[x].emplace(l);
    }
    void dot(int u){
        for(auto v:p[u])ins(u,node{u,v});
        for(auto v:e[u]){
            dot(v);
            if(s[v].size()>s[u].size())swap(s[u],s[v]);
            for(auto i:s[v])ins(u,i);
            s[v].clear();
        }
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>k;
    for(int i=2;i<=n;i++){
        cin>>fa[i];
        e[fa[i]].push_back(i);
    }
    dfs(1);
    for(int j=1;(1<<j)<=tot;j++){
        for(int i=1;i+(1<<j)-1<=tot;i++){
            st[i][j]=Min(st[i][j-1],st[i+(1<<j-1)][j-1]);
        }
    }
    bool f1=1;
    for(int i=1;i<=k;i++){
        cin>>u[i]>>v[i];
        w[i]=lca(dfn[u[i]],dfn[v[i]]);
        f1&=(w[i]==u[i]||w[i]==v[i]);
        p[u[i]].push_back(i);
        p[v[i]].push_back(i);
    }
    chain::dfs(1);
    inflex::dot(1);
    if(ans>0)cout<<ans<<endl<<a<<" "<<b;
    else cout<<0<<endl<<1<<" "<<2;
    return 0;
}
posted @ 2026-10-11 20:18  farenara_sayowell  阅读(10)  评论(0)    收藏  举报