hdu 1529 Cashier Employment 差分约束系统
hdu 1529 Cashier Employment 差分约束系统
//hdu 1529 Cashier Employment //差分约束系统 //还有国家集训队的黄源河的论文中的这题的后面两个不等式写错了 //不懂差分约束可以到这看看 //http://imlazy.ycool.com/post.1702305.html; //一开始看我完全没想到可以用差分约束 //这题也主要是看了别人代码才过了的,还用了蹩脚的英文注释 //不过真感觉这题挺神奇的 //题意: //第一行t,表示有几组测试数据;第二行24个数分别表示各个小时 //至少需要的出纳员数;接下去一行一个数表m示有几个应聘者 //接下去有m行,每行1个数表示该应聘者的开始工作时间 //每个出纳员若开始工作时间为st 则他一定连续工作8小时 //即工作到 st+8 小时 //思路: //根据差分约束,我们可以先找出一些看的到的不等式 //s[i] 表示在从第0小时到第i个小时总的需要多少出纳员,则我们 //要求的就是s[23]了;w[i]表示应聘第i个小时开始工作的出纳员数 //所以我们要在输入的时候累加每个小时的应聘的出纳员数; //r[i]表示第i个小时至少需要多少出纳员 //1、0 <= s[i] - s[i-1] <= w[i] (0 <= i <= 23) //2、s[i] - s[i-8] >= r[i] (8 <= i <= 23) //3、s[23] + s[i] - s[i+16] >= r[i] (0 <= i <= 7) //第一个不等式s[i]-s[i-1]表示答案中第i个小时有多少个出纳员 //第二个不等式s[i]-s[i-8]表示出纳员工作的时间段有第i-8小时的人数 //第三个不等式和第二个一样的意思,只是这时是跨过一天,比如工作的 //的时间段有包括第1个小时的出纳员数即为 s[23]+s[1]-s[1+16] //从上面的式子看,根据差分约束,我们要找出像求最短(长)路时的 //三角不等式:d[to] - d[from] >= w //我们要转化为相同的样式,也就是不等号 //方向要相同,于是:上面3个式子就第一个式子需要转化,转化为 //s[i] - s[i-1] >= 0 和 s[i-1]-s[i] >= -w[i] //这样子就变成4个不等式了 //接下来的难点就是最后一个式子有个s[23]这个既是我们所要求的答案 //这里又要当做常数(因为它的下标是固定的,当做常数比较好处理,而且 //按照式子把s[23]当做常数,跟上面的样式才一样,都有变量i), //这样我们就可以把s[23]当做常数移到右边去,然后枚举所有s[23]可能取到 //的值(0 <= s[23] <= 总的应聘人数m),这里也可以用二分,不过我们尝试过 //然后就可以根据三角不等式d[to]-d[from]>=w 建一条from 到 to的边 //因为差分约束是求单源最短路,所以要建一个源点,我把源点的下标设为24 //跟源点连边的就只要根据第1个式子跟0点建边即可(s[-1] - s[0] >= w[0]) //这里s[-1]极为源点s[24],从0指向24 //求出来的dis数组中每个数就是有经过各个时间的的出纳员总数 //我们用的是 >= 所以我们在求最短路是要维护好,即若s[to] - s[from] < w[i] //则要把s[to]维护为s[to] = s[from] + w[i] 这样就保持了>=号 //其他的看代码中的注释 //看不懂的话可以到这来看看,我也是看这的 //他写的解释不错,不过跟人觉得他代码风格不好,还用goto //http://www.cnblogs.com/zhuangli/archive/2008/07/26/1252252.html #define comein freopen("in.txt", "r", stdin); #include <stdio.h> #include <string.h> #include <queue> using namespace std; #define INF 1<<30 #define N 30 int hour_least[N], app_st[N], hour_most[N]; //count array is to count the time of a point have been visited int head[N], relate23[N], dis[N], count[N]; int eid, eid23, source_id; bool vis[N]; struct EDGE { int to, dis, next; }edge[1005]; void buildMap() //build map { eid = 0; memset(head, -1, sizeof(head)); for(int i = 1; i < 24; ++i) { //s[i] represent sum of employees at before i hour //so s[23] is the answer which us want to get //0 <= s[i] - s[i-1] <= most[i] (0 <= i <= 23) //build edge from i-1 to i for s[i] - s[i-1] >= 0 edge[++eid].to = i; edge[eid].dis = 0; edge[eid].next = head[i-1]; head[i-1] = eid; //build edge from i to i-1 for s[i-1] - s[i] >= -most[i] edge[++eid].to = i-1; edge[eid].dis = -hour_most[i]; edge[eid].next = head[i]; head[i] = eid; if(i >= 8) //s[i] - s[i-8] >= r[i] (8 <= i <= 23) { //r[i] represent at i hour need at least r[i] employees edge[++eid].to = i; edge[eid].dis = hour_least[i]; edge[eid].next = head[i-8]; head[i-8] = eid; } } //s[i-1] - s[i] >= -Num[i] (0 <= i <= 23) 这里取i=0 //s[-1] - s[0] >= -Num[0] 这里的Num相当于hour_most //from 24 to 0 edge[++eid].to = 24; edge[eid].dis = -hour_most[0]; edge[eid].next = head[0]; head[0] = eid; for(int i = 0; i < 24; ++i) { //s[23] + s[i] - s[i+16] >= r[i] (0 <= i <= 7) if(i <= 7) { //build edge from (16、17...23) to (0、1、2...7) edge[++eid].to = i; edge[eid].dis = hour_least[i] - 0; //assume s[23] zero edge[eid].next = head[i+16]; head[i+16] = eid; relate23[i] = eid; //mark the id which relate to s[23] } //Add a source point 24 //build edge from source to i, and value of edge is zero edge[++eid].to = i; edge[eid].dis = 0; //24到23的距离最长先设为0 edge[eid].next = head[24]; head[24] = eid; if(i == 23) source_id = eid; } } bool spfa() { for(int i = 0; i < 25; ++i) { vis[i] = false; dis[i] = -(INF); count[i] = 0; } dis[24] = 0; //这个记得赋值为0,刚开始一直没找到错误,原来是这里 queue<int>que; que.push(24); vis[24] = true; while(!que.empty()) { int now = que.front(); que.pop(); for(int i = head[now]; i != -1; i= edge[i].next) { int to = edge[i].to; if(dis[to] - dis[now] < edge[i].dis) { dis[to] = dis[now] + edge[i].dis; if(vis[to] == false) { vis[to] = true; count[to]++; //判断负环,若进队次数比边数还多,那就说明有负环 if(count[to] > eid) return false; que.push(to); } } } vis[now] = false; } return true; } void find(int n_app) { int sum = 0; bool is_find = false; while(is_find == false && sum <= n_app) { //as from point i to i+16 distanse is r[i]-s[23] //here r[i] equal to hour_least[i] and s[23] equal to sum //if s[23] too small, there will have a loop when spfa() for(int i = 0; i <= 7; ++i) //这里与最后一个不等式相关的边都要更新 edge[relate23[i]].dis = hour_least[i] - sum; //这里要更新,当最长路和sum,即从24到23的距离跟 从24到23的最长路相等 //时就表示找到答案了 edge[source_id].dis = sum; //here is most important is_find = spfa(); //寻找最长路 //听说这里sum 不能等于4 不知道为什么 不过没有加上 ||sum == 4 if(is_find == false || dis[23] != sum)//也可以过 { is_find = false; sum++; } } if(is_find == true) printf("%d\n", sum); else puts("No Solution"); } int main() { int n_case; scanf("%d", &n_case); while(n_case--) { for(int i = 0; i < 24; ++i) { hour_most[i] = 0; //记录 scanf("%d", &hour_least[i]); } int n_app; //number of applicants scanf("%d", &n_app); for(int i = 0; i < n_app; ++i) { scanf("%d", &app_st[i]); //the most applicant can reach to i hour hour_most[app_st[i]]++; } buildMap(); find(n_app); } return 0; }