图论——负环与01分数规划

Sightseeing Cows

思路:

01分数规划可以考虑二分答案,先判断边界的范围是(0,1000 ]

再来看二分的过程。

关键就在于判断是否存在一个环点权之和/边权之和>mid,此时答案在右半区间,否则答案在左半区间。

整理一下式子会发现

因为本题里既有点权又有边权,我们可以考虑把每个点的点权放到出边上。即
即把每条边的权重看作是f[i] - mid * t[i] ,问题即转化为图中是否存在正环。

思想跟求负环一样的。

代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e6+7;
const double eps=1e-4;

int n,m;
int wf[maxn];
int h[maxn],e[maxn],wt[maxn],ne[maxn],idx;
double d[maxn];
int q[maxn],cnt[maxn];
bool st[maxn];

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(d,0,sizeof d);
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    queue<int>q;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i]=1;
    }
    while(!q.empty()){
        int t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(d[j]<d[t]+wf[t]-mid*wt[i]){
                d[j]=d[t]+wf[t]-mid*wt[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n) return 1;
                if(!st[j]){
                    st[j]=1;
                    q.push(j);
                }
            }
        }
    }
    return 0;
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>wf[i];
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);//单向边
    }
    double l=0,r=1e6;
    while(r-l>eps){
        double mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%.2f\n",l);
    return 0;
}

Word Rings (建图的特殊性)

思路:

也是一道01分数规划问题,但关键在于如何建图。

因为匹配的时候只是考虑首尾两个字母,对于一个字符串我们可以分别以首尾两个字母为起点和终点,字符串长度为边的权重建图。

比如ababc,就可以建ab->bc 的长度为5的边。

建图完成后,再看如何求解该问题。问题转化为求所有的边权之和/1最大值。

先看二分的范围是(0,1000 ]

跟上题类似:当

答案在右半区间,反之在左半区间。

可以重新定义边权为w[i]-mid,看图中是否存在正环。

优化可以用之前提到的把SPFA的队列换成栈,真玄学优化。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 700, M = 100010;

int n;
int h[maxn], e[M], w[M], ne[M], idx;
double d[maxn];
int q[maxn], cnt[maxn];
bool st[maxn];

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

bool check(double mid){
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
   int hh=0,tt=0;
    for(int i=0;i<676;i++){
        q[tt++]=i;
        st[i]=1;
    }
    int count=0;
    while(hh!=tt){
        int t=q[hh++];
        if(hh==maxn) hh=0;
        st[t]=0;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(d[j]<d[t]+w[i]-mid){
                d[j]=d[t]+w[i]-mid;
                cnt[j]=cnt[t]+1;
                if(++count>10000) return 1;
                if(cnt[j]>=700) return 1;
                if(!st[j]){
                    st[j]=1;
                   q[tt++]=j;
                   if(tt==maxn) tt=0;
                }
            }
        }
    }
    return 0;
}

int main(){
    char str[1100];
    while(scanf("%d",&n),n){
        memset(h,-1,sizeof h);
        idx=0;
        for(int i=0;i<n;i++){
            scanf("%s",str);
            int len=strlen(str);
            if(len>=2){
                int left=(str[0]-'a')*26+str[1]-'a';
                int right=(str[len-2]-'a')*26+str[len-1]-'a';
                add(left,right,len);
            }
        }
        if(!check(0)) puts("No solution");
        else{
            double l=0,r=1000;
            while(r-l>1e-4){
                double mid=(l+r)/2;
                if(check(mid)) l=mid;
                else r=mid;
            }
            printf("%.10f\n",r);
        }
    }
    return 0;
}