Description
给定一个
n
个点,
Input
第一行包含两个正整数
n,m(1<=n<=20000,0<=m<=25000)
,分别表示点数和边数。
第二行包含
n
个整数,其中第
接下来
Output
输出一行一个整数,即最小的总费用。
Sample Input
6
3
1
2
1
3
4
4
Sample Output
7
HINT
分别在
据说没有任意两点间不存在节点数超过
10
的简单路径就是一个NP问题?
为了做DP,就根据dfs搜索树进行树DP。
然后发现对一个点的影响不仅有它的儿子,可能还有它的祖先……
然后就一脸懵逼辣QAQ
对每一个独立的联通块DP,令
f[i][s]
表示在dfs搜索树中深度为
i
的点,其祖先的状态为
如何转移?
访问节点
第一种转移:节点
u
不选。
若其中有一个点选了,则
第二种转移:节点
u
选了。
那么将
对于它的儿子,直接用 min(f[dep+1][s],f[dep+1][s+2∗pow[dep+1]]) 来更新 f[dep][s] 即可。
这道题与其他的树DP不同的地方在于要考虑祖先的影响……
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 20005
#define MAXM 25005
using namespace std;
const int inf=1e8;
int n, m, val[MAXN], ans;
int power[15], f[15][59060], d[MAXN], tmp[MAXN];
bool vis[MAXN];
struct node
{
int v, next;
}edge[MAXM<<1];
int adj[MAXN], pos;
void add(int a,int b)
{
edge[pos].v=b, edge[pos].next=adj[a];
adj[a]=pos;++pos;
}
void dfs(int u)
{
vis[u]=1;
int v, dep=d[u];
if(!dep)f[0][0]=val[u], f[0][1]=0, f[0][2]=inf;
else
{
int cnt=0;
for(int p=adj[u];~p;p=edge[p].next)
if(vis[(v=edge[p].v)]&&d[v]<dep)
tmp[++cnt]=d[v];
for(int s=0;s<power[dep+1];++s)f[dep][s]=inf;
for(int s=0, t, l;s<power[dep];++s)
{
t=1, l=s;
for(int i=1;i<=cnt;++i)
if(s/power[tmp[i]]%3==0)t=2;
else if(s/power[tmp[i]]%3==1)l+=power[tmp[i]];
f[dep][s+t*power[dep]]=min(f[dep][s+t*power[dep]],f[dep-1][s]);
f[dep][l]=min(f[dep][l],f[dep-1][s]+val[u]);
}
}
for(int p=adj[u];~p;p=edge[p].next)
if(!vis[(v=edge[p].v)])
{
d[v]=dep+1;
dfs(v);
for(int s=0;s<power[dep+1];++s)
f[dep][s]=min(f[dep+1][s],f[dep+1][s+2*power[dep+1]]);
}
}
int main()
{
power[0]=1;
for(int i=1;i<15;++i)power[i]=power[i-1]*3;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&val[i]);
adj[i]=-1;
}
int u, v;
while(m--)
{
scanf("%d%d",&u,&v);
add(u,v), add(v,u);
}
for(int i=1;i<=n;++i)
if(!vis[i])
{
dfs(i);
ans+=min(f[0][0],f[0][2]);
}
printf("%d\n",ans);
return 0;
}