zoukankan      html  css  js  c++  java
  • 洛谷P3275 [SCOI2011]糖果(缩点+拓扑序DP)

    题目描述

    幼儿园里有 NNN 个小朋友,lxhgww ext{lxhgww}lxhgww 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。

    但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,lxhgww ext{lxhgww}lxhgww 需要满足小朋友们的 KKK 个要求。

    幼儿园的糖果总是有限的,lxhgww ext{lxhgww}lxhgww 想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

    输入格式

    输入的第一行是两个整数 NNN,KKK。接下来 KKK 行,表示这些点需要满足的关系,每行 333 个数字,XXX,AAA,BBB。

    • 如果 X=1X=1X=1, 表示第 AAA 个小朋友分到的糖果必须和第 BBB 个小朋友分到的糖果一样多;
    • 如果 X=2X=2X=2, 表示第 AAA 个小朋友分到的糖果必须少于第 BBB 个小朋友分到的糖果;
    • 如果 X=3X=3X=3, 表示第 AAA 个小朋友分到的糖果必须不少于第 BBB 个小朋友分到的糖果;
    • 如果 X=4X=4X=4, 表示第 AAA 个小朋友分到的糖果必须多于第 BBB 个小朋友分到的糖果;
    • 如果 X=5X=5X=5, 表示第 AAA 个小朋友分到的糖果必须不多于第 BBB 个小朋友分到的糖果;

    输出格式

    输出一行,表示 lxhgww ext{lxhgww}lxhgww 老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1-11。

    输入输出样例

    输入 #1
    5 7
    1 1 2
    2 3 2
    4 4 1
    3 4 5
    5 4 5
    2 3 5
    4 5 1
    输出 #1
    11
    这个和bzoj银河那个题是一样的,好多坑TAT
    首先根据题干很容易看出lyd大佬的书上说这题是差分约束的题目,因为五种关系都可以转化为d[x]-d[y]>=0或d[x]-d[y]>=1。
    特别注意第一个条件d[x]-d[y]>=0且d[y]-d[x]>=0。
    但是这还不够,因为有一个隐含条件:每个小朋友都要分到,所以这提醒我们要设置一个超级源点0,对于任意的x,d[x]-d[0]>=1。
    根据差分约束的知识,对于d[x]-d[y]>=k这样的不等式(联想松弛条件),可以从y到x连一条长度为k的边,然后从超级源点出发求最长路,如果存在正环则无解。否则从超级源点到每个点的dist值就是对应的给每个小朋友的最少糖果数量。
    这时一般的思路是跑spfa。关于spfa它死了其实这个题可以用,只不过第六个点是一条1e5的长链,必须倒着才能过,这意味着我们需要一个更高效的算法。
    注意到书上写这个题的边权只有0或者1,而只要存在正环则无解,这就意味着只要每一个强连通分量里存在长度为1的边就无解,直接输出-1后return 0即可,这样可以在缩点建新图时顺便就判断了。
    然后就是按照拓扑序对新的DAG进行DP求出dist数组,如代码所示,外层循环是拓扑序的逆序,因为强连通分量的求解顺序的逆序已然是一个拓扑序,内层循环是扫描点的出边,更新邻近的点的距离。
    随后统计答案的时候要把每个dist值乘以相应的SCC的大小(因为点都缩到一起了
    坑点:1.数组一定要开足够大,不然WA+TLE 2.别用dfs去求dist数组,不然会在第六个点爆栈 3.注意遍历的时候有时需要从超级源点开始
    #include <bits/stdc++.h>
    #define N 600005//数组没开购会导致WA+TLE 玄学 
    using namespace std;
    int n,k;
    int head[N],ver[N],Next[N],edge[N],tot=0;
    long long d[N];
    int dfn[N],low[N];
    int sstack[N],ins[N],c[N];
    vector<int> scc[N];
    int num=0,top=0,cnt=0;
    int hc[N],vc[N],nc[N],ec[N],tc=0;
    void add(int x,int y,int z)//原图建边 
    {
        ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
    }
    void add_c(int x,int y,int z)//缩点后的图建边 
    {
        vc[++tc]=y,ec[tc]=z,nc[tc]=hc[x],hc[x]=tc;
    }
    void tarjan(int x)//tarjan板子 
    {
        dfn[x]=low[x]=++num;
        sstack[++top]=x,ins[x]=1;
        int i;
        for(i=head[x];i;i=Next[i])
        {
            if(!dfn[ver[i]])
            {
                tarjan(ver[i]);
                low[x]=min(low[x],low[ver[i]]);
            }
            else if(ins[ver[i]])
            {
                low[x]=min(low[x],dfn[ver[i]]);
            }
        }
        if(dfn[x]==low[x])
        {
            cnt++;
            int y;
            do
            {
                y=sstack[top--],ins[y]=0;
                c[y]=cnt,scc[cnt].push_back(y);
            }while(x!=y);
        }
    } 
    //void dfs(int x) dfs会爆栈 
    //{
    //    int i;
    //    for(i=hc[x];i;i=nc[i])
    //    {
    //        int y=vc[i],z=ec[i];
    //        d[y]=max(d[y],d[x]+(long long)z);
    //        dfs(y);
    //    }
    //}
    int main()
    {
        cin>>n>>k;
        int i,j;
        for(i=1;i<=k;i++)
        {
            int x,a,b;
            scanf("%d%d%d",&x,&a,&b);
            switch(x)
            {
                case 1:
                    add(a,b,0),add(b,a,0);
                    break;
                case 2:
                    if(a==b)
                    {
                        cout<<-1;
                        return 0;
                    }
                    add(a,b,1);
                    break;
                case 3:
                    add(b,a,0);
                    break;
                case 4:
                    if(a==b)
                    {
                        cout<<-1;
                        return 0;
                    }
                    add(b,a,1);
                    break;
                case 5:
                    add(a,b,0);
                    break;
            }
        }
        for(i=n;i>=1;i--)add(0,i,1);
        for(i=0;i<=n;i++)//由于设置了超级源点,一定要从0开始tarjan 
        {
            if(!dfn[i]) tarjan(i);
        }
        //无正环则求最长路 
        int x,y;
        for(x=0;x<=n;x++)//x得从0开始 因为设置了一个虚拟节点 
        {
            for(i=head[x];i;i=Next[i])
            {
                int y=ver[i];
                if(c[x]==c[y])
                {
                    if(edge[i]==1)
                    {
                        cout<<-1;
                        return 0;
                    }
                    continue;
                }
                add_c(c[x],c[y],edge[i]);
            }
        }
        long long ans=0;
        //dfs(0);//拓扑序dfs爆栈 
        for(x=cnt;x>=1;x--)
        {
            for(i=hc[x];i;i=nc[i])
            {
                int y=vc[i],z=ec[i];
                if(d[y]<d[x]+z)d[y]=d[x]+z;
            }
        }
        for(i=1;i<=cnt;i++)
        {
            ans+=d[i]*scc[i].size();
        }
        cout<<ans;
        return 0;
    }
     
    参考博客:https://blog.csdn.net/weixin_43701790/article/details/104962694#comments_12092989

  • 相关阅读:
    SpringBoot-13-简单整合Dubbo
    SpringBoot-12-整合Redis
    SpringBoot-11-任务
    SpringBoot-10-Swagger
    SpringBoot-09-Apche Shiro
    SpringBoot-08-Spring Security
    五十七-代码审计-JAVA项目框架类漏洞分析报告
    五十六:代码审计-JAVA项目Filter过滤器及XSS挖掘
    五十五:代码审计-JAVA项目注入上传搜索或插件挖掘
    五十四:代码审计-TP5框架审计写法分析及代码追踪
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/12825733.html
Copyright © 2011-2022 走看看