1. 程式人生 > 實用技巧 >最優比率環 SPFA+二分

最優比率環 SPFA+二分

【最大環】

1504: 【例 1】Word Rings

將一個字串看成一條邊,字元兩端的字元看成節點,長度看成權值二分列舉答案,最後SPFA刷正環,因為只要有一個正環存在就可以了。
把每個單詞視為一條邊,由開頭兩字母指向結尾兩字母,邊權為單詞長度。則原問題轉化為求一個最優比率環。(和最優比率生成樹很像哈)
可以利用二分答案+spfa判環來解決。點最多有26*26個,邊最多1e5.dfs版spfa判環就是快。。
若不存在環串,輸出 No solution,否則輸出最長的環串的平均長度。

QUS:為什麼不是單詞之間連邊?

這樣做最壞情況下會有 105 個點,1010 條邊,時間和空間都會承受不了

換一種建圖的方式:

對於每一個單詞,將它的前兩個字元和後兩個字元之間連一條長度為當前單詞的長度的邊。

稍微思考一下就會發現這種建圖方式其實是和第一種等價的。

建完圖後,這個問題就變成了一個 01 分數規劃問題,要求的是 ∑單詞長度總和/∑單詞個數 的最大值。

然後就是一些基本操作:二分答案 + SPFA 找 正環。

注意 SPFA 時需要加一些優化:

記錄一下進行過鬆弛操作的點的數量,如果所有的點都已經做過了,就說明很大可能存在一個正環,直接返回 true;【或者直接vis過也可以】

為什麼二分+SPFA判正環

#include<iostream>
#include<cstring>
#include
<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; const double eps=1e-4; typedef long long LL; //將一個字串看成一條邊,字元兩端的字元看成節點,長度看成權值。二分列舉答案,最後SPFA刷正環,因為只要有一個正環存在就可以了。
//把每個單詞視為一條邊,由開頭兩字母指向結尾兩字母,邊權為單詞長度。則原問題轉化為求一個最優比率環, //可以利用二分答案+spfa判環來解決。點最多有26*26個,邊最多1e5.dfs版spfa判環就是快。。 //這道題還是不太理解 //我知道了,我這個題都不讀的女人 //若不存在環串,輸出 No solution,否則輸出最長的環串的平均長度。 //這個是求最大環 char aa[1010]; int head[maxn],vis[maxn]; double dis[maxn]; int n,cnt; struct node{ int to,next,dis; }ed[maxn*10]; void add(int a,int b,int c){ ed[++cnt].to=b; ed[cnt].dis=c; ed[cnt].next=head[a]; head[a]=cnt; } int js(char a,char b){ return (a-'a')*26+(b-'a')+1; //要加1 } bool spfa(int x,double mid){ vis[x]=1; for(int i=head[x];i;i=ed[i].next){ int t=ed[i].to; if(dis[x]+ed[i].dis-mid>dis[t]){ //!!!!因為是想最大嘛 dis[t]=dis[x]+ed[i].dis-mid; if(vis[t]||spfa(t,mid)) { //如果t已經訪問過或者後面能夠返回1,那麼就存在環,就可以返回1 vis[x]=0; return 1; } } } vis[x]=0; return 0; } bool judge(double mid){ memset(dis,0,sizeof(dis)); for(int i=1;i<=26*26;i++){ //列舉!!!!!!點最多有26*26個,列舉以每一個點為起點 if(spfa(i,mid)) return 1; } return 0; } int main(){ while(scanf("%d",&n)&&n){ memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); for(int i=0;i<n;i++){ scanf("%s",aa); int len=strlen(aa); add(js(aa[0],aa[1]),js(aa[len-2],aa[len-1]),len); //建邊 } double mid,left=0,right=1000; //二分 while(right-left>=eps){ mid=(left+right)/2; if(judge(mid)){ left=mid; } else right=mid; } if(left==0) printf("No solution\n"); else printf("%.2lf\n",left); } return 0; }  
View Code

【最小環】

1506:最小圈

很明顯是個分數規劃的題目 如果我們能夠在圖中找到一個負環,就說明ans可以繼續縮小 使用實數二分即可

跟word rings比較像,但是這個是求最小

但是注意一點:word rings: if(judge(mid)) left=mid

這道題:if(judge(mid)) right=mid

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=3010;
const int maxm=1e4+10;
const int INF=0x3fffffff;
const int eps=1e-9;
typedef long long LL;
//很明顯是個分數規劃的題目 如果我們能夠在圖中找到一個負環,就說明ans可以繼續縮小 使用實數二分即可
int n,m;
int head[maxn];
struct node{
    int to,next;
    double dis;
}ed[maxm];
int vis[maxn],cnt;
double dis[maxn];
void adde(int a,int b,double c){
    cnt++;
    ed[cnt].to=b;
    ed[cnt].next=head[a];
    ed[cnt].dis=c;
    head[a]=cnt;
}
bool dfs(int now,double mid){
    vis[now]=1;
     
    for(int i=head[now];i!=0;i=ed[i].next){
        int t=ed[i].to;
        if(dis[t]>dis[now]+ed[i].dis-mid){
            dis[t]=dis[now]+ed[i].dis-mid;
            if(vis[t]) return 1;
            else if(dfs(t,mid)) return 1;
        }
    }
    vis[now]=0;
    return 0;
}
bool ju(double mid){
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    for(int i=1;i<=n;i++){
        if(dfs(i,mid)) return 1;
    }
    return 0;
}
int main(){
    scanf("%d %d",&n,&m);
    int x,y;
    double z;
    double l=1e8,r=-1e8,mid;
    for(int i=0;i<m;i++){
        scanf("%d %d %lf",&x,&y,&z);
        adde(x,y,z);  //有向圖
        l=min(l,z);
        r=max(r,z);
    }
     
    while(r-l>eps){
        mid=(l+r)/2.0;
        if(ju(mid)) r=mid;
        else l=mid;
    }
    printf("%.8lf\n",l);
return 0;
} 
View Code