[Nowcoder] 银河 差分约束_spfa+超级源点 | Tarjan缩点

简介: Description银河中的恒星浩如烟海,但是我们只关注那些最亮的恒星。我们用一个正整数来表示恒星的亮度,数值越大则恒星就越亮,恒星的亮度最暗是 1。现在对于 N 颗我们关注的恒星,有 M 对亮度之间的相对关系已经判明。你的任务就是求出这 N 颗恒星的亮度值总和至少有多大。

Description


银河中的恒星浩如烟海,但是我们只关注那些最亮的恒星。我们用一个正整数来表示恒星的亮度,数值越大则恒星就越亮,恒星的亮度最暗是 1。现在对于 N 颗我们关注的恒星,有 M 对亮度之间的相对关系已经判明。你的任务就是求出这 N 颗恒星的亮度值总和至少有多大。


Input


第一行给出两个整数N和M。

之后M行,每行三个整数T,A,B,表示一对恒星(A,B)之间的亮度关系。恒星的编号从1开始。


如果T=1,说明A和B亮度相等。

如果T=2,说明A的亮度小于B的亮度。

如果T=3,说明A的亮度不小于B的亮度。

如果T=4,说明A的亮度大于B的亮度。

如果T=5,说明A的亮度不大于B的亮度。


Output


输出一个整数表示答案。


Samples


Input

5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1


Output


11


Hint


对于30%的数据,N≤100。

对于100%的数据,N≤100000,M≤100000。


评测地址

这个题可以用Tarjan缩点来进行处理


从题意的制约关系来看,也可以当作差分 约束 来处理


当用差分约束的时候,通过题意,我们可以了解到:题目要求我们求出N颗恒星亮度的最小值,那么我们就要求出这N个点亮度之和的最长路


然后就是普通的SPFA处理,注意在跑SPFA的时候,我们要判断是否存在一个正环,如果说存在一个正环,我们要直接输出-1,因为这个时候他的亮度值会不断的加大:


举个例子(1 > 2, 2 > 3, 3 > 4,4 > 1),这个时候我们就要像用SPFA判断是否存在负环的方式来进行判断是否存在正环,在处理的过程中,我们可以记录每个点进入队列的次数,如果说进入队列的次数大于n,说明就会有正环,但是这里其实还会有一种小的优化,如果使用queue的话,会被卡tle,但是如果是用stack就能通过这个题,其实,栈优化的SPFA在不存在负环的时候,会比队列优化的SPFA更快一些


如果代码是这个样子的:(会TLE)

微信图片_20220609151105.png

const int maxn = 1e6 + 7;
int n, m;
ll dis[maxn];
bool vis[maxn];
struct  node
{
    int v, nex;
    ll w;
} e[maxn];
int cnt, head[maxn];
int tot[maxn];
void init()
{
    for(int i = 0; i <= n; i++)
    {
        dis[i] = -9999999;
        head[i] = -1;
        tot[i] = 0;
    }
}
void add(int u, int v, int w)
{
    e[cnt].v = v;
    e[cnt].w = w;
    e[cnt].nex = head[u];
    head[u] = cnt++;
}
int flag = 1;
void spfa()
{
    queue <int> st;
    dis[0] = 0;
    st.push(0);
    vis[0] = 1;
    while(st.size())
    {
        int u = st.front();
        st.pop();
        vis[u] = 0;
        for(int i = head[u]; ~i; i = e[i].nex)
        {
            int to = e[i].v;
            if(dis[to] < dis[u] + e[i].w)
            {
                dis[to] = dis[u] + e[i].w;
                tot[to] = tot[u] + 1;
                if(tot[to] > n)
                {
                    flag = 0;
                    break;
                }
                if(vis[to] == 0)
                {
                    st.push(to);
                    vis[to] = 1;
                }
            }
        }
        if(flag == 0) break;
    }
}
int main()
{
    cin >> n >> m;
    init();
    for(int i = 1; i <= m; i++)
    {
        int op = read, u = read, v = read;
        if(op == 1) add(u, v, 0), add(v, u, 0);
        else if(op == 2) add(u, v, 1);
        else if(op == 3) add(v, u, 0);
        else if(op == 4) add(v, u, 1);
        else if(op == 5) add(u, v, 0);
    }
    for(int i = 1; i <= n; i++) add(0, i, 1);///超级源点
    spfa();
    /*
    for(int i = 1; i <= n; i++)
    {
        cout << dis[i] << endl;
    }*/
    if(flag)
    {
        ll res = 0;
        for(int i = 1; i <= n; i++)
        {
            res += dis[i];
        }
        cout << res << '\n';
        return 0;
    }
    puts("-1");
    return 0;
}
/**
**/


用栈维护的:(AC)


#define Clear(x,val) memset(x,val,sizeof x)
int n,m;
ll dis[maxn],head[maxn],cnt;
bool vis[maxn];
struct node {
  int u,to,w,nex;
} e[maxn << 1];
void init() {
  cnt = 0;
  for(int i=0; i<=n; i++) {
    head[i] = -1;
    dis[i] = -inf;
  }
}
void add(int u,int v,int w) {
  e[cnt].u = u;
  e[cnt].to = v;
  e[cnt].w = w;
  e[cnt].nex = head[u];
  head[u] = cnt ++;
}
int instk[maxn];
int flag;
void spfa(int u) {
  stack <int> stk;
  dis[u] = 0, vis[u] = 1;
  stk.push(u);
  while(stk.size()) {
    u = stk.top();
//    u = stk.front();
    stk.pop();
    vis[u] = 0;
//    cout << "vis : " << u << endl;
    for(int i=head[u]; ~i; i=e[i].nex) {
      int to = e[i].to, w = e[i].w;
      if(dis[to] < dis[u] + w) {
        dis[to] = dis[u] + w;
        instk[to] = instk[u] + 1;
        if(instk[to] > n) {
          flag = 1;
          return ;
        }
        if(!vis[to]) {
          stk.push(to);
          vis[to] = 1;
        }
      }
    }
  }
}
int main() {
  n = read,m = read;
  init();
  for(int i=1; i<=m; i++) {
    int op = read, u = read, v = read;
    if(op == 1) add(u,v,0),add(v,u,0);
    else if(op == 2) add(u,v,1);
    else if(op == 3) add(v,u,0);
    else if(op == 4) add(v,u,1);
    else add(u,v,0);
  }
  for(int i=1; i<=n; i++) add(0,i,1);
  spfa(0);
  ll ans = 0;
  if(flag) {
    puts("-1");
    return 0;
  }
  for(int i=1; i<=n; i++) {
    ans += dis[i];
  }
  cout << ans << endl;
  return 0;
}
/**
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
**/


Tarjan有向图的连通性:


可以把这个图看成是一个有向图,首先将这个图进行缩点,过程中得到每个点属于的强连通分量以及连通块的大小size,然后判断一个点所连的边中的点是不是在一个强连通分量之中,如果有两个点在同一个强连通分量之中,那就说明是-1的情况,直接输出-1即可

排除-1的情况之后,可以对缩完之后的点重新进行建图,对于新建完的图,我们获取他们的每个强连通的最小亮度,然后将每个强连通的亮度 * size相加起来就是答案


Code:


int n,m,cnt,head[maxn],h2[maxn],cnt2;
int dfc,dfn[maxn],low[maxn];
struct node {
  int to,nex,w;
} e[maxn << 1],e2[maxn << 1];
void init() {
  dfc = cnt2 = cnt = 0;
  for(int i=0; i<=n+10; i++) {
    head[i] = -1;
    h2[i] = -1;
    low[i] = dfn[i] = 0;
  }
}
void add(int u,int v,int w) {
  e[cnt].to = v;
  e[cnt].w = w;
  e[cnt].nex = head[u];
  head[u] = cnt ++;
}
void add2(int u,int v,int w) {
  e2[cnt2].to = v;
  e2[cnt2].w = w;
  e2[cnt2].nex = h2[u];
  h2[u] = cnt2 ++;
}
bool inStk[maxn];
stack<int> stk;
ll pos[maxn], cntSCC;
ll siz[maxn];
void Tarjan(int u) {
  dfn[u] = low[u] = ++ dfc;
  stk.push(u);
  inStk[u] = 1;
  for(int i=head[u]; ~i; i=e[i].nex) {
    int to = e[i].to;
    if(!dfn[to]) {
      Tarjan(to);
      low[u] = min(low[u],low[to]);
    } else if(inStk[to]) {///to in stack
      low[u] = min(low[u],dfn[to]);
    }
  }
  if(dfn[u] == low[u]) {
    int tp;
    ++ cntSCC;
    do {
      tp = stk.top();
      stk.pop();
      inStk[tp] = 0;
      siz[cntSCC] ++;
      pos[tp] = cntSCC;
    } while(tp != u);
  }
}
ll dis[maxn];
int main() {
  n = read,m = read;
  init();
  for(int i=1; i<=m; i++) {
    int op = read,u = read,v = read;
    if(op == 1) add(u,v,0),add(v,u,0);
    else if(op == 2) add(u,v,1);
    else if(op == 3) add(v,u,0);
    else if(op == 4) add(v,u,1);
    else add(u,v,0);
  }
  for(int i=1; i<=n; i++) add(0,i,1);
  Tarjan(0);
  for(int i=0; i<=n; i++) {
    int u = i;
    for(int j=head[u]; ~j; j=e[j].nex) {
      int to = e[j].to;
      if(pos[u] == pos[to]) {
        if(e[j].w) {
          puts("-1");
          return 0;
        }
      } else add2(pos[u],pos[to],e[j].w);
    }
  }
  ll ans = 0;
  /// use e2 not e and use h2
  for(int i=cntSCC; i; i--) {
    int u = i;
    for(int j=h2[u]; ~j; j=e2[j].nex) {
      int to = e2[j].to;
      dis[to] = max(dis[to],dis[u] + e2[j].w);
    }
  }
  for(int i=1; i<=cntSCC; i++) {
    ans += dis[i] * siz[i];
  }
  cout << ans << endl;
  return 0;
}
/**
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
**/


目录
相关文章
|
9月前
|
索引
洛谷P1231 教辅的组成
洛谷P1231 教辅的组成
洛谷1102 A-B 暴力法
判断第 i 个数和 i 之后的每一个数的绝对值是否等于目标结果
|
人工智能 vr&ar Perl
codeforces1509 C. The Sports Festival (区间DP)
codeforces1509 C. The Sports Festival (区间DP)
88 0
|
人工智能
codeforces455——A. Boredom(线性DP)
codeforces455——A. Boredom(线性DP)
102 0
codeforces455——A. Boredom(线性DP)
洛谷 P1469 找筷子
题目描述 经过一段时间的紧张筹备,电脑小组的“RP餐厅”终于开业了,这天,经理LXC接到了一个定餐大单,可把大家乐坏了!员工们齐心协力按要求准备好了套餐正准备派送时,突然碰到一个棘手的问题,筷子!CX小朋友找出了餐厅中所有的筷子,但遗憾的是这些筷子长短不一,而我们都知道筷子需要长度一样的才能组成一双,更麻烦的是CX找出来的这些筷子数量为奇数,但是巧合的是,这些筷子中只有一只筷子是落单的,其余都成双,善良的你,可以帮CX找出这只落单的筷子的长度吗? 输入输出格式 输入格式:   第一行读入一个数N,它代表CX找到的筷子的根数。
1190 0
|
算法
洛谷 P1816 忠诚
题目描述 老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。
1144 0