1380 - A Scheduling Problem

There is a set of jobs, say x1, x2,…, xn , to be scheduled. Each job needs one day to complete. Your task is to schedule the jobs so that they can be nished in a minimum number of days. There are two types of constraints: Conflict constraints and Precedence constraints.

Conflict constraints: Some pairs of jobs cannot be done on the same day. (Maybe job xi and job xj need to use the same machine. So they must be done in different dates).

Precedence constraints: For some pairs of jobs, one needs to be completed before the other can start. For example, maybe job xi cannot be started before job xj is completed.

The scheduling needs to satisfy all the constraints.

To record the constraints, we build a graph G whose vertices are the jobs: x1, x2,…, xn . Connect xi and xj by an undirected edge if xi and xj cannot be done on the same day. Connect xi and xj by a directed edge from xi to xj if xi needs to be completed before xj starts.

If the graph is complicated, the scheduling problem is very hard. Now we assume that for our problems, the constraints are not very complicated: The graph G we need to consider are always trees (after omitting the directions of the edges). Your task is to nd out the number of days needed in an optimal scheduling for such inputs. You can use the following result:

If G is a tree, then the number of days needed is either k or k + 1 , where k is the maximum number of vertices contained in a directed path of G , i.e., a path P = (x1, x2,…, xk) , where for each i = 1, 2,…, k - 1 , there is a directed edge from xi to xi+1 .

Figure 1 below is such an example. There are six jobs: 1, 2, 3, 4, 5, 6. From this figure, we know that job 1 and job 2 must be done in different dates. Job 1 needs to be done before job 3, job 3 before job 5, job 2 before job 4 and job 4 before job 6. It is easy to verify that the minimum days to finish all the jobs is 4 days. In this example, the maximum number k of vertices contained in a directed path is 3.

Figure 1: Example
这里写图片描述
Input

The input consists of a number of trees (whose edges may be directed or undirected), say T1, T2,…, Tm , where m20 . Each tree has at most 200 vertices. We represent each tree as a rooted tree (just for convenience of presentation, the root is an arbitrarily chosen vertex). Information of each of the trees are contained in a number of lines. Each line starts with a vertex (which is a positive integer) followed by all its sons (which are also positive integers), then followed by a 0. Note that 0 is not a vertex, and it indicates the end of that line. Now some of the edges are directed. The direction of an edge can be from father to son, and can also be from son to father. If the edge is from father to son, then we put a letter d" after that son (meaning that it is a downward edge). If the edge is from son to father, then we put a letteru” after that son (meaning that it is an upward edge). If the edge is undirected then we do not put any letter after the son.

The first case of the sample input below is the example in Figure 1.

Consecutive vertices (numbers or numbers with a letter after it) in a line are separated by a single space. A line containing a single 0 means the end of that tree. The next tree starts in the next line. Two consecutive lines of single 0 means the end of the input.

Output

The output contains one line for each test case. Each line contains a number, which is the minimum number of days to finish all the jobs in that test case.

Sample Input

1 2 3d 0
2 4d 0
3 5d 0
4 6d 0
0
1 2d 3u 4 0
0
1 2d 3 0
2 4d 5d 10 0
3 6d 7d 11 0
6 8d 9 12 0
0
1 2 3 4 0
2 5d 0
3 6d 0
4 7d 0
5 8d 0
6 9d 0
7 10d 0
0
0
Sample Output

4
3
4
3

我有话说:
这道题很复杂,而且还是树上的动态规划。索性题目中海给出了一个定理。答案是忽略无向边后树上最长链的节点或者树上最长链的节点+1;
其实,如果全部是有向边,我们的安排策略是每次安排入度为0的节点,再把它们删去,在安排新的入度为0的节点,这样子答案就是最长链上节点的数目。这样,原问题就转换成给图中的无向边定向,使得定向后书上的最长链最短。
再加上定理就是对给定的k(忽略无向边后最长链的长度)是否满足定边后最长链的长度。即maxlen>=f[i]+g[i]是否能够成立。
我们定义f[i]为形如w->i的子树上的最长链的最小值,g[i]为形如i->w的子树上的最长链的最小值。
下面请看代码和注释。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <sstream>
using namespace std;
const int maxn=200+5;
const int INF=1000000000;
struct Edge{
   int u,v,d;  //d=0,u-v;d=1,u->v;d=2,v->u;
   Edge(int u=0,int v=0,int d=0):u(u),v(v),d(d){}
};
vector<Edge>edges[maxn];
int n,root,maxlen,f[maxn],g[maxn],have_father[maxn];
int dfs(int u)//查找最长链
{
    int ans=0;
    for(int i=0;i<edges[u].size();i++)
    {
        int v=edges[u][i].v;
        if(edges[u][i].d==1)
            ans=max(ans,dfs(v)+1);
    }
    return ans;
}
bool read_data()
{
    bool have_data=false;
    int a,b;
    n=0;
    for(int i=0;i<maxn;i++)edges[i].clear();
    memset(have_father,0,sizeof(have_father));

    while(cin>>a&&a)
    {
        string str;
        have_data=true;
        if(n>a)n=a;
        while(cin>>str&&str!="0")
        {
            int len=str.length();
            char dir=str[len-1];
            if(dir=='u'||dir=='v')str=str.substr(0,len-1);
            stringstream ss(str);
            ss>>b;
            if(b>n)n=b;
            have_father[b]=1;
            if(dir=='d'){
                edges[a].push_back(Edge(a,b,1));//a->b
                edges[b].push_back(Edge(b,a,2));//b<-a
            }else if(dir=='u'){
                edges[a].push_back(Edge(a,b,2));
                edges[b].push_back(Edge(b,a,1));
            }else{
                edges[a].push_back(Edge(a,b,0));//a-b
            }
        }
    }
    if(have_data){
        for(int i=1;i<=n;i++)
        {
            if(!have_father[i]&&!edges[i].empty()){//确定根节点
                root=i;
                break;
            }
        }
    }
    return have_data;
}
struct UndirectedSon{
   int w,f,g;
   UndirectedSon(int w=0,int f=0,int g=0):w(w),f(f),g(g){}
};
bool cmp_f(const UndirectedSon& w1,const UndirectedSon& w2){
    return w1.f<w2.f;
}
bool cmp_g(const UndirectedSon& w1,const UndirectedSon& w2){
    return w1.g<w2.g;
}
bool dp(int i,int fa)
{
    if(edges[i].empty()){
        f[i]=g[i]=0;//所以,叶子节点的本身一开始是没有计入的,后来的点计算f[i],g[i]时要+1
        return true;
    }
    vector<UndirectedSon>sons;
    int f0=0,g0=0;
    //f'[i],g'[i](已标号方向的点)
    //f'[i]=max{f[w]|w->i}+1,g'[i]=max{g[w]|i->w}+1;
    //通过确定一些点之间的方向,使得节点中->u或者u->的节点能够实现f'[i]+g'[i]<=maxlen
    //f[i]是满足上述最小的f'[i],g[i]是满足上述最小的g'[i]
    for(int k=0;k<edges[i].size();k++)//未定向的边
    {
        int w=edges[i][k].v;
        if(w==fa)continue;
        dp(w,i);//化大问题为小问题
        int d=edges[i][k].d;
        if(d==0)sons.push_back(UndirectedSon(w,f[w],g[w]));
        else if(d==1)g0=max(g0,g[w]+1);
        else f0=max(f0,f[w]+1);
    }
    if(sons.empty()){//如果所有的边都已经定好了方向,那么直接计算即可。
        f[i]=f0;g[i]=g0;
        if(f[i]+g[i]>maxlen){f[i]=g[i]=INF;}
        return f[i]<INF;
    }
    f[i]=g[i]=INF;
    //计算f[i],我们先把儿子节点按照f[]的值升序排序。
    //选取第p小的点,我们把它标记为指向i,用f[p]更新f[i],同时我们可以把前p个点一起标记为指向i,这样不会让f[i]变大
    //但是,有可能使g[i]减小。
    int s=sons.size();
    sort(sons.begin(),sons.end(),cmp_f);
    int maxg[maxn];//maxg[i]=max{sons[i].g,sons[i+1].g,……}
    maxg[s-1]=sons[s-1].g;
    for(int k=s-2;k>=0;k--)
        maxg[k]=max(maxg[k+1],sons[k].g);
    for(int p=0;p<=sons.size();p++)
    {
        int ff=f0,gg=g0;
        if(p>0)ff=max(ff,sons[p-1].f+1);//定边后在一次更新,有可能ff更大
        if(p<sons.size())gg=max(gg,maxg[p]+1);
        if(ff+gg<=maxlen)f[i]=min(f[i],ff);
    }
    sort(sons.begin(),sons.end(),cmp_g);
    int maxf[maxn];
    maxf[s-1]=sons[s-1].f;
    for(int k=s-2;k>=0;k--)
        maxf[k]=max(maxf[k+1],sons[k].f);
    for(int p=0;p<=sons.size();p++)
    {
        int ff=f0,gg=g0;
        if(p>0)gg=max(gg,sons[p-1].g+1);
        if(p<sons.size())ff=max(ff,maxf[p]+1);
        if(ff+gg<=maxlen)g[i]=min(g[i],gg);
    }
    return f[i]<INF;

}
int main()
{
    while(read_data())
    {
        maxlen=0;
        for(int i=1;i<=n;i++)maxlen=max(maxlen,dfs(i));
        if(dp(root,-1))cout<<maxlen+1<<endl;
        else cout<<maxlen+2<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值