D121 最短路→差分约束 Tarjan+拓扑 P3275 [SCOI2011] 糖果

D121 最短路→差分约束 Tarjan+拓扑 P3275 [SCOI2011] 糖果_哔哩哔哩_bilibili

 

P3275 [SCOI2011] 糖果 - 洛谷             P10935 银河 - 洛谷

求满足所有约束条件的最小解。$1≤N,K≤10^5,1≤X≤5,1≤A,B≤N$。

思路

本题是差分约束系统,但不能直接用 SPFA 跑最长路,$O(NK)$ 受不了

贪心思想,发现建图的边权只有 $0 或 1$,$X=1,3,5$,边权均取 $0$,$X=2,4$,边权均取 $1$

$X=1,A=B → add(A,B,0),add(B,A,0)$

$X=2,A<B → add(A,B,1)$

$X=3,A\ge B → add(B,A,0)$

$X=4,A>B → add(B,A,1)$

$X=5,A\le B → add(A,B,0)$

先 Tarjan 缩点,如果一个 SCC 内部出现了一条 u 到 v 的边权为 1 的边(表示 v 点的糖果数多 1),那么一定存在一条 v 到 u 的路径,由于边权  $≥0$,所以一定会出现正环,则这个差分约束系统无解

如果所有的 SCC 内部均是边权为 0 的边,那么 SCC 内部的点取值相同,缩点后就变成了一个 DAG 上的最长路,拓扑排序即可

相关板子:

D116【模板】最短路→差分约束 SPFA 算法 P5960 差分约束 - 董晓 - 博客园

D14【模板】强连通分量 Tarjan 算法 - 董晓 - 博客园

D01【模板】拓扑排序 - 董晓 - 博客园

 

// 差分约束 Tarjan+拓扑 O(N+M)
#include<bits/stdc++.h>
using namespace std;

const int N=100010;
vector<pair<int,int>>e[N],ne[N];
int n,k;
int dfn[N],low[N],tim,stk[N],top,scc[N],siz[N],cnt;

void tarjan(int u){
  dfn[u]=low[u]=++tim; stk[++top]=u;
  for(auto [v,w]:e[u]){
    if(!dfn[v]){ //若v尚未访问
      tarjan(v);
      low[u]=min(low[u],low[v]);
    }
    else if(!scc[v]) //若v已访问且未构成SCC
      low[u]=min(low[u],dfn[v]);
  }

  if(low[u]==dfn[u]){ //若u不是SCC的根,则low<dfn
    ++cnt; //缩点的个数
    for(int v=-1;v!=u;){
      v=stk[top--];
      scc[v]=cnt; //缩点的编号
      ++siz[cnt]; //缩点的大小
    }
  }
}
int rd[N],f[N];
void build(){
  for(int u=1;u<=n; u++){
    for(auto [v,w]:e[u]){
      int x=scc[u],y=scc[v]; //缩点的编号
      if(x==y && w==1){ //如果是同一缩点且边权为1
        puts("-1");
        exit(0);
      }
      if(x!=y){ //如果是不同的缩点
        ne[x].push_back({y,w}); //缩点连新边
        rd[y]++; //记录入度
      }
    }
  } 
}
void topo(){
  queue<int>q;
  for(int i=1;i<=cnt; i++)if(!rd[i])q.push(i),f[i]=1;
  while(!q.empty()){
    int u=q.front();q.pop();
    for(auto [v,w]:ne[u]){
      f[v]=max(f[v],f[u]+w); //v点的点权(每点的糖果数)
      if(--rd[v]==0) q.push(v);
    }
  }
}
int main(){
  scanf("%d%d",&n,&k);
  for(int i=1,op,a,b;i<=k; i++){
    scanf("%d%d%d",&op,&a,&b);
    if(op==1) e[a].push_back({b,0}),e[b].push_back({a,0}); //a=b
    else if(op==2) e[a].push_back({b,1}); //a<b
    else if(op==3) e[b].push_back({a,0}); //a>=b
    else if(op==4) e[b].push_back({a,1}); //a>b
    else if(op==5) e[a].push_back({b,0}); //a<=b
  }
  
  for(int i=1;i<=n; i++)if(!dfn[i])tarjan(i);
  build(); //建DAG
  topo();  //拓扑
  long long ans=0;
  for(int i=1;i<=cnt; i++)ans+=1ll*f[i]*siz[i]; //每点的糖果数*缩点大小
  printf("%lld\n",ans);
}

 

posted @ 2026-03-30 19:35  董晓  阅读(86)  评论(0)    收藏  举报