关于差分约束的一些总结和题解。(poj 1364 ,3159, 2983, 3169 ,1201 ,1716 ,1275)

这些天写了7道差分约束的题。以下是我的心得和总结。
1.写对不等式是关键。不仅是题中给出的显性的不等关系,还有诸如b>a等隐形的关系。不管重不重要,不等式一定要写全。
2.对于一些题,需要加入超级源点,并将该点与其他每一点连一条权值都相同(一般选0)的有向边。

3.注意最后的输出,到底是f[n],还是f[n+1],还是f[1]。若存在负环,则无最短路,存在正环,则无最长路。对于多组数据,要注意加边的结构体的数组大小(是否太小而导致re)。


poj 1364

样例输入:

4 2

1 2 gt 0

2 2 lt 2

1 2 gt 0

1 0 lt 0

0

样例输出:

lamentable kingdom
successful conspiracy

思路:

这道题的源点选错了。由于每个点都会使用,所以应该开一个超级源点,并将该点与其他的点连上权值为
k的有向边。并且由于不等式的不等号为严格的大于或小于,所以连边时应将权值加上1才能表示大于等于
或小于等于。
以后对于这种题,都可以开一个超级源点,将该点与其他的点连权值相同的有向边(一般选取0),然后
判断是否有环(即in[u]>n)。最长路有正环则无解,最短路有负环则无解。 


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
int n,m;
int head[110],vis[110],f[110],in[110];


struct edge
{
int v,w,next;
}e[100000];


void init()
{
freopen("king.in","r",stdin);
freopen("king.out","w",stdout);
}


int k=1;
void adde(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}


void work()
{
memset(vis,0,sizeof(vis));
memset(in,0,sizeof(in));
memset(f,-0x3f3f3f,sizeof(f));

queue<int>q;
f[n+1]=0;
vis[n+1]=1;
in[n+1]=1;
q.push(n+1);

int ok=0;

while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
if(in[u]>n)
{
ok=1;
break;
}

for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(f[u]+e[i].w>f[v])
{
f[v]=f[u]+e[i].w;
//printf("***%d  %d  %d\n",u,f[v],v);
if(vis[v]==0)
{
vis[v]=1;
q.push(v);
in[v]++;
}
}
}
}

if(ok==1)printf("successful conspiracy\n");
if(ok==0)printf("lamentable kingdom\n");
}


void read()
{
while(scanf("%d",&n)==1)
{
if(n==0)break;
scanf("%d",&m);

memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
int a,b,c;
char x,y;
scanf("%d%d",&a,&b);
scanf(" %c%c",&x,&y);
if(x=='g'&&y=='t')//大于 
{
scanf("%d",&c);
adde(a-1,a+b,c+1);
}
if(x=='l'&&y=='t')//小于 
{
scanf("%d",&c);
adde(a+b,a-1,1-c);
}
}

for(int i=0;i<=n;i++)
{
adde(n+1,i,0);
}
work();
}
}


int main()
{
init();
read();
return 0;
}


poj 3159

思路:

这道题我开始用spfa+队列来做就超时了,然后看某些大神的题解才知道用spfa+栈不会超时。
这道题好像是2008年四川的省选题。原题应该是考的是Dijkstra+堆。但是我还是写的是spfa+栈。

 

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<stack>
using namespace std;
#define maxn 30000+10
int n,m;
int head[maxn],vis[maxn],f[maxn];


struct edge
{
int v,next,w;
}e[150000+10];


int k=1;
void adde(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}


void work()
{
stack<int>q;
q.push(1);
vis[1]=1;
f[1]=0;

while(!q.empty())
{
int u=q.top();
q.pop();
vis[u]=0;

for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(f[u]+e[i].w<f[v])
{
f[v]=f[u]+e[i].w;
if(vis[v]==0)
{
vis[v]=1;
q.push(v);
}
}
}
}
printf("%d",f[n]);
}


int main()
{
memset(head,-1,sizeof(head));
memset(f,0x3f3f3f,sizeof(f));

scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
adde(a,b,c);
}

work();

return 0;
}


poj 2983

思路:

这道题对于精确的等式有一个巧妙地处理。
对于一个等式a-b=c。若要将它化为不等式,可以转化为两个不等式,即:
a-b>=c&&a-b<=c,我们对于这两个不等式求交集的话,就是a-b=c,所以这两者等价。
所以在读到精确信息之后,可以加一条b到a权值为c的边,和一条a到b权值为-c的边。


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define maxn 1000+3
int n,m;
int head[maxn],vis[maxn],f[maxn],in[maxn];


struct node
{
int v,w,next;
}e[5000000+10];


int k=1;
void adde(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}


void work()
{
memset(f,-0x3f3f3f,sizeof(f));
memset(vis,0,sizeof(vis));
memset(in,0,sizeof(in));

int ok=1;
queue<int>q;
q.push(0);
f[0]=0;
vis[0]=1;
in[0]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
if(in[u]>n)
{
printf("Unreliable\n");
return ;
}
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(f[u]+e[i].w>f[v])
{
f[v]=f[u]+e[i].w;
if(vis[v]==0)
{
in[v]++;
vis[v]=1;
q.push(v);
}
}
}
}
printf("Reliable\n");
}


void read()
{
while(scanf("%d%d",&n,&m)==2)
{
   memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
scanf("\n");
int a,b,c;
char x;
scanf("%c",&x);
if(x=='P')
{
scanf("%d%d%d",&a,&b,&c);
adde(b,a,c);
adde(a,b,-c);
}
if(x=='V')
{
scanf("%d%d",&a,&b);
adde(b,a,1);
}
}
for(int i=1;i<=n;i++)
{
adde(0,i,0);
}
work();
}
}


int main()
{
read();
return 0;
}


poj 3169

思路:

这道题的不等式很容易得到
f[b]-f[a]>=c;
f[b]-f[a]<=c;___________f[a]-f[b]>=-c
我个人是将加边反着加的,这样就可以从1到n跑一次spfa
注意用in记录每个点入队的次数,in[u]大于n的话,就无解,输出-1;
如果f[n]没有更新的话,说明前面的那些点的条件对它没有限制,那么就应有无数解,输出-2;
其他的就输出f[n]即可。 


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
int n,m1,m2;
int head[1001],vis[1001],f[1001],in[1001];


struct node
{
int v,w,next;
}e[1000000];


int k=1;
void adde(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}


void work()
{
queue<int>q;
q.push(1);
vis[1]=1;
f[1]=0;
in[1]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
if(in[u]>n)
{
printf("-1");
return ;
}

for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(f[u]+e[i].w<f[v])
{
f[v]=f[u]+e[i].w;
if(vis[v]==0)
{
in[v]++;
vis[v]=1;
q.push(v);
}
}
}
    }
    if(f[n]==0x3f3f3f3f)
    {
    printf("-2");
    return ;
    }
printf("%d",f[n]);
}


int main()
{
memset(head,-1,sizeof(head));
memset(f,0x3f3f3f,sizeof(f));

scanf("%d%d%d",&n,&m1,&m2);
int a,b,c;
for(int i=1;i<=m1;i++)
{
scanf("%d%d%d",&a,&b,&c);
adde(a,b,c);
}
for(int i=1;i<=m2;i++)
{
scanf("%d%d%d",&a,&b,&c);
adde(b,a,-c);
}
work();
return 0;


poj 1201

思路:

由于太水而直接上代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;


#define maxn 50000+3
int n;
int head[maxn],vis[maxn],f[maxn];
int min_num=0x3f3f3f,max_num=-1;


struct adge
{
int v,next,w;
}e[4*maxn];


int k=1;
void adde(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}


void work()
{
queue<int>q;
q.push(min_num);
f[min_num]=0;
vis[min_num]=1;
while(!q.empty())
{
int u=q.front();
//printf("***%d\n",u);
q.pop();
vis[u]=0;

for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(f[u]+e[i].w>f[v])
{
f[v]=f[u]+e[i].w;
if(vis[v]==0)
{
vis[v]=1;
q.push(v);
}
}
}
}

printf("%d",f[max_num]);
}


int main()
{
memset(head,-1,sizeof(head));
memset(f,-0x3f3f3f,sizeof(f));

scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
min_num=min(min_num,a);
max_num=max(max_num,b+1);
adde(a,b+1,c);
}

for(int i=min_num;i<max_num;i++)
{
adde(i+1,i,-1);
adde(i,i+1,0);
}
work();
return 0;
}


poj 1716

思路:

尼玛的处理边界。
其实不等式还是很好推,就是这三个
s[b+1]-s[a]>=2;
s[i+1]-s[i]>=0;
s[i]-s[i-1]>=-1;
后面两个不等式是根据题目中b>a推出的。


就是最后的源点的选取和与处理的边界。
源点选0,那么边界就为[1,max_num+1]; 


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define maxn 10000+10
int n,max_num=-1;
int head[maxn],vis[maxn],f[maxn];


struct node
{
int v,next,w;
}e[1000000];


int k=1;
void adde(int u,int v,int w)
{
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}


void work()
{
memset(f,-0x3f3f3f,sizeof(f));
queue<int>q;
q.push(0);
vis[0]=1;
f[0]=0;

while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(f[u]+e[i].w>f[v])
{
f[v]=f[u]+e[i].w;
if(vis[v]==0)
{
vis[v]=1;
q.push(v);
}
}
}
    }

printf("%d",f[max_num]);
}


int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
adde(a,b+1,2);
max_num=max(max_num,b+1);
}

for(int i=0;i<=max_num;i++)
{
adde(i,i+1,0);
adde(i+1,i,-1);
}

work();
return 0; 
}


poj 1275

由于太难而直接上代码:

#include <iostream>  
#include <queue>  
#include <cstring>
#include <cstdio>
using namespace std;  
#define inf 0x7fffffff  
#define M 25  
  
struct edge{  
    int v, w, next;  
}e[5*M];  
bool inq[M];  
int dist[M], pre[M], n = 24, cnt, ind[M];  
  
void init ()  
{  
    memset (pre, -1, sizeof(pre)); cnt = 0;  
}  
void addedge (int u, int v, int w)  
{  
    e[cnt].v = v; e[cnt].w = w; e[cnt].next = pre[u]; pre[u] = cnt++;  
}  
bool spfa (int u)  
{  
    int i, v, w;  
    for (i = 0; i <= n; i++)  
    {  
        dist[i] = -inf;
        inq[i] = false;  
        ind[i] = 0;  
    }  
    queue<int> q;  
    q.push (u);  
    inq[u] = true;  
    dist[u] = 0;  
    while (!q.empty())  
    {  
        u = q.front();  
        if (++ind[u] > n)  
            return false;  
        q.pop();  
        inq[u] = false;  
        for (i = pre[u]; i != -1; i = e[i].next)  
        {  
            v = e[i].v;  
            w = e[i].w;  
            if (dist[u] + w > dist[v])  
            {  
                dist[v] = dist[u] + w;  
                if (!inq[v])  
                {  
                    q.push (v);  
                    inq[v] = true;  
                }  
            }  
        }  
    }  
    return true;  
}  
  
int main()  
{  
    int t, R[M], num[M], i, pos, m, l, r, ans;  
    scanf ("%d", &t);  
    while (t--)  
    {  
        for (i = 1; i <= n; i++)  
            scanf ("%d", R+i);  
        scanf ("%d", &m);  
        memset (num, 0, sizeof(num));  
        for (i = 0; i < m; i++)  
        {  
            scanf ("%d", &pos);  
            num[pos+1]++;  
        }  
        l = 0, r = m;  
        bool flag = true;  
        while (l < r)          
        {  
            init();  
            ans = (l+r) / 2;  
            for (i = 1; i <= n; i++)  
            {  
                addedge (i-1, i, 0);            
                addedge (i, i-1, -num[i]);        
            }  
            for (i = 8; i <= n; i++)  
                addedge (i-8, i, R[i]);           
            for (i = 1; i < 8; i++)  
                addedge (i+16, i, R[i]-ans);      
            addedge (0, 24, ans);                 
            if (spfa (0))  
                r = ans, flag = false;  
            else l = ans + 1;  
        }  
        if (flag)  
            puts ("No Solution");  
        else printf ("%d\n", r);  
    }  
    return 0;  
}  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值