[BZOJ2330] [SCOI2011] 糖果 [差分约束][单源最短路][缩点][拓扑排序]

link

SPFA

题目要求求最小值。
建原点 0 \mathfrak{0} ,也就是要 d i s [ x ] d i s [ 0 ] \mathfrak{\sum{dis[x]-dis[0]}} 最小。
最小值受到 d i s [ x ] d i s [ 0 ] v a l [ x ] [ 0 ] \mathfrak{dis[x]-dis[0]\ge val[x][0]} 的约束也即 d i s [ 0 ] + v a l [ x ] [ 0 ] d i s [ x ] \mathfrak{dis[0]+val[x][0]\le dis[x]}
连边跑差分约束,最长路。
然后最长路就 d i j k s t r a \mathfrak{dijkstra} 用不了了,不过数据范围也不允许 B e l l m a n F o r d \mathfrak{Bellman-Ford}
总之就用 s p f a \mathfrak{spfa} 期望过一个吧(

一定有约束,所以不用判连通。不过要判负环(输出 " 1 " \mathfrak{"-1"} 的情况)

听说这题甚至构造数据试图卡 s p f a \mathfrak{spfa} ?有点恐怖的,难道还能 d i j k s t r a \mathfrak{dijkstra}
(不过好像也可以缩点+拓扑排序的方式来做)
总之 s l f + l f u \mathfrak{slf}+\mathfrak{lfu} 大法好

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
using namespace std;
#define getchar() (frS==frT&&(frT=(frS=frBB)+fread(frBB,1,1<<12,stdin),frS==frT)?EOF:*frS++)
#define ll long long
char frBB[1<<12],*frS=frBB,*frT=frBB;
inline void read(int&T)
{
    int x=0;char ch=getchar();bool w=0;
    while(!isdigit(ch))w|=(ch=='-'),ch=getchar();
    while(isdigit(ch))x=x*10+(ch-48),ch=getchar();
    T=w?-x:x;
}
inline void write(ll T)
{
    if(T<0)putchar('-'),T=-T;
    if(T>9)write(T/10);
    putchar(T%10+48);
}
#define add_edge(a,b,c) nxt[++tot]=head[a],head[a]=tot,to[tot]=b,val[tot]=c,slf-=c
int limR,limL,slf;
int N,K,tot=0;
ll ans=0;
int nxt[300005]={},to[300005]={},head[100005]={},val[300005]={};
int dis[100005]={},ext[100005]={},vt[100005]={};
bool vis[100005]={};
deque<int>Q;
bool spfa()
{
    memset(dis,0x3f,sizeof(dis));
    Q.push_back(0); dis[0]=0; ext[0]=0; vt[0]=1;
    int cnt=0;
    while(!Q.empty())
    {
        int x=Q.front(); vis[x]=0; Q.pop_front(); ++cnt;
        for(int i=head[x];i;i=nxt[i])
        {
            if(dis[to[i]]>dis[x]+val[i])
            {
                dis[to[i]]=dis[x]+val[i];
                if(!vis[to[i]])
                {
                    vis[to[i]]=1; ++vt[to[i]];
                    if((((!Q.empty())&&dis[to[i]]>dis[Q.front()]+slf))||(vt[to[i]]<=limR&&vt[to[i]]>=limL))Q.push_back(to[i]);
                    else Q.push_front(to[i]);
                    ext[to[i]]=ext[x]+1;
                    if(ext[to[i]]>N)return 1;
                }
            }
        }
    }
    return 0;
}
int main()
{
    limL=2; limR=sqrt(N);
    read(N); read(K);
    for(int X,A,B,i=1;i<=K;++i)
    {
        read(X); read(A); read(B);
        switch(X)
        {
            case 1:
                add_edge(A,B,0),add_edge(B,A,0);
                break;
            case 2:
                if(A==B)
                {
                    printf("-1");
                    return 0;
                }
                add_edge(A,B,-1);
                break;
            case 3:
                add_edge(B,A,0);
                break;
            case 4:
                if(A==B)
                {
                    printf("-1");
                    return 0;
                }
                add_edge(B,A,-1);
                break;
            case 5:
                add_edge(A,B,0);
                break;
        }
    }
    for(int i=1;i<=N;++i)add_edge(0,i,-1);
    slf=sqrt(slf);
    if(spfa())
    {
        puts("-1");
        return 0;
    }
    for(int i=1;i<=N;++i)ans+=dis[i];
    write(-ans);
    return 0;
}


缩点+拓扑排序

s p f a \mathfrak{spfa} 的复杂度上界是 Θ ( N M ) \mathcal{\Theta(NM)} ,所以 s p f a \mathfrak{spfa} 跑差分约束的做法其实不是正解。

图中只有五种约束关系。
如果关系不成环,那就很好做了;如果关系不成环呢?
1 \mathfrak{1} 代表两个点一样,可以缩成一个。
关系 2 , 4 \mathfrak{2,4} 是差不多的,如果靠这两种关系成环,环里面的点值都相同,也可以缩点。

3 , 5 \mathfrak{3,5} 成环无解。
所以先缩点,然后用剩下的 2 , 4 \mathfrak{2,4} 3 , 5 \mathfrak{3,5} 分别统计。

码力底下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
using namespace std;
#define getchar() (frS==frT&&(frT=(frS=frBB)+fread(frBB,1,1<<12,stdin),frS==frT)?EOF:*frS++)
#define ll long long
char frBB[1<<12],*frS=frBB,*frT=frBB;
inline void read(int&T)
{
    int x=0;char ch=getchar();bool w=0;
    while(!isdigit(ch))w|=(ch=='-'),ch=getchar();
    while(isdigit(ch))x=x*10+(ch-48),ch=getchar();
    T=w?-x:x;
}
inline void write(ll T)
{
	if(T<0)putchar('-'),T=-T;
	if(T>9)write(T/10);
	putchar(T%10+48);
}
#define add_edge(a,b) nxt[++tot]=head[a],head[a]=tot,to[tot]=b
#define readd_edge(a,b,c) bnxt[++tot]=bhead[a],bhead[a]=tot,bto[tot]=b,type[tot]=c,++in[b]
int N,K,tot=0;
long long ans=0;
int head[100005]={},to[200005]={},nxt[200005]={},dfn[100005]={},low[100005]={};
int dfs_id=0;
int stk[100005]={},col[100005]={},siz[100005]={};
int bhead[100005]={},bto[200005]={},bnxt[200005]={};
bool type[200005]={};
struct adt
{
	int x,u,v;
	adt(int a=0,int b=0,int c=0)
	{
		x=a; u=b; v=c;
	}
}E[200005];
void tarjan(int x)
{
	dfn[x]=low[x]=++dfs_id; stk[++stk[0]]=x;
	for(int i=head[x];i;i=nxt[i])
	{
		if(!dfn[to[i]])
		{
			tarjan(to[i]);
			low[x]=min(low[x],low[to[i]]);
		}
		else if(!col[to[i]])
		{
			low[x]=min(low[x],dfn[to[i]]);
		}
	}
	if(low[x]==dfn[x])
	{
		++col[0];
		while(stk[0])
		{
			col[stk[stk[0]]]=col[0];
			++siz[col[0]];
			if(stk[stk[0]--]==x)break;
		}
	}
}
queue<int>Q;
int in[100005]={};
int an[100005]={};
void topo_sort()
{
	for(int i=1;i<=col[0];++i)if(!in[i])Q.push(i),an[i]=1;
	while(!Q.empty())
	{
		int x=Q.front(); Q.pop();
		for(int i=bhead[x];i;i=bnxt[i])
		{
			--in[bto[i]];
			if(!in[bto[i]])Q.push(bto[i]);
			an[bto[i]]=max(an[bto[i]],an[x]+type[i]);
		}
	}
	for(int i=1;i<=col[0];++i) if(in[i]) { printf("-1"); exit(0);}
}
int main()
{
	scanf("%d%d",&N,&K);
	for(int x,u,v,i=1;i<=K;++i)
	{
		scanf("%d%d%d",&E[i].x,&E[i].u,&E[i].v);
		switch(E[i].x)
		{
			case 1:
				add_edge(E[i].u,E[i].v);
				add_edge(E[i].v,E[i].u);
				break;
			case 3:
				add_edge(E[i].v,E[i].u);
				break;
			case 5:
				add_edge(E[i].u,E[i].v);
				break;
			default:break;
		}
	}
	for(int i=1;i<=N;++i)if(!dfn[i])tarjan(i);
	tot=0;
	for(int i=1;i<=N;++i)
	{
		for(int j=head[i];j;j=nxt[j])
		{
			if(col[i]!=col[to[j]])readd_edge(col[i],col[to[j]],0);
		}
	}
	for(int i=1;i<=K;++i)
	{
		if(E[i].x==2)
		{
			if(col[E[i].u]==col[E[i].v])
			{
				printf("-1");
				return 0;
			}
			readd_edge(col[E[i].u],col[E[i].v],1);
		}
		if(E[i].x==4)
		{
			if(col[E[i].u]==col[E[i].v])
			{
				printf("-1");
				return 0;
			}
			readd_edge(col[E[i].v],col[E[i].u],1);
		}
	}
	topo_sort();
	for(int i=1;i<=col[0];++i)ans+=1ll*an[i]*siz[i];
	printf("%lld",ans);
    return 0;
}

Summary

差分约束问题有时候可以用拓扑排序求解
(不考虑缩点之类的追加部分的话这好像有专门的叫法,叫做求关键路径)
用拓扑排序的话,相对于最短路可以降低复杂度。

实际上差分约束更多是一种解决问题的思考方法这样的地位

猜你喜欢

转载自blog.csdn.net/Estia_/article/details/83185720