2016微软探星夏令营在线技术笔试题解(4)

(p.s:本题是本次笔试的最后一题,难度确实有。。。而且俺至今都没有做出来。。。。)
 

题目来源: 
        HihoCoder1343
 

题目描述:
    Little Hu is taking paid part-time jobs in his college. There are N buildings in his college, numbered from 1 toN.These buildings are connected by M bi-directional paths in such a way that every pair of buildings is connected by these paths directly or indirectly. Moving from one building to another will cost Little Hu some time.

In the morning Little Hu receives Q jobs. Each job can be described with 5 integers, LiSiEiTiCiLi, the building number of the i-th job, describes the location to do the job. Si is the earliest time to start the job and Eiis the latest time to start the job. Ti is the time cost. Ci is the amount of money paid if Little Hu finished the job on time.
    
Little Hu starts at time 0 in building 1. He wants to maximize his total pay by carefully selecting jobs. Can you write a program to help him find the maximum total pay?

题目大意:
    有N个不同的建筑物之间有M条道路相连,道路的设计保证任意两个城市之间都是连通的,并且通过每条道路都需要花一定的时间。同时有Q个不同的工作分布在这N个建筑物中,每份工作给出了工作所在的位置、最早和最晚的开始时间以及工作需要花费的时间,最后给出了完成工作后可以得到的报酬。
    现假设在0时刻Little Hu在1号建筑物,题目要求通过合理地安排工作方案,Little Hu可以得到的最多的报酬。

解答:
    本题难度较大,考查了算法的综合使用。本题结合了图算法、状态压缩以及动态规划的知识。基本思路是:首先对于任意的两份工作,计算出从其中一份工作所在的地点到另一份工作所在的地点所走过的最短路径,然后在通过状态压缩和动态规划的思想求得本题的最优解。具体解法如下:

·最短路径:
    由于每份工作都规定了工作开始的最早和最晚的时间,如果到达工作所在地点的时候,时间已经超过了工作最晚开始的时间,那么该工作就无法完成。因此,基本的思路就是:尽可能少地将时间花在路上的移动过程。因此我们需要知道任意两个建筑物之间的最短距离,使得路上花费的时间尽可能少。由于并不是每一座建筑物内都有工作安排,因此,我们仅需要知道在有工作安排的建筑物之间切换时的最短路径就可以了。计算最短路径可以使用经典的迪杰斯特拉(dijkstra)算法。以每一个有工作安排的建筑作为源点计算一次。
    经典的迪杰斯特拉算法的时间效率为O(n²),可以采用堆堆算法进行优化,使得整个算法的效率为O(nlgn)。
    可以采用一个Q×Q的矩阵dist来存储最短路径的数据,元素dist[i][j]记录了从编号为i的工作的地点移动到编号为j的工作的地点所需要的最少的时间。

·状态压缩:
    在计算出最短路径后,我们采用动态规划的方式计算结果。首先要做的是定义状态。这里的状态涉及到了选择哪些工作完成,同时还涉及到了以什么样的顺序完成这些工作。这里采用一个Q位的二进制数来标记哪些工作被完成:对于第i位二进制位,如果该位值是0,表示编号为i的工作没有完成,如果值是1,则表示编号为i的工作已经完成。为了记录工作完成的顺序,我们需要记录当前最后完成的工作的编号。定义状态如下:

    dp[s][k]当状态为s,并且最后完成的工作为k号工作时,需要花费的最少时间。如果当前状态是不可行的,则其值置为-1。

需要说明的是:对于dp[s][k]中的状态s,如果它的第k为二进制位的值为0,那么dp[s][k]一定是非法的。因为既然当前状态下最后完成的工作为k号,那么也就意味的k号工作一定已经完成,因此状态s中的表示k号工作的第k为二进制位的值一定是1。

·动态规划:
    定义状态后,我们还需要知道状态的转移方式。对于dp[s][k],当我们得到它的值时,表明当前的状态为:完成工作的状态是s,并且最后完成的工作为k号工作。接下来的策略就是,继续选择目前尚未完成的工作,移动到该工作的地点,然后完成工作。此时,就转移到了下一个状态。
    对于当前状态到底哪些工作还没有完成,则取决于状态值s,s中哪些值为0的二进制位,就表示对应的工作尚未完成。
    假设当前状态下,编号为m的工作尚未完成,此时,我们需要进行以下步骤:
    首先需要移动到工作m的所在地点,由于当前状态下,最后完成的工作时工作k,所以需要从工作k的地点移动到工作m的地点,花费的时间是dist[k][m]
    然后完成工作m,花费是时间是给定的工作m需要花费的时间T(m)
    然后更新状态s,标记工作m已经完成,即此时,将s中的第m为二进制位值为1,我们用按位或运算完成状态值的更新。新的状态值: s_new = s | [2^(m-1)]
    总最后完成的工作就是工作m,因此新的状态就是:dp[s_new][m],于是,我们利用dp[s][k]的结果得到了dp[s_new][m],具体
dp[s_new][m]的值,则分为以下的几种情况:

    ① 当到达工作m的地点时,时间已经超过了工作m的最晚开始时间E(m),即:

dp[s][k] + dist[k][m] > E(m)

表示当前工作无法完成,该方案不可行,因此不需要更新dp[s_new][m]。
    ② 当到达工作m的地点时,时间要比工作m的最早开始时间S(m)还要早,即:

dp[s][k] + dist[k][m] < S(m)
表示当前情况为提前到达,需要等待,到时间S(m)时才可以开始工作。因此,工作完成时的时间为:S(m) + T(m),此时需要更新dp[s_new][m]的值,比较当前计算结果和dp[s_new][m]的原始值,保留较小者:
若dp[s_new][m] 的值为-1:直接更新:
    dp[s_new][m] = S(m) + T(m)
否则,保留较小者:
    dp[s_new][m] = min{dp[s_new][m], S(m) + T(m)} 
    ③ 当到达工作m的地点时,时间介于S(m)和E(m)之间,即:
    S(m)≤dp[s][k] + dist[k][m] ≤ E(m) 
表示此时可以立刻开始进行工作m,花费时间同样是T(m),完成后的时间为:
dp[s][k] + dist[k][m] + T(m)
此时和情况②类似,也需要更新dp[s_new][m]的值:
若dp[s_new][m] 的值为-1:直接更新:
    dp[s_new][m] = dp[s][k] + dist[k][m] + T(m)
否则,保留较小者:
    dp[s_new][m] = min{dp[s_new][m], dp[s][k] + dist[k][m] + T(m)} 
    以上为状态转移的规则。
    需要说明的是:dp[s][k]中s的范围是 0 - (2^Q-1), k的范围则是1-Q。对于工作数目Q数值较大的情况,在通过动态规划计算结果时,要处理的数据量也会呈指数式增长。所以,在某些情况下,这个过程的效率非常低。

·初始化和迭代顺序:
    在进行状态转移前,需要一个初始的状态,这里我们可以定义一个“冗余”的工作。它的工作地点就在最开始的起点:建筑1,同时,为了不影响计算结果,它的最早开始时间,最晚开始时间,持续时间以及工作的报酬均置为0,该工作的编号也记为0,并且该工作不需要再状态值s中记录,即此时工作的数目为Q+1,但s依旧是一个Q位的二进制数。
    此时,完成工作0所需的最小时间就是0,这种情况最后完成的是工作0,并且其他的工作还都没有完成,即:
    dp[0][0] = 0
    在状态转移过程中,由于每次状态转移过程都会使得完成的工作数目增加,因此我们的计算顺序由dp[s][k]中的状态值s决定。这里的迭代顺序取决于s中二进制位值为1的位置的数目。首先处理只有1位二进制位值为1的所有状态值s的情况,然后处理2个1的情况、3个1的情况......这里可以使用队列来保证迭代顺序的正确性,每次迭代取出队列首部的元素,同事将计算过程中转移到的新状态加入到队列中。

·计算结果:
    动态规划计算过程完成后,我们通过遍历所有的状态dp[s][k],如果dp[s][k]的结果不是-1,表明当前的方案是可行的,然后根据状态值s计算可以得到的报酬,最后输出得到的最大值即可。 

以上是本题的解答思路。

输入输出格式:
    输入:
The first line contains 3 integers N, M and Q.

The following M lines describe the paths. Each line contains 3 integers UiVi and Wi indicating that there is path between building Ui and Vi and moving from Ui to Vi along the path, or vice versa, costs time Wi.

The following Q lines describe the jobs. Each line contains 5 integers LiSiEiTiCi.

For 20% of the data: 1 ≤ UiViLi ≤ N ≤ 5, 1 ≤ M ≤ 20, 1 ≤ Q ≤ 5

For 40% of the data: 1 ≤ UiViLi ≤ N ≤ 100, 1 ≤ M ≤ 1000, 1 ≤ Q ≤ 10

For 100% of the data: 1 ≤ UiViLi ≤ N ≤ 10000, 1 ≤ M ≤ 1000000, 1 ≤ Q ≤ 20


For 100% of the data: 0 ≤ Si  Ei ≤ 10000, 1 ≤ Ti, Wi ≤ 10000, 1 ≤ Ci ≤ 100, Ui  Vi
 输出:
         Output the maximum pay Little Hu can get.
备注:
    
本题的数据基数比较大,题目中工作的数目Q最大值为20,地点数N最大值是10000,在这种情况下,动态规划的搜索空间是:2^20 × 20,这是一个庞大的数字。因此,本题在某些情况下会卡算法时间效率的常数因子。
    程序处理的每一个环节和步骤,必须要尽可能的达到最高的效率,否则,即使是同样的解题思路,有些程序代码提交后同样会超时。笔者就是比较悲催的后者 ,后面的代码先给出笔者自己的超时代码,然后给出一份别人的AC代码。 
 

程序代码:  

·俺的代码:

/****************************************************/
/* File        : HihoCoder1344                      */
/* Author      : Zhang Yufei                        */
/* Date        : 2016-07-22                         */
/* Description : HihoCoder ACM program. (submit:g++)*/
/*               Result: TLE                        */
/****************************************************/

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<memory.h>

/*
 * Define the job.
 * Parameters:
 *		@L: The location of job.
 *		@S & @E: The easiest and latest time to start the job.
 *		@T: The time to finish the job.
 *		@C: The salary of this job.
 */
typedef struct node1 {
	int L, S, E, T, C;
} job;

/*
 * The heap node use in dijkstra algorithm.
 * Parameters:
 *		@value: The value of this node.
 *		@index: The index of this node.
 */ 
typedef struct node2 {
	int value;
	int index;
} heap_node;

// Input data.
int M, N, Q;

// The matrix to store graph.
int **matrix;

// Record the mininum distance between every two jobs.
int **dist;

// Job list.
job *jobs;

// Dp matrix.
int **dp;

// Queue used in dp. 
int *indexes;
int index_cnt = 0;

// Mark if the state has been in queue.
int *tags;

// Heap used in dijkstra algorithm.
heap_node **heap;
int heap_size;

// temp-space used in dijkstra algorithm.
heap_node *tmp;
int *set;

/*
 * Basic function used in heap sort.
 * Parameters:
 *		@cur: The current node to operate.
 * Returns:
 *		None.
 */
void min_heapify(int cur) {
	int min, min_index;
	min = heap[cur]->value;
	min_index = cur;
	
	if(2 * cur <= heap_size) {
		if(min == -1 ||
			heap[2 * cur]->value != -1 && 
			heap[2 * cur]->value < min) {
			min = heap[2 * cur]->value;
			min_index = 2 * cur;
		}
	}
	
	if(2 * cur + 1 <= heap_size) {
		if(min == -1 ||
			heap[2 * cur + 1]->value != -1 && 
			heap[2 * cur + 1]->value < min) {
			min = heap[2 * cur + 1]->value;
			min_index = 2 * cur + 1;
		}
	}
	
	if(min_index != cur) {
		heap_node *tmp = heap[cur];
		heap[cur] = heap[min_index];
		heap[min_index] = tmp;
		
		min_heapify(min_index);
	}	
}

/*
 * Remove the top element from heap.
 * Parameters:
 *		None.
 * Returns:
 *		The removed node.
 */
heap_node* remove(void) {
	heap_node *del = heap[1];
	heap[1] = heap[heap_size];
	heap_size--;
	
	min_heapify(1);
	
	return del;
}

/*
 * This function created a heap.
 * Parameters:
 *		None.
 * Returns:
 *		None.
 */
void create_heap(void) {
	for(int i = heap_size; i > 0; i--) {
		min_heapify(i);
	}
}

/*
 * This function update a node value in heap.
 * Parameters:
 *		@cur: The node to update.
 * Returns:
 *		None.
 */
void update(int cur) {
	if(cur / 2 > 0) {
		if(heap[cur / 2]->value == -1 ||
			heap[cur / 2]->value > heap[cur]->value) {
			heap_node *tmp = heap[cur];
			heap[cur] = heap[cur / 2];
			heap[cur / 2] = tmp;
			
			update(cur / 2);
		}
	}
}

/*
 * Compute the shortest path using dijkstra algorithm.
 * Parameters:
 *		@start: The start vertex.
 * Returns:
 *		None.
 */ 
void dijkstra(int start) {
	 
	for(int i = 0; i < N; i++) {
		tmp[i].value = -1;
		tmp[i].index = i;
		heap[i + 1] = &tmp[i];
		set[i] = 0;
	}
	
	tmp[jobs[start].L].value = 0;
	set[jobs[start].L] = 1;
	
	heap_size = N;
	create_heap();
	
	for(int cnt = 1; cnt < N; cnt++) {
		heap_node *min = remove();
		int cur = min->index;
		
		for(int i = 0; i < N; i++) {
			if(set[i] == 1) {
				continue;
			} 
			if(matrix[cur][i] != -1) {
				if(tmp[i].value == -1 ||
					tmp[i].value > tmp[cur].value + matrix[cur][i]) {
					tmp[i].value = tmp[cur].value + matrix[cur][i];
				}
			}
		}
	}
	
	dist[start][Q] = dist[Q][start] = tmp[0].value;
	for(int i = 0; i < Q; i++) {
		dist[start][i] = dist[i][start] = 
			tmp[jobs[i].L].value;
	} 
}

/*
 * This function computes the salary according to state value.
 * Parameters:
 *		@state: The state value to compute.
 * Returns:
 *		The total salary.
 */ 
int compute(int state) {
	int cnt = 0;
	int sum = 0;
	while(state > 0) {
		if(state % 2 == 1) {
			sum += jobs[cnt].C;
		}
		state /= 2;
		cnt++;
	}	
	
	return sum;
}

/*
 * Compute result using dynamic programming.
 * Parameters:
 *		@last: The id of the job last finished.
 *		@state: The current state value.
 * Returns:
 *		None.
 */
void dp_compute(int last, int state) {
	int s = state;
	int weight = 1;
	for(int i = 0; i < Q; i++) {
		if(s % 2 == 0) {
			if(dp[last][state] + dist[last][i] <= 
				jobs[i].E) {
				if(dp[last][state] + dist[last][i] >= jobs[i].S) {
					if(dp[i][state | weight] == -1 ||
						dp[last][state] + dist[last][i] +
						jobs[i].T < dp[i][state | weight]) {
						dp[i][state | weight] = 
							dp[last][state] + dist[last][i] +
							jobs[i].T;	
					}
				} else {
					if(dp[i][state | weight] == -1 ||
						jobs[i].S + jobs[i].T < dp[i][state | weight]) {
						dp[i][state | weight] = 
							jobs[i].S +	jobs[i].T;	
					}
				}
				
				if(tags[state | weight] == 0) {
					tags[state | weight] = 1;
					indexes[index_cnt++] = state | weight;
				}
			}
		}
		weight *= 2;
		s /= 2;
	} 	
}

/*
 * The main program.
 */
int main(void) {
	scanf("%d %d %d", &N, &M, &Q);
	
	matrix = (int**) malloc(sizeof(int*) * N);
	for(int i = 0; i < N; i++) {
		matrix[i] = (int*) malloc(sizeof(int) * N);
		memset(matrix[i], -1, sizeof(int) * N);
		matrix[i][i] = 0;
	}
	
	for(int i = 0; i < M; i++) {
		int U, V, W;
		scanf("%d %d %d", &U, &V, &W);
		U--;
		V--;
		if(matrix[U][V] == -1 || matrix[U][V] > W) {
			matrix[U][V] = matrix[V][U] = W;
		}
	}
	
	jobs = (job*) malloc(sizeof(job) * Q);

	dist = (int**) malloc(sizeof(int*) * (Q + 1));
	for(int i = 0; i < Q + 1; i++) {
		dist[i] = (int*) malloc(sizeof(int) * (Q + 1));
	}
	
	for(int i = 0; i < Q; i++) {
		scanf("%d %d %d %d %d", 
			&jobs[i].L, &jobs[i].S, &jobs[i].E, 
			&jobs[i].T, &jobs[i].C);
		jobs[i].L--;
	}
	
	tmp = (heap_node*) malloc(sizeof(heap_node) * N);
	set = (int*) malloc(sizeof(int) * N);
	heap = (heap_node**) malloc(sizeof(heap_node*) * (N + 1));
	
	for(int i = 0; i < Q; i++) {
		int tag = 0;
		for(int j = 0; j < i; j++) {
			if(jobs[j].L == jobs[i].L) {
				tag = 1;
				for(int k = 0; k < Q + 1; k++) {
					dist[i][k] = dist[k][i] = dist[j][k];
				}
				break;
			}
		}
		if(tag == 0) {
			dijkstra(i);
		}
	}
	
	dp = (int**) malloc(sizeof(int*) * Q);
	int c = (int) pow(2, Q);
	indexes = (int*) malloc(sizeof(int) * c);
	index_cnt = 0;
	tags = (int*) malloc(sizeof(int) * c);
	
	memset(tags, 0, sizeof(int) * c);
	
	for(int i = 0; i < Q; i++) {
		dp[i] = (int*) malloc(sizeof(int) * c);
		for(int j = 0; j < c; j++) {
			dp[i][j] = -1;
		}	
	}
	
	for(int i = 0; i < Q; i++) {
		if(dist[Q][i] <= jobs[i].E) {
			if(dist[Q][i] >= jobs[i].S) {
				dp[i][1<<i] = dist[Q][i] + jobs[i].T;
			} else {
				dp[i][1<<i] = jobs[i].S + jobs[i].T;
			}
			tags[1<<i] = 1;
			indexes[index_cnt++] = 1<<i;
		}
	}
	
	int max = 0;
	for(int i = 0; i < index_cnt; i++) {
		int s = indexes[i];
		int count = 0;
		int r = compute(indexes[i]);
		if(max < r) {
			max = r;
		}
		while(s > 0 ) {
			if(s % 2 == 1) {
				if(dp[count][indexes[i]] != -1) {
					dp_compute(count, indexes[i]);
				}
			}
			s /= 2;
			count++;
		}
	}
	
	printf("%d\n", max);
	
	return 0;
} 
·AC代码:

#include <stdio.h>  
#include <ctype.h>  
#include <string.h>  
#include <stdlib.h>  
#include <limits.h>  
#include <math.h>  
#include <algorithm>  
#include <vector>  
#include <queue>  
using namespace std;  
typedef long long ll;  
  
template <class T>  
inline void scan(T &ret) {  
    char c; ret=0;  
    while((c=getchar())<'0'||c>'9');  
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();  
}  
  
template<class X,int MaxSize>  
struct LoopQueue{  
    X data[MaxSize];  
    int head,tail,sz;  
    LoopQueue(){  
        init();  
    }  
    void init(){  
        head=0;tail=0;sz=0;  
    }  
    void push(const X& x){  
        data[tail]=x;  
        tail++;if(tail>=MaxSize)tail-=MaxSize;  
        sz++;  
    }  
    void pop(){  
        head++;if(head>=MaxSize)head-=MaxSize;  
        sz--;  
    }  
    X& front(){  
        return data[head];  
    }  
    int size(){  
        return sz;  
    }  
    bool empty(){  
        return head==tail;  
    }  
};  
  
int n,m,q;  
vector<pair<int,int> > G[10005];  
  
struct Job{  
    int L,S,E,T,C;  
    void get(){  
        scan(L);scan(S);scan(E);scan(T);scan(C);  
    }  
}job[25];  
  
struct State{  
    int mask;  
    int lastp;  
    State(){}  
    State(int _m,int _p):mask(_m),lastp(_p){}  
};  
LoopQueue<State,10000000> qu;  
int matDis[25][25];  
int dp[1<<21][20];  
bool checked[1<<21][20];  
bool vis[1<<21];  
  
int dis[10005];  
void dij(int x){  
    fill(dis,dis+n+1,INT_MAX/2);  
    dis[x]=0;  
    priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;  
    q.push(make_pair(0,x));  
    while(!q.empty()){  
        pair<int,int> tx=q.top();q.pop();  
        if(tx.first!=dis[tx.second])continue;  
        int p=tx.second;  
        for(int i=0;i<G[p].size();++i){  
            pair<int,int> edge=G[p][i];  
            int y=edge.first,w=edge.second;  
            if(dis[y]>dis[p]+w){  
                dis[y]=dis[p]+w;  
                q.push(make_pair(dis[y],y));  
            }  
        }  
    }  
}  
  
int ans=0;  
void checkAns(int mask){  
    int tans=0;  
    for(int i=1;i<=q;++i){  
        if((mask & (1<<(i-1)))){  
            tans+=job[i].C;  
        }  
    }  
    ans=max(ans,tans);  
}  
  
int main(){  
    scan(n);scan(m);scan(q);  
    for(;m--;){  
        int u,v,w;  
        scan(u);scan(v);scan(w);  
        G[u].push_back(make_pair(v,w));  
        G[v].push_back(make_pair(u,w));  
    }  
    for(int i=1;i<=q;i++){  
        job[i].get();  
    }  
    job[0].L=1;  
    for(int i=0;i<=q;i++){  
        matDis[i][i]=0;  
        if(i==q)break;  
        bool flag=false;  
        for(int j=0;j<i;j++){  
            if(job[i].L==job[j].L){  
                flag=true;  
                for(int k=0;k<=q;k++){  
                    matDis[i][k]=matDis[j][k];  
                }  
                break;  
            }  
        }  
        if(flag)continue;  
          
        dij(job[i].L);  
        for(int j=i+1;j<=q;j++){  
            matDis[i][j]=matDis[j][i]=dis[job[j].L];  
        }  
    }  
      
    fill(dp[0],dp[(1<<q)+1]+20,INT_MAX/2);  
    dp[0][0]=0;  
    qu.push(State(0,0));  
    while(!qu.empty()){  
        State& x=qu.front();qu.pop();  
        if(!vis[x.mask])checkAns(x.mask),vis[x.mask]=1;  
        for(int i=1;i<=q;++i){  
            if(!(x.mask & (1<<(i-1)))){  
                int startTime=max(job[i].S,dp[x.mask][x.lastp]+matDis[x.lastp][i]);  
                if(startTime>job[i].E)continue;  
                State y(x.mask|(1<<(i-1)),i);  
                if(dp[y.mask][i]>startTime+job[i].T){  
                    dp[y.mask][i]=startTime+job[i].T;  
                    if(checked[y.mask][y.lastp])continue;  
                    checked[y.mask][y.lastp]=1;  
                    qu.push(y);  
                }  
            }  
        }  
    }  
      
    printf("%d\n",ans);  
    return 0;  
}  



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值