UVA - 1380 A Scheduling Problem --树形dp

问题

给定一些任务,任务之间存在着一些约束关系,分为两类,冲突约束和先后约束,存在冲突约束的两个任务不能再同一天执行,先后约束的两个任务x和y,必须执行完x,才能执行y,每个任务都需要一天的时间才能完成
所以,可以构建出一棵树,一个由任务作为节点,任务之间的约束条件作为边连接成的树,冲突约束可以做为无向边,先后约束作为有向边,求至少需要多少天能够完成所有工作

定理: 删去无向边后存在的最长链长度是k,那么所需要的天数为k或k+1

分析

只要求出删除无向边的最长链长度maxlen,然后再判断是无向边定向后maxlen是否是最长链长就可以了,如果不是,答案就是maxlen+1

首先初始化,将输入数据转化为树的形式,其中每一条有向边存储两次(正向和反向)方便DFS处理和之后的DP(因为无法确定那一端是父节点)
然后使用DFS从每个点开始遍历, 找出去删除了无向边的最长链的长度maxlen(这里计算的是边长)
接下来,从root开始进行对树上的节点进行DFS,在这过程中,求出每一个点的f和g值,i是一个节点

在满足i为根节点的子树上,所有边全部定向后,最长链长度不大于maxlen时,f[i]是u->i的最长链长度,g[i]是i->u的最长链长度,u是以i为根节点的子树上的一个节点,不满足<=maxlen,就令f[i]=g[i]=Inf
DP函数的流程:按照DFS顺序求解每一个点的f和g值,对于当前点u和它的父亲fa
如果u没有子节点,那么f[u]=g[u]=0,return true;
如果u有子节点,并且和子节点之间全都是有向边,类似于w->u这种,就求出所有的w之中f的最大值,加一作为f[u],公式是 f [ u ] = m a x ( f [ w ] ) + 1 w u w > u f[u]={max(f[w])+1|w是u的子节点且方向是w->u} ,反之,对于u->w这种,就求出所有的w之中f的最大值,加一作为g[u],公式是 g [ u ] = m a x ( g [ w ] ) + 1 w u u > w g[u]={max(g[w])+1|w是u的子节点且方向是u->w} ,最后,评判条件是否满足 f [ u ] + g [ u ] < = m a x l e n f[u]+g[u]<=maxlen ,不满足 f [ u ] = g [ u ] = I n f f[u]=g[u]=Inf
如果u有子节点,且与子节点之间是无向边,那么先按照上述步骤处理完有向边子节点,求出u的有向边子节点中的最大f值和最大g值 ,加一得到fu和gu,再单独处理无向边子节点,对于无向边进行定向,每条边定向成 u > w u->w 还是 w > u w->u 呢,首先我们要求接的是定向后的满足<=maxlen条件的f[u]最小值和g[u]最小值,重定向的情况如果暴力求解时间太长,需要优化,
先看求f[u]最小值,把无向边子节点存储在集合sons中,按照f的从小到大排序,对于其中的一个点w来说,在它被定向为w->u时,那么f值比他小的那些子节点(就是从小到大排在他前面的),对于最后的f[u]值没有影响,因为他们f值都比较小,不会影响这种定向情况下的f[u],就可以把他们都定向为->u,这样还可能减小这种情况下的g[u],使得<=maxlen条件满足,所以就可以把求解最小f[u]的重定向简化为s+1种(s=sons.size())
同理对于求解最小的g[u],可以按照相同的情况处理

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=205,Inf=1000000000;
struct Edge{
    int u,v,t;
    //uv是一条边,t=0代表无向,t=1代表u->v,t=2代表v->u
    Edge(int u=0,int v=0,int t=-1):u(u),v(v),t(t) {}
};
struct UndirectedEdge{
    int w,f,g;
    UndirectedEdge(int w=0,int f=0,int g=0):w(w),f(f),g(g) {}
};

bool cmpF(UndirectedEdge &lhs,UndirectedEdge &rhs){
    return lhs.f<rhs.f;
}

bool cmpG(UndirectedEdge &lhs,UndirectedEdge &rhs){
    return lhs.g<rhs.g;
}

int n,root,maxlen,f[maxn],g[maxn];
//f[i]是i的子树节点到i的最长链长度,g[i]是i到它的自述中的节点的最长链长度
string str;
vector<Edge> graph[maxn];

bool readData(){
    for(int i=1;i<maxn;++i) graph[i].clear();
    n=0;
    int u,v,t;
    bool haveData=false;
    while(cin>>u){
        if(u==0) break;
        while(cin>>str){
            if(str[0]=='0') break;
            if(str.back()=='d') {
                v=stoi(str.substr(0,str.length()-1));
                graph[u].push_back(Edge(u,v,1));  //这里存两次,一次正向,一次反向,方便求解maxlen和dp,一开始不能确定父节点
                graph[v].push_back(Edge(v,u,2));
            }else if(str.back()=='u'){
                v=stoi(str.substr(0,str.length()-1));
                graph[u].push_back(Edge(u,v,2));
                graph[v].push_back(Edge(v,u,1));
            }else{
                v=stoi(str);
                graph[u].push_back(Edge(u,v,0));//此处方便
                graph[v].push_back(Edge(v,u,0));
            }
            ++n;
        }
    }
    root=1;
    ++n;
    return n!=1;
}

int dfs(int u){
    int ans=0;
    for(int i=0;i<graph[u].size();++i){
        if(graph[u][i].t==1)
            ans=max(ans,dfs(graph[u][i].v)+1);
    }
    return ans;
}

bool DP(int u,int pa){
    if(graph[u].empty()){
        f[u]=g[u]=0;
        return true;
    }
    //fu和gu是u的有向边子儿子节点中的最大f值和最大g值,fu2和gu2是u的所有子儿子节点中的最大f值和最大g值
    int fu=0,gu=0,fu2=0,gu2=0;
    vector<UndirectedEdge> undirectedSons;
    for(int i=0;i<graph[u].size();++i){
        Edge temp=graph[u][i];
        if(temp.v==pa) continue;
        DP(temp.v,u);
        if(temp.t==1){
            gu=max(gu,g[temp.v]+1);
        }else if(temp.t==2){
            fu=max(fu,f[temp.v]+1);
        }else{
            undirectedSons.push_back(UndirectedEdge(temp.v,f[temp.v],g[temp.v]));
        }
    }
    if(undirectedSons.empty()){
        if(fu+gu>maxlen) f[u]=g[u]=Inf;
        else{
            f[u]=fu; g[u]=gu;
        }
        return f[u]<Inf;  //f[u]<Inf说明,fu+gu<=maxlen,说明最长链没有超过k,返回true
    }

    f[u]=g[u]=Inf;
    int s=undirectedSons.size();
    sort(undirectedSons.begin(),undirectedSons.end(),cmpF);
    int maxg[maxn];
    maxg[s-1]=undirectedSons[s-1].g;
    for(int k=s-2;k>=0;--k){
        maxg[k]=max(maxg[k+1],undirectedSons[k].g);
    }
    //这地方注意,k代表选择u->undirectedSons[k].w方向边的数量,k和k之前的边u->i,k之后的i->u
    for(int k=0;k<=s;++k){
        fu2=fu; gu2=gu;  //还未划分方向
        if(k>0) fu2=max(fu,undirectedSons[k-1].f+1);
        if(k<s) gu2=max(gu,maxg[k]+1);
        if(fu2+gu2<=maxlen)  f[u]=min(f[u],fu2);
    }

    sort(undirectedSons.begin(),undirectedSons.end(),cmpG);
    int maxf[maxn];
    maxf[s-1]=undirectedSons[s-1].f;
    for(int k=s-2;k>=0;--k){
        maxf[k]=max(maxf[k+1],undirectedSons[k].f);
    }
    for(int k=0;k<=s;++k){
        fu2=fu; gu2=gu;  //还未划分方向
        if(k>0) gu2=max(gu,undirectedSons[k-1].g+1);
        if(k<s) fu2=max(fu,maxf[k]+1);
        if(fu2+gu2<=maxlen)  g[u]=min(g[u],gu2);
    }
    return f[u]<Inf;
}

int main(void){
    while(readData()){
        maxlen=0;  //maxlen为删去无向边最长链边数
        for(int i=1;i<=n;++i){
            maxlen=max(maxlen,dfs(i));
        }
        //确定初始值
//        fill(f,f+maxn,Inf);
//        fill(g,g+maxn,Inf);
        //本题中链长为节点数+1
        if(DP(root,-1)){
            printf("%d\n",maxlen+1);
        }else printf("%d\n",maxlen+2);
    }
    return 0;
}

第一种时间复杂度是O(n*log(n))级别的,字数上还提及了第二种时间复杂度O(n^2)级别的,接下来再补充
第二种方法参考代码
d p ( i , j ) dp(i,j) 表示节点i在时间j天恰好完成时,也就是说i为根节点的子树中,指向i的最长链长度f[i]恰好等于j,g[i]的最小值,所以i为根节点的子树最长链就是j+g[i]
和第一种方法相比,就是将所有的dp[i][j]求出来,显示初始化为Inf
当所有子节点都是有向的,和第一种方法一样
当子节点存在无向时,只需求出每种划分出来的的f[u]和对应的最小的g[u],然后令dp[u][f[u]]=g[u],构建dp数组,然后遍历这一行,判断是否有合法的最长链

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=205,Inf=1000000000;
struct Edge{
    int u,v,t;
    //uv是一条边,t=0代表无向,t=1代表u->v,t=2代表v->u
    Edge(int u=0,int v=0,int t=-1):u(u),v(v),t(t) {}
};
struct UndirectedEdge{
    int w,f,g;
    UndirectedEdge(int w=0,int f=0,int g=0):w(w),f(f),g(g) {}
};

bool cmpF(UndirectedEdge &lhs,UndirectedEdge &rhs){
    return lhs.f<rhs.f;
}

bool cmpG(UndirectedEdge &lhs,UndirectedEdge &rhs){
    return lhs.g<rhs.g;
}

int n,root,maxlen,f[maxn],g[maxn],dp[maxn][maxn];
//f[i]是i的子树节点到i的最长链长度,g[i]是i到它的自述中的节点的最长链长度
string str;
vector<Edge> graph[maxn];

bool readData(){
    for(int i=1;i<maxn;++i) graph[i].clear();
    n=0;
    int u,v,t;
    bool haveData=false;
    while(cin>>u){
        if(u==0) break;
        while(cin>>str){
            if(str[0]=='0') break;
            if(str.back()=='d') {
                v=stoi(str.substr(0,str.length()-1));
                graph[u].push_back(Edge(u,v,1));  //这里存两次,一次正向,一次反向,方便求解maxlen和dp,一开始不能确定父节点
                graph[v].push_back(Edge(v,u,2));
            }else if(str.back()=='u'){
                v=stoi(str.substr(0,str.length()-1));
                graph[u].push_back(Edge(u,v,2));
                graph[v].push_back(Edge(v,u,1));
            }else{
                v=stoi(str);
                graph[u].push_back(Edge(u,v,0));//此处方便
                graph[v].push_back(Edge(v,u,0));
            }
            ++n;
        }
    }
    root=1;
    ++n;
    return n!=1;
}

void init() {
    //确定初始值
    fill(f,f+maxn,Inf);
    fill(g,g+maxn,Inf);
    for(int i=0;i<=n;++i){
        for(int j=0;j<=n;++j)
            dp[i][j]=Inf;
    }
}
int dfs(int u){
    int ans=0;
    for(int i=0;i<graph[u].size();++i){
        if(graph[u][i].t==1)
            ans=max(ans,dfs(graph[u][i].v)+1);
    }
    return ans;
}

//和方法一相比,主要是这个地方不同,方法一使用了f和g计算了状态变化,同时记录了一个点的2个状态,方法2需要使用dp数组
bool DP(int u,int pa){
    if(graph[u].empty()){
        f[u]=g[u]=0;
        return true;
    }
    //fu和gu是u的有向边子儿子节点中的最大f值和最大g值,fu2和gu2是u的所有子儿子节点中的最大f值和最大g值
    int fu=0,gu=0,fu2=0,gu2=0;
    vector<UndirectedEdge> undirectedSons;
    for(int i=0;i<graph[u].size();++i){
        Edge temp=graph[u][i];
        if(temp.v==pa) continue;
        if(!DP(temp.v,u)) return false;
        if(temp.t==1){
            gu=max(gu,g[temp.v]+1);
        }else if(temp.t==2){
            fu=max(fu,f[temp.v]+1);
        }else{
            undirectedSons.push_back(UndirectedEdge(temp.v,f[temp.v],g[temp.v]));
        }
    }
    if(undirectedSons.empty()){
        dp[u][fu]=min(dp[u][fu],gu);
        f[u]=fu; g[u]=gu;
        return f[u]+g[u]<=maxlen;  //f[u]<Inf说明,fu+gu<=maxlen,说明最长链没有超过k,返回true
    }

//    f[u]=g[u]=Inf;
    int s=undirectedSons.size();
    sort(undirectedSons.begin(),undirectedSons.end(),cmpF);
    int maxg[maxn];
    maxg[s-1]=undirectedSons[s-1].g;
    for(int k=s-2;k>=0;--k){
        maxg[k]=max(maxg[k+1],undirectedSons[k].g);
    }
    //这地方注意,k代表选择u->undirectedSons[k].w方向边的数量,k和k之前的边u->i,k之后的i->u
    for(int k=0;k<=s;++k){
        fu2=fu; gu2=gu;  //还未划分方向
        if(k>0) fu2=max(fu,undirectedSons[k-1].f+1);
        if(k<s) gu2=max(gu,maxg[k]+1);
        dp[u][fu2]=min(dp[u][fu2],gu2);
    }
    for(int i=0;i<=n;++i){
        if(dp[u][i]+i<=maxlen){
            f[u]=min(f[u],i);
            g[u]=min(g[u],dp[u][i]);
        }
    }
    for(int i=0;i<=n;++i){
        if(dp[u][i]+i<=maxlen) return true;
    }
    return false;
}

int main(void){
    while(readData()){
        maxlen=0;  //maxlen为删去无向边最长链边数
        for(int i=1;i<=n;++i){
            maxlen=max(maxlen,dfs(i));
        }
        init();
        //本题中链长为节点数+1
        if(DP(root,-1)){
            printf("%d\n",maxlen+1);
        }else printf("%d\n",maxlen+2);
    }
    return 0;
}

发布了15 篇原创文章 · 获赞 0 · 访问量 170

猜你喜欢

转载自blog.csdn.net/zpf1998/article/details/103987220
今日推荐