【BZOJ2330】【SCOI2011】糖果——差分约束系统+tarjan

10 篇文章 1 订阅
8 篇文章 0 订阅

题目链接

BZOJ2330

差分约束

这是一道经典的差分约束问题
我们假设最后第i个小朋友分得的糖果数为 ai,aiN
那么对于约束条件:i分得的糖果少于j的,有 ai<aj ,由于 ai 是整数,可以变形为 aiaj+(1)
同样,对于 aiaj 也可以看成 aiaj+0
这些条件是不是很像最短路的最终状态中有 djdi+ei,j
于是我们可以将问题转换成最短路/最长路求解
如果存在一个符合要求的最短路(最长路)的解,那么每个点的最短路就是分配的糖果数。而如果因为出现负环没有最短路(出现正环没有最长路),那么原问题无解
如何保障所有的 ai>0 呢?我们新加入一个超级节点s,使得 as=0 ,并且新加入n个限制 as<ai,i[1,n] ,这样既不影响答案,又能保证性质

最短路?最长路?

不难发现,以上两个限制也可以写成:

ajai+1

ajai+0

这样就符合了最长路的限制 djdi+ei,j
那么最短路和最长路,使用哪一种都可以,没有区别吗?
其实不然,因为 最短路可以最大化 ai ,而最长路可以最小化
为什么呢?因为最短路使得每一个点的权值都是由一条边更新来,而且这个点的权值无法再大,否则就会不符合该边限制。最长路则正好相反,使得所有点权值无法再小。
这题我们要最小化 ai ,故考虑最长路

SPFA做法的缺陷

因为需要判环,考虑通过限制SPFA中节点入队次数来判
每个节点最多入队n次,若环长为n,环上每个节点都会入队O(n)次,总体复杂度 O(N2) 理论上是会T的(虽然大家都是这么写的也没人T,郁闷)
因此我们需要考虑更高效的判环方法

tarjan求SCC+拓扑图DP

考虑什么样的环是正环
显然边权只有两种,正的和0
如果一个环上有一条边是正的,那么整个环就是正环
如果没有,那么整个环的值应当相同
将环的结论拓展到SCC仍然适用
于是我们可以求出所有SCC,若SCC内存在某正边,直接输出-1
若不存在,可将SCC缩为一个点共同处理
缩点之后为一个拓扑图
我们可以每次找入度为0的点到其他点更新最长路,因为这个点已经不能被其他点更新了
至此我们解决了这个问题
那么这个思想可不可以应用到其他查分约束系统呢
答案是未必
只有边权全为非负的最长路和边权全为非正的最短路可以,而一般的差分约束并不保证这一点

代码

#include<stack> 
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100006
#define long long long
struct edge
{
    int v;
    int next;
    int w;
    edge(int v,int n,int w):v(v),next(n),w(w){}
    edge(){}
}e[maxn*3],e2[maxn*3];
int n,m;
int newedge,dfsclock,newcolor;
int ind[maxn],ind2[maxn];
bool flag=true;
void addedge(edge *e,int *ind,int u,int v,int a)
{
    //if(e-e2==0)printf("edge:%d %d %d\n",u,v,a);
    e[++newedge]=edge(v,ind[u],a);
    ind[u]=newedge;
}
long d[maxn];
int dfn[maxn],low[maxn];
int deg[maxn];
int color[maxn];
int s[maxn];
stack<int> stk;
queue<int> que;
void dfs(int u)
{
    dfn[u]=low[u]=++dfsclock;
    stk.push(u);
    for(int i=ind[u];i;i=e[i].next)
    {
        int v=e[i].v;
        if(!dfn[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else
            if(!color[v])
                low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        ++newcolor;
        s[newcolor]=1;
        while(stk.top()!=u)
        {
            color[stk.top()]=newcolor;
            s[newcolor]++;
            que.push(stk.top());
            stk.pop();
        }
        color[u]=newcolor;
        que.push(u);
        stk.pop();
        while(!que.empty())
        {
            int t=que.front();
            for(int i=ind[t];i;i=e[i].next)
                if(color[e[i].v]==newcolor&&e[i].w==1)
                    flag=false;
            que.pop();
        }
    }
    return;
}           
long ans;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        addedge(e,ind,0,i,1);
    for(int i=1;i<=m;i++)
    {
        int x,a,b;
        scanf("%d%d%d",&x,&a,&b);
        switch(x)
        {
            case 1:
                addedge(e,ind,a,b,0);
                addedge(e,ind,b,a,0);
                break;
            case 2:
                addedge(e,ind,a,b,1);
                break;
            case 3:
                addedge(e,ind,b,a,0);
                break;
            case 4:
                addedge(e,ind,b,a,1);
                break;
            case 5:
                addedge(e,ind,a,b,0);
                break;
            default:puts("QAQ");
        }
    }
    dfs(0);
    if(!flag)
    {
        puts("-1");
        return 0;
    }
    newedge=0;
    for(int u=0;u<=n;u++)
        for(int i=ind[u];i;i=e[i].next)
        {
            if(color[u]==color[e[i].v])
                continue;
            addedge(e2,ind2,color[u],color[e[i].v],e[i].w);
            deg[color[e[i].v]]++;
        }
    que.push(color[0]);
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        for(int i=ind2[u];i;i=e2[i].next)
        {
            int v=e2[i].v;
            d[v]=max(d[u]+e2[i].w,d[v]);
            deg[v]--;
            if(!deg[v])
                que.push(v);
        }
    }
    for(int i=1;i<=newcolor;i++)
        ans+=(long)d[i]*s[i];
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值