CSP-差分約束系統和最短路求解

CSP-差分約束系統及求解

知識簡述

差分約束系統,是一種不等式系統,形式比較固定。具體的形式如下:
在這裏插入圖片描述在上面的例子中我們可以看到,差分約束系統的核心約束即爲m個形如:xi-xj<=ck的不等式,解即爲使所有條件都成立的一組答案。值得一提的是,差分約束系統一般說來都有無窮多個解,因此我們往往需要固定一個初始值來保證可以求出唯一解。
對於差分約束系統一般有兩種形式:
1、xi-xj<=ck,求解的上限–>即xi-xj=ck的情況
2、xi-xj>=ck,求解的下限–>即xi-xj=ck的情況
對這兩種情況,可以使用相同的轉換思路,將xj移到不等式右側後可以得到xi<=ck+xj求上限xi>=ck+xj求下限,熟悉最短路的鬆弛操作的同學就會發現,這個式子和鬆弛後得到的條件是一模一樣的,也就是:將xi視爲dis[i],將xj視爲dis[j],ck視爲邊長,利用最短路算法來求解差分約束系統
進過轉換後,具體的實現思路如下:
1、將每一個約束條件(例xi-xj<=ck)轉換成一條有向邊(j,i,ck),並存入圖中
2、對生成好的圖跑最短路算法(由於ck的值可能爲負值,採用SPFA算法比較合理)
3、令dis[1]=0,得到的xi=dis[i]即爲差分約束系統的一組解
兩種特殊情況的處理:
1、出現負環,出現負環,說明某些點的dis[i]=-inf,則表達式變爲xi-x1<=-Inf,我們無法找到這樣的一個解,說明該差分約束系統無解。
2、出現某點不可達,說明某點滿足dis[i]=inf,則表達式變爲xi-x1<=Inf,該條件在任何取值下都滿足,說明差分約束系統對xi無明確約束,可以取任何值。

題目概述

給定一個數軸上的 n 個區間,要求在數軸上選取最少的點使得第 i 個區間 [ai, bi] 裏至少有 ci 個點

INPUT&輸入樣例

輸入第一行一個整數 n 表示區間的個數,接下來的 n 行,每一行兩個用空格隔開的整數 a,b 表示區間的左右端點。1 <= n <= 50000, 0 <= ai <= bi <= 50000 並且 1 <= ci <= bi - ai+1。
輸入樣例

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

OUTPUT&輸出樣例

輸出一個整數表示最少選取的點的個數
輸出樣例:

6

題目重述

給定幾個區間,每個區間內要求有n個點,求出能夠滿足條件的選擇的最少點。

思路概述

由於題目要求最少點滿足選點條件,看到這個題目的第一反應可能會想到是一道貪心算法的題目,且這道題和貪心問題裏的區間選點問題十分相似。
但如果使用貪心算法,對多個點的貪心並不容易實現,可能需要比較複雜的模擬。這裏介紹一種利用差分約束求解這道題的做法。
我們使用sum[i]來表示從源點0開始,選的點個數。(雖然題目中說明點的範圍是1-5e4,但如果從sum[1]開始我們是無法衡量出1號點是否被選中,所以使用0號點開始操作比較方便)
條件A B a(A到B閉區間內選擇a個點)可以轉換爲sum[B]-sum[A-1]>=a,通過類比發現該類型的條件與上述的差分約束系統十分相似,可以使用差分約束來巧妙求解。
但是隻有這些邊條件是不夠的,因爲實際意義,每個點只能是選擇或者不選擇兩個可能,我們可以得出如下的條件0<=sum[i]-sum[i-1]<=1,對圖中的每兩點進行連接邊即可。由於求的是最少點,所以需要將所有約束條件轉換成xi-xj>=ck的形式。在轉換完成後的圖中跑一遍最長路即可得出解。
題目所求的最少點,也就是sum[maxi]的值。

題目源碼(c++)

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;


const int N=5e4+5;
struct Edge
{
    int des;
    int nxt;
    int value;
}Edges[N];

int edge_cnt;
int point_cnt;
int head[N];

void init()
{
    edge_cnt=0;
    for(int i=0;i<=N;i++)
    head[i]=-1;
}
void add(int x,int y,int value)
{
    edge_cnt++;
    Edges[edge_cnt].des=y;
    Edges[edge_cnt].nxt=head[x];
    Edges[edge_cnt].value=value;
    head[x]=edge_cnt;
}

int vis[N];
int dis[N];
queue<int> q;
void SPFA()
{
    while(!q.empty()) q.pop();
	for(int i=0;i<=point_cnt;i++)
    {
        vis[i]=0;
        dis[i]=0;
    }
    vis[0]=1,dis[0]=0;
    q.push(0);
    
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i>=0;i=Edges[i].nxt)
        {
            int y=Edges[i].des;
            if(dis[y]<dis[x]+Edges[i].value || (dis[y]==0&&dis[x]+Edges[i].value==0))
            {
                dis[y]=dis[x]+Edges[i].value;
                if(vis[y]==0)
                {
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
}
int main()
{
    int limit_number;
    cin>>limit_number;
    int start,end,value;
    init();
    int max_number=0;
    for(int i=0;i<limit_number;i++)
    {
        scanf("%d %d %d",&start,&end,&value);
        add(start,end+1,value);
        if(end+1>max_number) max_number=end+1;
    }
    point_cnt=max_number;
    for(int i=1;i<=point_cnt;i++)
    {
        add(i,i-1,-1);
        add(i-1,i,0);
    }
    
    SPFA();
    cout<<dis[point_cnt];
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章