1 条题解

  • 0
    @ 2025-8-24 22:07:50

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar mydiplomacy
    **

    搬运于2025-08-24 22:07:50,当前版本为作者最后更新于2018-12-20 18:19:58,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    如果A同学观察B同学,那么我们连一条从点A到点B的有向边。

    在下面的陈述中,我们将“A同学收到指令”称为“点A被染色”。

    那么我们就可以把题意转化为:

    给定一个有向图,已知当一个点连向的点被染色时,那么这个点也被染色。支持两种操作:

    查询操作:给定区间[L,R][L,R]内的所有点被染色,那么求还需要给多少点染色,才能使最终所有点被染色。

    修改操作:给定区间[L,R][L,R]xx,将编号在区间[L,R][L,R]内的所有点连向x。

    下面考虑做法。

    • 20分:

      完全暴力即可。对于每个查询操作,首先忽略所有[L,R][L,R]内的点和[L,R][L,R]被染色后被染色的点。接下来枚举每个点是否被选中,对于每种方案进行判定是否合法,最后输出最小答案。

    • 50分:

      需要进一步研究性质。

      首先,由于边只会从编号大的点连到编号小的点,那么这个图一定是一个有向无环图。

      观察所有出度为零(指图中不存在这个点连出的边)的点。我们可以证明:

      • 如果这些点当中的每个点,如果这个点没被[L,R][L,R]包含,那么这个点必须被染色。

        证明:由于这个点没有连出的边,那么如果任何其他点被染色了,这个点都不会被染色。

      • 当所有这些点被染色了,那么所有点都会被染色。

        证明:假设存在一个点,当所有这些点被染色时,还没有被染色。于是我们可以推出,这个点所连向的所有点都没有被染色。所以,这个点所连向的点所连向的点也没有被染色,以此类推。由于点的数量是有限的,所以必定会出现下列情况之一:

        • 这个点所连向的点所连向的点...所连向的点,与这个点重合,或者与另外一个这个点所连向的点...所连向的点重合。这与无环矛盾。
        • 这个点所连向的点...所连向的点,没有连出的边。由于我们前面的推导,这个点一定没有染色。而这又与我们假设的前提矛盾。

        所以,假设不成立!

      于是,所有点被染色的充分必要条件是所有出度为零的点被染色。所以这道题的询问操作就转化为了:查询有多少出度为零的点,不在区间[L,R][L,R]内。只需首先预处理 出入度为零的点,修改操作时移除所有区间内的点,均可以O(n)O(n)处理。

      50分代码见下面参考代码1。

    • 70分(所有具备特殊性质的数据点):

      我们可以首先O(m)O(m)预处理每个点是否为出度为零的点并记录,接下来O(n)O(n)预处理出sum[i]sum[i],代表11~ii中有多少个出度为零的点。

      对于每个修改操作,只需要将[L,R][L,R]内的每个点标记为出度非零,再根据之前处理出的每个点的是否入度为零标记重新计算sumsum数组。效率O(n)

      对于每个查询操作,利用前缀和O(1)O(1)查询[1,L1][1,L-1][R+1,N][R+1,N]内共有多少个出度非零的点,然后输出。效率O(1)O(1)

      但由于修改不超过100组,可以得到部分分。

      70分代码见下面参考代码2。

    • 100分:

      利用线段树等数据结构维护数组aaa[i]a[i]代表是否出度为零,如果是则为1,否则为0。

      查询操作相当于查询[1,L1][1,L-1]的和加上[R+1,N][R+1,N]的和。

      修改操作相当于将[L,R][L,R]区间区间覆盖为0。

      问题得解。

      std见下面参考代码3。

    参考代码1:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    
    using namespace std;
    
    const int maxn=300005;
    
    int d[maxn]; //d代表:当前节点出度是否为零。1:出度为零,0:出度非零
    
    int main()
    {
        int n,q;
        scanf("%d %d",&n,&q);
        for(int i=1;i<=n;i++) d[i]=1;
        for(int i=1;i<=n;i++)
        {
            int x,y;
            scanf("%d",&x);
            for(int j=1;j<=x;j++)
            {
                scanf("%d",&y);
                d[y]=0; //出度非零
            }
        }
        for(int i=1;i<=q;i++)
        {
            int opt,x,y,z;
            scanf("%d",&opt);
            if(opt==1)
            {
                int sum=0;
                scanf("%d %d",&x,&y);
                for(int j=1;j<x;j++) 
                    sum+=d[j];
                for(int j=y+1;j<=n;j++)
                    sum+=d[j];
                printf("%d\n",sum);
            }
            if(opt==2)
            {
                scanf("%d %d %d",&x,&y,&z);
                for(int i=x;i<=y;i++) d[i]=0;
            }
        }
        return 0;
    }
    

    参考代码2:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    
    using namespace std;
    
    const int maxn=300005;
    
    int d[maxn],s[maxn]; //s:前缀和
    
    int main()
    {
        int n,q;
        scanf("%d %d",&n,&q);
        for(int i=1;i<=n;i++) d[i]=1;
        for(int i=1;i<=n;i++)
        {
            int x,y;
            scanf("%d",&x);
            for(int j=1;j<=x;j++)
            {
                scanf("%d",&y);
                d[y]=0;
            }
        }
        for(int i=1;i<=n;i++) s[i]=s[i-1]+d[i];
        for(int i=1;i<=q;i++)
        {
            int opt,x,y,z;
            scanf("%d",&opt);
            if(opt==1)
            {
                int sum=0;
                scanf("%d %d",&x,&y);
                sum=s[x-1]+s[n]-s[y];
                printf("%d\n",sum);
            }
            if(opt==2)
            {
                scanf("%d %d %d",&x,&y,&z);
                for(int i=x;i<=y;i++) d[i]=0; 
                for(int i=1;i<=n;i++) s[i]=s[i-1]+d[i]; //修改操作后,重新计算前缀和
            }
        }
        return 0;
    }
    

    参考代码3:

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int maxn=900005;
    
    struct Node //线段树节点
    {
        int left, right;
        int sum, lazy;
    }a[maxn];
    
    int ab[maxn],x,y,z,opt,d[maxn];
    int n,q;
    
    void buildtree(int id, int l, int r)
    {
        a[id].left=l; a[id].right=r; a[id].lazy=0;
        if(l==r)
        {
            if(d[l]==1)
                a[id].sum=0;
            else
                a[id].sum=1;
            return;
        }
        int mid=(l+r)/2;
        buildtree(id<<1,l,mid);
        buildtree(id<<1|1,mid+1,r);
        a[id].sum=a[id<<1].sum+a[id<<1|1].sum;
    }
    
    void pushdown(int id)
    {
        if(a[id].lazy==1)
        {
            a[id].sum=0;
            a[id<<1].lazy=1;
            a[id<<1|1].lazy=1;
            a[id].lazy=0;
        }
    }
    
    int query(int id, int l, int r)
    {
        if(a[id].left==l && a[id].right==r)
        {
            if(a[id].lazy==0)
                return a[id].sum;
            else
                return 0;
        }
        pushdown(id);
        if(r<=a[id<<1].right) return query(id<<1,l,r);
        else if(l>=a[id<<1|1].left) return query(id<<1|1,l,r);
        else
        {
            return query(id<<1,l,a[id<<1].right)+query(id<<1|1,a[id<<1|1].left,r);
        }
    }
    
    void change(int id, int l, int r)
    {
        if(a[id].left==l && a[id].right==r)
        {
            a[id].lazy=1;
            return;
        }
        pushdown(id);
        if(r<=a[id<<1].right) change(id<<1,l,r);
        else if(l>=a[id<<1|1].left) change(id<<1|1,l,r);
        else
        {
            change(id<<1,l,a[id<<1].right);
            change(id<<1|1,a[id<<1|1].left,r);
        }
        a[id].sum=(a[id<<1].lazy==0?a[id<<1].sum:0)+(a[id<<1|1].lazy==0?a[id<<1|1].sum:0);
    }
    
    int main()
    {
        scanf("%d %d",&n,&q);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&ab[i]);
            for(int j=1;j<=ab[i];j++)
            {
                scanf("%d",&x);
                d[x]=1;
            }
        }
        buildtree(1,1,n);
        for(int i=1;i<=q;i++)
        {
            scanf("%d",&opt);
            if(opt==1)
            {
                scanf("%d %d",&x,&y);
                printf("%d\n",query(1,1,n)-query(1,x,y));
            }
            else
            {
                scanf("%d %d %d",&x,&y,&z);
                change(1,x,y);
            }
        }
        return 0;
    }
    
    • 1

    信息

    ID
    4073
    时间
    1000ms
    内存
    125MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者