这题的确是一道好题,很好的将状态dp以及图论的最短路径,这里上面的权值表示的花费的钱,另外还有很多约束问题,
首先大体描述下这道题,就是一个技术员,遍历N个节点,首先他一开始在0号节点,且是星期一,然后遍历1..N-1 编号的节点,这里要求每个节点只能走一次,而且必须每个节点都得走到,最后还要回到0号节点,而且从一个定点到另一个顶点是要花费p天的时间,还要要一定的钱,而且在每个节点也至少得待一天的时间,最后的最后,要我们解决的问题是,首先判定他是否能够每个节点都能走到,另外,如果能又是否能在星期六或星期天到,若是,那么我们至少得花多少钱呢!
问题已经很清晰了,现在的问题就是如何求了,按照我开始的思路,首先判定这是否是一个遍历所有节点的回路,然后既然这是一个状态dp的问题,那么肯定是涉及到状态的改变的,那么状态是什么呢,这里就是走过的节点,可以作为状态,如果已经走过,那么为1,否则为0,那么N个节点可以表示成一个二进制串,然后相对应的十进制值就是状态数,如上,我们如何判断状态的合法性,这里就是走过的节点就不要回去了,所以就很好判断了,也就是说每一位只能置1一次,好,搞过之后,我们在找状态方程,因为这里好像很隐藏,
因为问题描述当中,有要求的天数,和花费的钱数,那么这个方程到底怎么表示呢!
这里设dp[state][des][d] 这个方程的意思就是状态为state时,且到达了des节点,且此时是星期d ,另外这个值就是达到这点所花费的钱 0<=state<2^N,1<=des<=N-1,0<=d<7
然后就是初始化问题,dp[0][0][0]=inf,dp[1][0][0]=0,然后就是用bfs遍历找出求最短路劲了,这里最短路劲也好求,首先在输入的时候,建立邻接表,bfs的用队列维护,这里就如上所述,所涉及的要保存的信息很多,于是我们想到结构体,用结构体来表示节点以及边,这样,这个过程就很好办了,当然我们知道bfs的过程中,还有一个visit的,这里也需要建立类似的标记,这里要和dp有同样的大小,
这样搞过之后,最后就很好办了,首先dp[2^N-1][1....N][D] ,循环遍历在目标1....N个节点中看看有没到0号节点的,两个fou循环,找出最小值,同时判断是否能够在星期六和星期天道,这些都很好办了,,接下来就可以去是实现了,,就是苦力活了,,有许多的细节,要考虑全面!
下面的代码就是写给自己看的,成功AC,,要实现就自己写吧!
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#define inf 0x7fffffff
using namespace std;
struct edge
{
int y,p,t; //含义如题所述
};
struct node
{
int s,n,d; //s表示当前状态,n表示节点标号,d表示经过的天数
};
vector<edge> num[11];
int dp[1<<10][11][7];
int visit[1<<10][11][7];
queue<node> qu;
int N,M;
void sloveinput()
{
//初始化
for(int i=0;i<(1<<N);i++)
for(int j=0;j<N;j++)
for(int k=0;k<7;k++)
{
dp[i][j][k]=inf;
visit[i][j][k]=0;
}
for(int i=0;i<N;i++)
num[i].clear();
edge tmp;
for(int i=0;i<M;i++)
{
int a;
cin>>a>>tmp.y>>tmp.p>>tmp.t;
num[a].push_back(tmp);
}
//邻接表以建立好
}
void slovedp()
{
//这个函数就是要求dp[][][]里面能求的值,要用队列
node start;
edge tmp;
start.s=1;start.n=0;start.d=0;
//
tmp.y=0;tmp.p=0;tmp.t=0;
//这里从0号节点开始,00...001 所以状态s为1,但是节点y为0,当然p,和t为0
// qu.clear();
dp[1][0][0]=0;
visit[1][0][0]=1;
qu.push(start);
while(!qu.empty()) //开始bfs喽
{
node tp=qu.front();
qu.pop();
for(int i=0;i<num[tp.n].size();i++) //在这些临边遍历
{
if(!(tp.s&(1<<num[tp.n][i].y)))
//这说明以前这个节点还是没有走过的,那么可扩展,
//这里就是判定合法状态的条件,状态dp一般到要这样
{
//建立节点保存,并入队列,
node node1;
node1.s=tp.s|(1<<num[tp.n][i].y);
node1.n=num[tp.n][i].y;
node1.d=(tp.d+num[tp.n][i].t+1)%7;
//到这里后,如何判定入栈呢
if(dp[tp.s][tp.n][tp.d]+num[tp.n][i].p<dp[node1.s][node1.n][node1.d])
//如果通过上一个状态 花费加上到当前节点的花费小于 当前状态的花费,则扩充,入栈,//这里是重要的地方,状态转移方程
{
dp[node1.s][node1.n][node1.d]=dp[tp.s][tp.n][tp.d]+num[tp.n][i].p; //更新吧
if(!visit[node1.s][node1.n][node1.d])
{
visit[node1.s][node1.n][node1.d]=1;
qu.push(node1);
}
}
}
}
}
}
void sloveans()
{
//这个收尾就很简单喽
int end=(1<<N)-1;
int flag1=0,flag2=0;
int ans=inf;
for(int i=1;i<=N-1;i++) //遍历
for(int j=0;j<num[i].size();j++)
{
if(num[i][j].y==0) //说明有回到0节点的
{
for(int dd=0;dd<7;dd++) //判定是否能在周六或周日到
{
if(dp[end][i][dd]<inf)
//说明有成功到达0节点的,且遍历了所有节点,这里一开始写成里 dp[end][num[i][j].y][dd]<inf,细节问题一定要注意啊
{
flag1=1;
//再是第二个条件,看是否能在周六或周日到达
if(((dd+num[i][j].t)%7)>=5&&ans>dp[end][i][dd]+num[i][j].p)
//这里把星期一去掉,默认从0,那么经过t天后,如果是5,6,就说明了是能在星期六和星期日到达的
//并且找到满足的最小花费值
{
flag2=1;
ans=dp[end][i][dd]+num[i][j].p;
}
}
}
}
}
if(!flag1) cout<<"It's not my thing!"<<endl;
else if(!flag2) cout<<"Oh, My god!"<<endl;
else cout<<ans<<endl;
}
int main()
{
while(cin>>N>>M)
{
sloveinput();
slovedp();
sloveans();
}
}