1. 程式人生 > 實用技巧 >#差分約束,Floyd#洛谷 2474 [SCOI2008]天平

#差分約束,Floyd#洛谷 2474 [SCOI2008]天平

題目


分析

非傳統差分約束??
注意只有結果保證惟一的選法才統計在內
這就為差分約束提供了依據
以左邊重為例,假設現在選擇的砝碼為\(i,j\)
那麼\(\because A+B>i+j\therefore A-i>j-B\)
既然結果保證唯一,那麼也就是\(min(A-i)>max(j-B)\)
右邊重同理,也就是\(max(A-i)<min(j-B)\)
同樣重也很顯然,就是
\(min(A-i)=max(A-i),min(j-B)=max(j-B),max(A-i)=min(j-B)\)
講到這裡該怎麼做,floyd現身了,考慮維護最短路和最長路,

  1. 等號也就是\(i-j\leq 0,i-j\geq 0\)
  2. 大於號也就是\(i-j\leq -1,i-j\geq -2\)
  3. 小於號也就是\(i-j\leq 2,i-j\geq 1\)
  4. 問號也就是\(i-j\geq -2,i-j\leq 2\)
    按照這個floyd建圖分別跑最短路和最長路即可
    為什麼非傳統呢,是因為這個最短路和最長路僅僅是用來判斷上下界的

程式碼

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N=51; char s[N];
int n,A,B,dmn[N][N],dmx[N][N],ans1,ans2,ans3;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline signed min(int a,int b){return a<b?a:b;}
inline signed max(int a,int b){return a>b?a:b;}
signed main(){
	scanf("%d%d%d",&n,&A,&B);
	for (rr int i=1;i<=n;++i){
	    scanf("%s",s+1),s[i]='=';
	    for (rr int j=1;j<=n;++j)
	    switch (s[j]){
	    	case '=':{
	    		dmn[i][j]=0,dmx[i][j]=0;
				break;
			}
			case '+':{
				dmn[i][j]=1,dmx[i][j]=2;
				break;
			}
			case '-':{
				dmn[i][j]=-2,dmx[i][j]=-1;
				break;
			}
			case '?':{
				dmn[i][j]=-2,dmx[i][j]=2;
				break;
			}
		}
	}
	for (rr int k=1;k<=n;++k)
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<=n;++j)
	if ((i^k)&&(i^j)&&(k^j))
		dmn[i][j]=max(dmn[i][j],dmn[i][k]+dmn[k][j]),
		    dmx[i][j]=min(dmx[i][j],dmx[i][k]+dmx[k][j]);
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<i;++j)
	if ((i^j)&&(i^A)&&(i^B)&&(j^A)&&(j^B)){
		if (dmn[A][i]>dmx[j][B]||dmn[B][i]>dmx[j][A]) ++ans1;
		if (dmn[i][A]>dmx[B][j]||dmn[i][B]>dmx[A][j]) ++ans3;
		if ((dmn[A][i]==dmx[A][i]&&dmn[j][B]==dmx[j][B]&&dmn[A][i]==dmx[j][B])||
		    (dmn[A][j]==dmx[A][j]&&dmn[i][B]==dmx[i][B]&&dmn[A][j]==dmx[i][B]))
		    ++ans2;
	}
	return !printf("%d %d %d",ans1,ans2,ans3);
}