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;
}