洛谷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-1−1。
输入输出样例
输入 #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