eJOI 2022 部分题解

eJOI 2022 部分题解

Adjacent Pairs

题意

     求把原序列变成一个 a,b,a,b,a,b, 形式(ab) 的序列且过程中不出现两个相邻的数相等至少需要多少次单点赋值。
     1n2×105.

题解

     简单的 n3 做法:枚举奇数和偶数位分别是哪两个数,但是注意到如果过程中会出现 b,a,a,b 这种东西那还要额外增加 len2 的操作。

     怎么优化到 n2? 对每个奇数位预处理出偶数位的答案和额外增加段的值。

     怎么更快?额外增加段与奇数和偶数取什么没有关系,所以把那段预处理出来。那这样我们就只用关心奇数和偶数位取什么数,那贪心选需要改的最少(也即出现最多的)即可。

代码

查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
map<int,int>M[200005];
int P[200005],arr[200005],C1[200005],C2[200005];
bool cmp(int a,int b){return C2[a]>C2[b];}
int main() {
    #ifndef ONLINE_JUDGE
    freopen("input","r",stdin);
    freopen("output","w",stdout);
    #endif
    int T,n;read(T);
    while(T--)
    {
        read(n);
        for(int i=1;i<=n;i++) C1[i]=C2[i]=0;
        for(int i=1,x;i<=n;i++)
        {
            M[i].clear();
            read(arr[i]); P[i]=i;
            if(i&1) C1[arr[i]]++;
            else C2[arr[i]]++;
        }
        sort(P+1,P+1+n,cmp);
        for(int i=1;i<=n;i++)
        {
            if(i&1) M[arr[i+1]][arr[i]]++;
            else M[arr[i]][arr[i+1]]++;
            if(arr[i]==arr[i+2]) i++;
        }
        int Ans=0;
        for(int i=1;i<=n;i++)
        {
            for(auto j:M[i]) Ans=max(Ans,C1[i]+C2[j.first]-j.second);
            for(int j=1;j<=n;j++)
            {
                if(!M[i].count(P[j])&&i!=P[j]) {Ans=max(Ans,C1[i]+C2[P[j]]);break;}
            }
        }
        printf("%d\n",n-Ans);
    }
    return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
*/

Where Is the Root?

题意

     交互,给出大小为 n 的树,每次询问一个点集,返回其公共 LCA 是否存在于该点集中。要求 9 次询问找到根。、
     保证存在一个点度数 3
     1n500.

题解

     注意到如果选择了所有的叶子那其实我们可以断言:若返回YES则根必定在点集之中,否则必定不在点集之中。

     显然 9 大概是 log500,考虑二分。

     每次二分的时候对一个 [l,r] 考虑,如果 [l,mid] 没有包含所有叶子那就把 [l,r] 外所有叶子加入一起询问,每次递归仍然存在根的那边即可。

     但是在只剩两个点并且还都是叶子的时候会出问题,所以我们假设若某个叶子是根,那把这个叶子放入不询问的集合,同时若当前不询问的集合旁边邻接的点度为 2 的点则把改点也加入不询问集合。之所以要不断更新是为了防止像下面这个数据一样:(rt=1)

1 2
2 3
3 4
3 5

     如果不询问集合只在 {1,2} ,那询问 3,4,5 答案是 YES ,于是你就会以为另一个备选的叶子为根。

代码

查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
int n,deg[505],P[505],Leaf;
bool cmp(int x,int y){return deg[x]<deg[y];}
vector <int> A;
void Ask()
{
    printf("? %d",(int)A.size());
    for(int i=0;i<(int)A.size();i++) printf(" %d",A[i]);
    puts("");A.clear();
    fflush(stdout);
}
int To[100005];
char op[10];
bool Ban[100005];
vector <int> G[100005];
void dfs(int u,int Fa)
{
    // cerr<<x<<endl;
    To[Fa]=u;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==Fa) continue;
        dfs(v,u);
    }
}
void Solve(int l,int r)
{
    // cerr<<l<<" "<<r<<endl;
    if(l==r)
    {
        printf("! %d\n",P[l]);
        fflush(stdout);
        exit(0);
    }
    if(r-l+1<=2&&r<=Leaf)
    {
        int x=P[l];
        // cerr<<x<<endl;
        dfs(x,0);
        while(deg[x]<=2)
        {
            // cerr<<x<<endl;
            Ban[x]=true;
            x=To[x];
        }
        Ban[x]=true;
        for(int i=1;i<=n;i++) if(!Ban[i]) A.push_back(i);
        Ask();scanf("%s",op+1);
        if(op[1]=='Y') Solve(r,r);
        else Solve(l,l);
    }
    int mid=(l+r)>>1;
    for(int i=l;i<=mid;i++) A.push_back(P[i]);
    for(int i=1;i<=Leaf&&i<l;i++) A.push_back(P[i]);
    Ask(); scanf("%s",op+1);
    if(op[1]=='Y') Solve(l,mid);
    else Solve(mid+1,r);
}
int main() {
    // #ifndef ONLINE_JUDGE
    // freopen("input","r",stdin);
    // freopen("output","w",stdout);
    // #endif
    read(n);
    for(int i=1,u,v;i<n;i++)
    {
        read(u);read(v);
        G[u].push_back(v);
        G[v].push_back(u);
        deg[u]++; deg[v]++;
        P[i]=i;
        To[u]=v; To[v]=u;
    }
    P[n]=n;
    // for(int i=1;i<=n;i++) cerr<<To[i]<<" ";
    sort(P+1,P+1+n,cmp);
    // for(int i=1;i<=n;i++) cerr<<P[i]<<" ";
    for(int i=1;i<=n;i++) if(deg[P[i]]==1&&deg[P[i+1]]>=2) {Leaf=i;break;}
    // cerr<<Leaf<<endl;
    Solve(1,n);
    return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。

叶子是重要的
当一组询问包含了所有叶子并且为YES时那必然 救赎之道,就在其中!(大雾
询问一个区间:
纯非叶:YES不能说明根在其中,NO说明 必然不在其中
叶非叶混合:若叶子不完全同上,否则同下
纯叶:YES说明必然在其中,NO说明必然不在其中
前两个YES太假了怎么办:把所有叶子也拿来放进去
这样就是真正区分区间了
*/

Bounded Spanning Tree

题意

     n 个点 m 条边的图,每条边有 [l,r] 之间的可选权值。要求给每条边赋权使得输入的前 n1 条边可以成为该图的一个最小生成树,且所有边的权值是 [1,m] 的排列。求构造方案或说明无解。
1n1m5×105.

题解

     考虑克鲁斯卡尔的过程,那我们应该满足后面的边连接的两个端点在树上的路径权值最大值 < 该边权值。

     所以我们考虑加强 [l,r] 的限制。具体来说先用倍增维护树上路径 l 最大值,然后对每条之后的边把它的权值下界赋为路径上的 l 最大值+1、对每条之后的边倍增把它的 r 拆分到树上路径边的最小值,然后具体到每条边上,这样树边的就是路径 r 最小值 1

     现在每条边在 [l,r] 内任选都是合法的(前提是区间 [l,r][1,m] 内的且 l>r),所以我们只需要考虑能不能构造成排列。把每个离线到 r 上, r 内部 l 按从小到大排序,并用 set 维护当前可用的权值。那么我们只需要 lower_bound 出最小的 >l 的值即可。把这个值赋给这条边并从 set 里删除,这样贪心显然是正确的。

代码

查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
template<typename T>void print(T x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
int T,n,m;
vector <PII> V[500005];
vector <PII> G[500005];
int l[500005],r[500005],lg[500005],dep[500005],Ans[500005];
int f[500005][21],Min[500005][21],Max[500005][21],FA[500005];
void dfs(int u,int fa)
{
    f[u][0]=fa; dep[u]=dep[fa]+1;
    for(int i=1;i<=lg[dep[u]];i++) f[u][i]=f[f[u][i-1]][i-1],Max[u][i]=max(Max[u][i-1],Max[f[u][i-1]][i-1]);
    for(int i=0;i<(int)G[u].size();i++)
    {
        int v=G[u][i].first,id=G[u][i].second;
        if(v==fa) continue;
        Max[v][0]=l[id]+1; FA[v]=id;
        dfs(v,u);
    }
}
void Maintain(int x,int y,int id)
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=lg[dep[x]];~i;i--)
    {
        if(dep[f[x][i]]>=dep[y])
        {
            l[id]=max(l[id],Max[x][i]);
            Min[x][i]=min(Min[x][i],r[id]);
            x=f[x][i];
        }
    }
    if(x==y) return;
    for(int i=lg[dep[x]];~i;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            l[id]=max(l[id],max(Max[x][i],Max[y][i]));
            Min[x][i]=min(Min[x][i],r[id]); Min[y][i]=min(Min[y][i],r[id]);
            x=f[x][i]; y=f[y][i];
        }
    }
    l[id]=max(l[id],max(Max[x][0],Max[y][0]));
    Min[x][0]=min(Min[x][0],r[id]); Min[y][0]=min(Min[y][0],r[id]);
}
inline bool check(int L,int R){return L<=R&&L>=1&&R>=1&&L<=m&&R<=m;}
int main() {
    #ifndef ONLINE_JUDGE
    freopen("input","r",stdin);
    freopen("output","w",stdout);
    #endif
    read(T);
    for(int i=1;i<=500000;i++) lg[i]=lg[i>>1]+1;
    while(T--)
    {
        read(n);read(m);
        for(int i=1;i<=n;i++)
        {
            G[i].clear(); 
            for(int j=0;j<=lg[n];j++) f[i][j]=Max[i][j]=0,Min[i][j]=1e9;
        }
        for(int i=1;i<=m;i++) V[i].clear();
        for(int i=1,u,v;i<n;i++)
        {
            read(u);read(v);read(l[i]);read(r[i]);
            G[u].push_back(mp(v,i)); G[v].push_back(mp(u,i));
        }
        dfs(1,0);//cerr<<"???"<<endl;
        for(int i=n,u,v;i<=m;i++)
        {
            read(u);read(v);read(l[i]);read(r[i]);
            Maintain(u,v,i);
        }
        for(int j=lg[n];j;j--)
        {
            for(int i=1;i<=n;i++)
            {
                Min[i][j-1]=min(Min[i][j-1],Min[i][j]);
                Min[f[i][j-1]][j-1]=min(Min[f[i][j-1]][j-1],Min[i][j]);
            }
        }
        for(int i=2;i<=n;i++) r[FA[i]]=min(r[FA[i]],Min[i][0]-1);
        bool fail=false;bool Sure=true;
        for(int i=1;i<=m;i++)
        {
            Sure&=(l[i]==r[i]);
            if(!check(l[i],r[i])) {fail=true;break;}
            V[r[i]].push_back(mp(l[i],i));
        }
        if(fail) {puts("NO");continue;}
        set<int>Ava;
        for(int i=1;i<=m;i++)
        {
            Ava.insert(i);
            sort(V[i].begin(),V[i].end());
            for(int j=0;j<(int)V[i].size();j++)
            {
                int L=V[i][j].first,id=V[i][j].second;
                set<int>::iterator it;
                if((it=Ava.lower_bound(L))==Ava.end()) {fail=true;break;}
                Ans[id]=*it; Ava.erase(it); 
            }
            if(fail) break;
        }
        if(fail) {puts("NO");continue;}
        puts("YES");
        for(int i=1;i<=m;i++) print(Ans[i]),putchar(' ');
        puts("");
    }
    return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
*/
posted @   Azazеl  阅读(324)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
历史上的今天:
2020-11-02 Final Zadanie 题解
点击右上角即可分享
微信分享提示