14278251 h123120 3249 Accepted 10452K 1907MS G++ 2266B 2015-06-09 13:56:59
这是一道动态规划的题,也是我第一次做动态规划的题,卡了我10个小时,各种没考虑到的条件。。。。在也不相信百度了,说好的Dp,结果是拓扑排序+DP我也是醉了~~
废话不多说了先说说思路:
DAG上的路径问题,找它点权最大的路径。dp的状态量为:从u点出发到终点的最大权值d[u],状态转移方程为 d[u]=max(d[u],dp(v)+p[u]) (u,v)属于点集,p[u]为某个点的权值
但有以下几个点需要注意:
1.起点不是任意的点,需要入度为0!!!当然对应的终点需要出度为0,但是终点一定具有出度为0的属性,所以不需要管它,只需要在输入的时候统计点的入度即可。
2.这道题有10^5个结点,所以如果用邻接矩阵来储存图,开不了这么大的二维数组,所以需要邻接链表来储存图,我也是第一次用,以前只在数据结构上见过这东西,当时想有矩阵,谁会用这东西,然后有去学,不过别人的解题报告中这种光用数组来做链表的思路很巧妙啊,我还需要多多学习啊~~
3,接下来就是dp函数了,各种虐心啊,特殊值选取不定,
1)因为本题的权值可以为负值,所以选取一个很小的-INF来初始化状态量d,
2)然后各种细节,什么搜索到终点的flag,仔细体会为什么要有这个东西,
3)还有return时,忘了给终点赋值,因为终点的状态量的权值就是他本身的权值,不是前边的权值叠加,但是不知道他是不是终点,所以在搜的时候搜到终点再赋值,我在return那赋值的return d[u]=p[u];
其他的没什么了,自己还太菜啊,被个一般的dp题,困住了~~
附送一组数据,正确答案是13
10 10
-5
6
-1
-2
5
-9
7
-4
10
-12
1 2
1 3
2 3
3 6
3 7
7 10
7 8
8 9
8 10
ac代码(这个代码方便我调试,不怎么简洁,下面赋个简洁的):
#include <iostream>
#include<cstdio>
#include <cstring>
#include<algorithm>
#include<memory>
using namespace std;
const int maxN=100024;
const int maxM=1000024;
int n,m,e;
int p[maxN],head[maxN],d[maxN],in[maxN],vis[maxN];
//int next[maxN];
struct Edge
{
int v,next;
}edge[maxM];
int dp(int u)
{
// cout<<"中间点"<<u<<endl;
if(vis[u]) {/*cout<<"已经得出过的值d["<<u<<"]:"<<d[u]<<endl;*/return d[u];}
vis[u]=1;
int& ans=d[u];
ans=-(1<<30);
int flag=0;
for(int i=head[u];i!=-1;i=edge[i].next)//利用邻接链表
{
int v=edge[i].v;//依次访问的结点
// ans=max(ans,dp(v)+p[u]);
flag=1;
int tmp=dp(v)+p[u];
// cout<<"临时状态量"<<"d["<<u<<"]:"<<tmp<<" "<<"当前结点:"<<v<<endl;
if(ans<tmp)//k可用max函数简化
{
ans=tmp;
// cout<<"最终状态量:d["<<u<<"]:"<<ans<<endl;
// next[u]=v;
}
// cout<<endl;
}
// cout<<"flag:"<<flag<<endl;
if(flag) { /*cout<<"返回的状态量"<<"d["<<u<<"]:"<<ans<<endl;*/return ans;}
else{/* cout<<"返回的状态量"<<"d["<<u<<"]:"<<p[u]<<endl;*/return d[u]=p[u];}
}
int main()
{
freopen("1.txt","r",stdin);
while(scanf("%d%d",&n,&m)==2)
{
for(int i=1;i<=n;++i)
scanf("%d",&p[i]);//输入利润
e=0;
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
memset(in,0,sizeof(in));
// memset(next,0,sizeof(next));
while(m--){
int a,b;
scanf("%d%d",&a,&b);
in[b]=1;//入度不为0
edge[e].v=b;//赋予它权值
edge[e].next=head[a];//建立链表
head[a]=e++;//他的表头指针 (用了之后再加)
}
for(int i=1;i<=n;i++) d[i]=-(1<<30);//初始化它~~
int temp=-(1<<30),first;
for(int i=1;i<=n;i++)
{
if(in[i]==0)
{
// cout<<"起点"<<i<<endl;
first=i;
temp=max(temp,dp(i));
}
}
// for(int i=first;i!=0;i=next[i])
// cout<<i;
//cout<<endl;
printf("%d\n",temp);
}
return 0;
}
分割线----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
简化的代码:
#include <iostream>
#include<cstdio>
#include <cstring>
#include<algorithm>
#include<memory>
using namespace std;
const int maxN=100024;
const int maxM=1000024;
int n,m,e;
int p[maxN],head[maxN],d[maxN],in[maxN],vis[maxN];
struct Edge
{
int v,next;
}edge[maxM];
int dp(int u)
{
if(vis[u]) {return d[u];}
vis[u]=1;
int& ans=d[u];
ans=-(1<<30);//特殊值注意!!
int flag=0;
for(int i=head[u];i!=-1;i=edge[i].next)//利用邻接链表
{
int v=edge[i].v;//依次访问的结点
flag=1;
ans=max(ans,dp(v)+p[u]);
}
if(flag) return ans;
else return d[u]=p[u];//别忘了赋值啊
}
int main()
{
freopen("1.txt","r",stdin);
while(scanf("%d%d",&n,&m)==2)
{
for(int i=1;i<=n;++i)
scanf("%d",&p[i]);//输入利润
e=0;
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
memset(in,0,sizeof(in));
while(m--){
int a,b;
scanf("%d%d",&a,&b);
in[b]=1;//入度不为0
edge[e].v=b;//赋予它权值
edge[e].next=head[a];//建立链表
head[a]=e++;//他的表头指针 (用了之后再加)
}
for(int i=1;i<=n;i++) d[i]=-(1<<30);//初始化它~~
int temp=-(1<<30),first;
for(int i=1;i<=n;i++)
{
if(in[i]==0)
{
first=i;
temp=max(temp,dp(i));
}
}
printf("%d\n",temp);
}
return 0;
}