加载中...

spsfa求负环/正环 +二分

负环:一个环边权和<0 如果边权积走一圈,那么就求-logN

正环:最长路

a个b可以换成wc个d 求w最多设为0-1的哪个数 可以使得得到的数字最大

二分+判断负环https://ac.nowcoder.com/acm/contest/33187/D
所有边权值w*ci/ai 取-log logw+logc-loga 那么是否负环变成所有环 存在 -log权值 的和>0

#include <bits/stdc++.h>
 
using namespace std;
 
#define double long double
 
typedef pair<int, double> pid;
 
const int N = 1010;
 
int n, m;
vector<pid> g[N];
double dis[N];
int cnt[N];
bool vis[N];
 
bool check(double mid) {
    mid = -log(mid);
    memset(dis, 0, sizeof(dis));
    memset(vis, false, sizeof(vis));
    memset(cnt, 0, sizeof(cnt));
     
    queue<int> q;
    for (int i = 1; i <= n; i ++ ) {
        q.push(i);
//      vis[i] = true;
    }
     
    while (q.size()) {
        int u = q.front();
        q.pop();
         
        vis[u] = false;     
        for (auto [v, w] : g[u]) {
            if (dis[v] > dis[u] + w + mid) {
                dis[v] = dis[u] + w + mid;
                cnt[v] = cnt[u] + 1;
                 
                if (cnt[v] >= n)
                    return false;
                 
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }    
    return true;
}
 
signed main() {
    scanf("%d %d", &n, &m);
     
    while (m -- ) {
        int a, b, c, d;
        scanf("%d %d %d %d", &a, &b, &c, &d);
         
        g[b].push_back({d, -log(1.0 * c / a)});
    }
     
    double l = 0, r = 1;
    while (r - l > 1e-10) {
        double mid = (l + r) / 2;
        if (check(mid))
            l = mid;
        else
            r = mid;
    }     
    printf("%.10Lf", l);    
    return 0;
}
##虫洞https://www.acwing.com/problem/content/906/
正c表示前进 负c表示回溯

include

include

include

include

using namespace std;
const int N = 510,M=5201;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
int cnt[N];
int d[N];

int n,m,s;
queueq;
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(){
queueq;
memset(st, false, sizeof st);//判重数组
memset(d, 0, sizeof d);//因为是判断负环 所以点的距离是0
memset(cnt, 0, sizeof cnt);//更新的次数

for (int i = 1; i <= n; i ++ ){//假装你建立了一个虚拟源点,总之所有点进去
    q.push(i);
    st[i]=true;//所有点进队列
}
while ( q.size()){
    auto t=q.front();
    q.pop();
    st[t]=false;//出队取消标记
    for (int i = h[t]; ~i; i =ne[i] ){
        int j=e[i];
        if(d[j]>d[t]+w[i]){
            d[j]=d[t]+w[i];
            cnt[j]=cnt[t]+1;//最条最短路出现的点+1
            if(cnt[j]>=n) return true;//如果某条边出现了n个点说明走回去了
            if(!st[j]){//如果j不在队里面
                st[j]=true;
                q.push(j);
            }
        }
    }        
}    
return false;

}
int main()
{
cin.tie(0);
int t ;cin>>t;
while (t -- ){
memset(h, -1, sizeof h);
scanf("%d%d%d", &n, &m, &s);
idx=0;
for (int i = 1; i <= m; i ++ ){
int a,b,c;cin>>a>>b>>c;
add(a, b, c);
add(b, a, c);
}
for (int i = 1; i <= s; i ++ ){
int a,b,c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, -c);
}

    if(spfa()){
        puts("YES");
    }else{
        puts("NO");
    } 
} 
return 0;

}

##01分数规划+环 
点权放到边权上 可以转化

include

include

include

using namespace std;

const int N = 1010, M = 5010;

int n, m;
int wf[N];
int h[N], e[M], wt[M], ne[M], idx;
double dist[N];
int q[N], cnt[N];
bool st[N];

void add(int a, int b, int c)
{
e[idx] = b, wt[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

bool check(double mid)
{
memset(dist, 0, sizeof dist);
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);

int hh = 0, tt = 0;
for (int i = 1; i <= n; i ++ )
{
    q[tt ++ ] = i;
    st[i] = true;
}

while (hh != tt)
{
    int t = q[hh ++ ];
    if (hh == N) hh = 0;
    st[t] = false;

    for (int i = h[t]; ~i; i = ne[i])
    {
        int j = e[i];
        if (dist[j] < dist[t] + wf[t] - mid * wt[i])//最长路
        {
            dist[j] = dist[t] + wf[t] - mid * wt[i];
            cnt[j] = cnt[t] + 1;
            if (cnt[j] >= n) return true;
            if (!st[j])
            {
                q[tt ++ ] = j;
                if (tt == N) tt = 0;
                st[j] = true;
            }
        }
    }
}

return false;

}

int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> wf[i];

memset(h, -1, sizeof h);
for (int j = 0; j < m; j ++ )
{
    int a, b, c;
    cin >> a >> b >> c;
    add(a, b, c);
}

double l = 0, r = 1e6;
while (r - l > 1e-4)
{
    double mid = (l + r) / 2;
    if (check(mid)) l = mid;
    else r = mid;
}

printf("%.2lf\n", l);

return 0;

}

## if ( ++ count > 5N) return true; // 经验上的trick所有点更新总次数>N/2就return 
最好变成存在一个环
posted @ 2022-05-15 19:39  liang302  阅读(31)  评论(0编辑  收藏  举报