P1137 旅行计划
P1137 旅行计划
题目描述
小明要去一个国家旅游。这个国家有N个城市,编号为1~N,并且有M条道路连接着,小明准备从其中一个城市出发,并只往东走到城市i停止。
所以他就需要选择最先到达的城市,并制定一条路线以城市i为终点,使得线路上除了第一个城市,每个城市都在路线前一个城市东面,并且满足这个前提下还希望游览的城市尽量多。
现在,你只知道每一条道路所连接的两个城市的相对位置关系,但并不知道所有城市具体的位置。现在对于所有的i,都需要你为小明制定一条路线,并求出以城市i为终点最多能够游览多少个城市。
输入输出格式
输入格式:
输入的第1行为两个正整数N, M。
接下来M行,每行两个正整数x, y,表示了有一条连接城市x与城市y的道路,保证了城市x在城市y西面。
输出格式:
输出包括N行,第i行包含一个正整数,表示以第i个城市为终点最多能游览多少个城市。
输入输出样例
5 6 1 2 1 3 2 3 2 4 3 4 2 5
1 2 3 4 3
说明
均选择从城市1出发可以得到以上答案。
对于20%的数据,N ≤ 100;
对于60%的数据,N ≤ 1000;
对于100%的数据,N ≤ 100000,M ≤ 200000。
分析:
方法一:
拓扑结构+简单dp(其实可以边拓扑边dp)
1、用栈实现拓扑结构,找出拓扑序列
2、在这个拓扑序列的基础上,找所有i节点西边的点中经过城市最多的点+1,就是i对应的答案
个人感觉verctor数组存图要比数组模拟链表存图方便很多。
1 #include<cstdio> 2 #include<stack> 3 #include<vector> 4 using namespace std; 5 6 int n,m,x,y,ru[100001],ans[100001]; 7 vector<int> ro[100001],tot; 8 stack<int> q; 9 10 int main() 11 { 12 scanf("%d%d",&n,&m); 13 for(int i=1;i<=m;i++) 14 { 15 //读入数据 16 scanf("%d%d",&x,&y); 17 //y节点的入度加1 18 ru[y]++; 19 //相当于x多了个孩子 20 ro[x].push_back(y); 21 } 22 //下面是拓扑排序,用栈实现,其实和队列实现差不多 23 //如果入度为0,入栈,对应的答案为1 24 for(int i=1;i<=n;i++) 25 if(!ru[i]) q.push(i),ans[i]=1; 26 //当q中还有元素的时候 27 while(!q.empty()) 28 { 29 //取栈最上面的元素 30 int k=q.top();q.pop(); 31 tot.push_back(k); 32 //找所有连接的边 33 for(int i=0;i<ro[k].size();i++) 34 { 35 //如果入度为空,继续整入栈 36 ru[ro[k][i]]--;if(!ru[ro[k][i]]) q.push(ro[k][i]); 37 } 38 } 39 //遍历这n个点,简单dp 40 for(int i=0;i<tot.size();i++) 41 { 42 int k=tot[i]; 43 //找这个点的所有入度的点 44 for(int j=0;j<ro[k].size();j++) 45 { 46 //找到这些点中节点数最大的,+1 47 int z=ro[k][j]; 48 if(ans[z]<ans[k]+1) ans[z]=ans[k]+1; 49 } 50 } 51 for(int i=1;i<=n;i++) printf("%d\n",ans[i]); 52 return 0; 53 }
方法二:
栈实现spfa
1、找到起点
2、从起点处做spfa,用出栈的点去更新别的所有点
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<stack> 5 using namespace std; 6 #define maxm 200010 7 int n,m,ans,num,head[maxm],dp[100010],ru[100010]; 8 stack<int>q; 9 bool vis[100010]; 10 //边 11 struct node{ 12 int pre,to; 13 }e[maxm]; 14 //数组模拟链表 15 void Insert(int from,int to){ 16 e[++num].to=to; 17 e[num].pre=head[from]; 18 head[from]=num; 19 } 20 void spfa(){ 21 //栈中还有元素 22 while(!q.empty()){ 23 //元素出栈 ,表示不再栈中 24 int point=q.top();q.pop();vis[point]=0; 25 //遍历point的所有出度的边 26 for(int i=head[point];i;i=e[i].pre){ 27 int to=e[i].to; 28 //用这个节点去更新所有节点 29 if(dp[to]<dp[point]+1){ 30 dp[to]=dp[point]+1; 31 if(!vis[to]){ 32 vis[to]=1;//表示在栈中 33 //被更新的节点继续入栈 34 q.push(to); 35 } 36 } 37 } 38 } 39 } 40 //读数 41 int qread(){ 42 int i=0; 43 char ch=getchar(); 44 while(ch<'0'||ch>'9')ch=getchar(); 45 while(ch<='9'&&ch>='0'){i=i*10+ch-'0';ch=getchar();} 46 return i; 47 } 48 int main(){ 49 //读数据 50 n=qread();m=qread(); 51 int x,y; 52 for(int i=1;i<=m;i++){ 53 x=qread();y=qread(); 54 //插入x到y的边 55 Insert(x,y); 56 //y的入度加1 57 //入度出度这个写在读入数据这里是极好的 58 ru[y]++; 59 } 60 //遍历n个节点,将空节点先入栈 61 for(int i=1;i<=n;i++){ 62 if(ru[i]==0){ 63 q.push(i); 64 vis[i]=1; 65 //并且把值置为1 66 dp[i]=1; 67 } 68 } 69 //做spfa 70 spfa(); 71 for(int i=1;i<=n;i++)printf("%d\n",dp[i]); 72 }
方法三:
记忆化搜索
这个题只能向一边走而不能走环,所以满足无后效性原理,显然可以动态规划,一般题目显然搜索比较好(ˇˍˇ) 想;但是搜索往往会是指数级别的时间复杂度,这个题的最后几组数据显然不支持指数级别的算法,但是记忆化搜索可以满足需求,就是记忆每一次搜索的结果,可以很快速的完成搜索,而且记忆化搜索的时间复杂度和动归理论上是一样的,于是很多动归题目实在想不出来都可以记忆化搜索,有时还能减支优化。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio> 4 #include<vector> 5 #include<algorithm> 6 #define MAXN 400010 7 using namespace std; 8 vector<int>l[MAXN]; 9 int n,m,u[MAXN],v[MAXN],dp[MAXN],cnt=1; 10 void add(int x,int y) 11 { 12 u[++cnt]=x;v[cnt]=y; 13 //把边的编号信息加入东边的那个点 14 l[x].push_back(cnt); 15 } 16 int dfs(int x) 17 { 18 int i; 19 //如果搜索过x这个点,就返回 20 if(dp[x])return dp[x]; 21 //初始值置为1 22 dp[x]=1; 23 //找所有x点的入边 24 for(i=l[x].size()-1;i>=0;i--) 25 { 26 int k=l[x][i]; 27 //所有入边中的最大值+1即为结果 28 //其实是在边搜索边dp 29 dp[x]=max(dp[x],dfs(v[k])+1); 30 } 31 return dp[x]; 32 } 33 int main() 34 { 35 int i,j,k; 36 //读数据 37 scanf("%d%d",&n,&m); 38 for(i=1;i<=m;i++) 39 { 40 int a,b; 41 scanf("%d%d",&a,&b); 42 //加边 43 add(b,a); 44 } 45 //记忆化搜索 46 for(i=1;i<=n;i++) 47 cout<<dfs(i)<<endl; 48 return 0; 49 }