题目大意:
就是现在给出一个n个点m条边的无向图, 其中有k个点被标记
要求从图中找到尽量多的路径, 这些路径起点和终点互不重合(也就是每个标记点作为起点或者终点只能用一次), 并且没有公共边
输出能找到的最多的路径条数, 以及其中一种方案
大致思路:
首先对原图求出一个生成森林, 于是可以将这个问题转化成在每一个生成树上问连接标记点, 路径没有公共边且每个标记点只能作为起点或者终点用一次, 最多的路径数
容易证明在一棵树上有k的标记点的话最大路径数是k/2, 向下取整, 因为两个路径相交有公共边的话总能找到替换方案, 使得4个点重组变成路径没有公共边
那么对于生成树上找路径的问题, 可以用树形dp解决, 用dp[u]表示当前以u为根的子树下还没有被匹配的点的标号, 进行一遍DP即可找到所有的配对
对于每一个配对求出LCA然后暴力向上爬到LCA把路径输出来即可
代码如下:
Result : Accepted Memory : 15324 KB Time : 93 ms
/*
* Author: Gatevin
* Created Time: 2015/10/23 9:57:54
* File Name: Sakura_Chiyo.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define maxn 50010
#define maxm 50010
int n, m, k;
struct Edge
{
int u, v, nex;
Edge(){}
Edge(int _u, int _v, int _nex)
{
u = _u, v = _v, nex = _nex;
}
};
Edge edge[maxm << 1];
int head[maxn];
int E;
void add_Edge(int u, int v)
{
edge[++E] = Edge(u, v, head[u]);
head[u] = E;
}
int f[maxn];//并查集
int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
void Union(int x, int y)
{
int fx = find(x), fy = find(y);
if(fx != fy)
f[fx] = fy;
return;
}
vector<int> G[maxn];//生成森林
void Kruskal()//寻找生成森林
{
for(int i = 1; i <= n; i++) G[i].clear();
for(int i = 1; i <= n; i++) f[i] = i;//初始化并查集
for(int i = 1; i < E; i++)
{
int u = edge[i].u, v = edge[i].v;
int fu = find(u), fv = find(v);
if(fu != fv)
{
G[u].push_back(v);
G[v].push_back(u);
f[fu] = fv;
}
}
return;
}
bool die[maxn];//是否标记的点
int dp[maxn];//用dp[u]表示以结点u为根的子树中没有被匹配的那个点
vector<pair<int, int> > ans;//匹配对
bool vis[maxn];
void dfs(int now, int father)
{
int nex;
for(int i = G[now].size() - 1; i >= 0; i--)
if((nex = G[now][i]) != father)
{
if(vis[nex]) continue;
vis[nex] = 1;
dfs(nex, now);
}
int match = -1;
for(int i = G[now].size() - 1; i >= 0; i--)
if((nex = G[now][i]) != father)
{
if(dp[nex] != -1)//这个子树有需要匹配的点
{
if(match == -1) match = dp[nex];
else ans.push_back(make_pair(match, dp[nex])), match = -1;
}
}
if(match == -1 && !die[now]) dp[now] = -1;
else if(match == -1 && die[now]) dp[now] = now;
else if(match != -1 && die[now]) dp[now] = -1, ans.push_back(make_pair(match, now));
else dp[now] = match;
return;
}
//======== LCA ========
int st[2*maxn + 1][20];
int fa[maxn];
int dfn[maxn];
int pos[maxn];
int sum;
void add(int x)
{
st[++sum][0] = x;
pos[x] = sum;
return;
}
void dfs2(int now)
{
for(int i = 0, sz = G[now].size(); i < sz; i++)
{
int u = G[now][i];
if(u == fa[now]) continue;
dfn[u] = dfn[now] + 1;
fa[u] = now;
add(now);
dfs2(u);
}
add(now);
return;
}
int Min(int u, int v)
{
return dfn[u] < dfn[v] ? u : v;
}
int lca(int u, int v)
{
u = pos[u], v = pos[v];
if(u > v) swap(u, v);
int t = log(v - u + 1.) / log(2.);
return Min(st[u][t], st[v - (1 << t) + 1][t]);
}
void output()
{
printf("%d\n", (int)ans.size());
vector<int> S;
vector<int> V;
for(int i = ans.size() - 1; i >= 0; i--)
{
int x = ans[i].first, y = ans[i].second;
int A = lca(x, y);
S.clear();
V.clear();
S.push_back(y);
while(y != A)
y = fa[y], S.push_back(y);
while(x != A)
V.push_back(x), x = fa[x];
printf("%d ", (int)(S.size() + V.size()) - 1);
for(int i = 0, sz = V.size(); i < sz; i++)
printf("%d ", V[i]);
for(int i = S.size() - 1; i >= 0; i--)
printf("%d ", S[i]);
putchar('\n');
}
return;
}
void solve()
{
ans.clear();
memset(vis, 0, sizeof(vis));
//init LCA
sum = 0;
dfn[1] = 0;
fa[1] = -1;
for(int i = 1; i <= n; i++)
{
if(!vis[i])
{
vis[i] = 1;
dfs(i, -1);
dfs2(i);//LCA
}
}
for(int j = 1; (1 << j) <= sum; j++)
for(int i = 1; i <= sum - (1 << j) + 1; i++)
st[i][j] = Min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
output();
}
int main()
{
scanf("%d %d %d", &n, &m, &k);
E = 0;
memset(head, -1, sizeof(head));//初始化邻接表
int u, v;
for(int i = 0; i < m; i++)
{
scanf("%d %d", &u, &v);
add_Edge(u, v);
add_Edge(v, u);
}
memset(die, 0, sizeof(die));
int x;
for(int i = 0; i < k; i++)
{
scanf("%d", &x);
die[x] = 1;
}
Kruskal();//求生成森林
solve();//接下来进行树形DP
return 0;
}
/*
6 4 4
1 2
2 3
4 5
5 6
1 3 4 6
*/