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 算法 - 董晓 - 博客园
// 差分约束 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); }
浙公网安备 33010602011771号