Loading

洛谷P3275 [SCOI2011]糖果(缩点+拓扑序DP)

题目描述

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

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

幼儿园的糖果总是有限的,lxhgww\text{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\text{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

posted @ 2020-05-04 11:02  脂环  阅读(329)  评论(0编辑  收藏  举报