给定一颗树 有些边已经标好方向 现在要给其余的边标上方向 使得最长的有向链最短
HIT: 题目额外给了一个结论 假设已经确定方向的边所能得到的最长链为k 最后的最长链一定k 或k+1
详解:https://www.cnblogs.com/KonjakJuruo/p/5969831.html
利用题目给的结论 我们实际上只需要解决以下这个几乎等价的问题
判断所给的树是否可以通过改变无向边的方向构造出最长链不超过k的方案
如果是的 那么最长链就是k否则为k+1
对于每颗子树的最长链 它要么经过这颗子树的根节点 要么在这个根节点的某个儿子所对应的子树中
因此只需递归地去检验每颗子树是否合法即可。
#include
#include
#include
#include
using namespace std;
const int N = 200+5;
const int INF = 1000000;
struct Edge {
int u, v, d; // d=1 means u->v, d=2 means v->u, d=0 means u-v
Edge(int u=0, int v=0, int d=0):u(u),v(v),d(d){}
};
vector edges[N];
int n, root, longest, have_father[N], f[N], g[N];
bool input(){
bool have_data = false;
int a, b;
char ch;
n = 0;
memset(have_father,0,sizeof(have_father));
for(int i = 0; i < N; i++) edges[i].clear();
while(scanf("%d",&a) == 1 && a){
//printf("a = %d ", a);
have_data = true;
if(a > n) n = a;
while(scanf("%d%c",&b,&ch) == 2&&b){
//printf("%d%c ",b,ch);
if(b > n) n = b;
have_father[b] = 1;
if(ch == 'd'){
edges[a].push_back(Edge(a, b, 1));
edges[b].push_back(Edge(b, a, 2));
}
else if(ch == 'u'){
edges[a].push_back(Edge(a, b, 2));
edges[b].push_back(Edge(b, a, 1));
}
else{
edges[a].push_back(Edge(a, b, 0));
}
}
}
// 找根节点
if(have_data){
for(int i = 1; i <= n; ++i) if(!have_father[i]&&!edges[i].empty()) {
root = i; break;
}
}
return have_data;
}
// 求出 u 为根的子树的最长有向链的长度
int dfs(int u){
int ans = 0;
for(int i = 0; i < edges[u].size(); ++i){
int v = edges[u][i].v;
if(edges[u][i].d == 1) // u -> v
ans = max(ans, dfs(v)+1);
}
return ans;
}
// 孩子中的无向边
struct UndirectedSon {
int w, f, g;
UndirectedSon(int w=0, int f=0, int g=0):w(w),f(f),g(g){}
};
bool cmp_f(const UndirectedSon& w1, const UndirectedSon& w2) {
return w1.f < w2.f;
}
bool cmp_g(const UndirectedSon& w1, const UndirectedSon& w2) {
return w1.g < w2.g;
}
// 检查 u 为根的子树
bool dp(int u, int fa){
if(edges[u].empty()){
f[u] = g[u] = 0;
return true;
}
vector sons;
int f0 = 0, g0 = 0;
for(int i = 0; i < edges[u].size(); ++i){
int w = edges[u][i].v;
if(w == fa) continue;
//dp(w, u);
if(!dp(w, u)) return false;
int d = edges[u][i].d;
if(d == 0){ // 把孩子中的无向边放在一个数组
sons.push_back(UndirectedSon(w, f[w], g[w]));
}
else if(d == 1) g0 = max(g0, g[w]+1);
else f0 = max(f0, f[w] + 1);
}
// 1. 孩子全是有向边,这个情况最简单
if(sons.empty()){
f[u] = f0; g[u] = g0;
if(f0 + g0 > longest) { f[u] = g[u] = INF; }
return f[u] < INF;
}
f[u] = g[u] = INF;
// 2. 孩子中存在无向边,需要规划它的方向,算出f[u] 和 g[u]
int s = sons.size();
//printf("size = %d\n", s);
sort(sons.begin(), sons.end(), cmp_f);
int maxg[N]; // maxg[i] is max{sons[i].g, sons[i+1].g, ...}
maxg[s-1] = sons[s-1].g;
for(int i = s-2; i >= 0; --i) maxg[i] = max(sons[i].g, maxg[i+1]);
for(int i = 0; i <= s; ++i){
int f1 = f0, g1 = g0;
if(i > 0) f1 = max(f1, sons[i-1].f+1);
if(i < s) g1 = max(g1, maxg[i]+1);
if(f1 + g1 <= longest) { f[u] = min(f[u], f1); break; }
}
sort(sons.begin(), sons.end(), cmp_g);
int maxf[N]; // maxf[i] is max{sons[i].f, sons[i+1].f, ...}
maxf[s-1] = sons[s-1].f;
for(int i = s-2; i >= 0; --i) maxf[i] = max(sons[i].f, maxf[i+1]);
for(int i = 0; i <= s; ++i){
int f1 = f0, g1 = g0;
if(i > 0) g1 = max(g1, sons[i-1].g+1);
if(i < s) f1 = max(f1, maxf[i]+1);
if(f1 + g1 <= longest) { g[u] = min(g[u], g1); break; }
}
return f[u] < INF;
}
int main()
{
freopen("in.txt","r",stdin);
while(input()){
longest = 0;
for(int i = 1; i <= n; ++i) longest = max(longest, dfs(i));
if(dp(root, -1)) printf("%d\n", longest+1);
else printf("%d\n",longest+2);
}
return 0;
}
我们可以建立三个数组 f[x][y] up[x] down[x]
f[x][y]代表:从该根节点x出发的向下(u->…)的最长链最小值为y时,向上(…->x)的最长链最小值为多少
up[x] down[x]分别代表该根节点向上/下的最长链的最小值。对应于上面的f[], g[]
对于当前子树的根节点u,它与孩子节点v的边有3种情况:
#include
#include
#include
#include
using namespace std;
const int N = 200+5;
const int INF = 10000;
struct Edge {
int u, v, d; // d=1 means u->v, d=2 means v->u, d=0 means u-v
Edge(int u=0, int v=0, int d=0):u(u),v(v),d(d){}
};
vector edges[N];
int n, root, longest, have_father[N], up[N], down[N], f[N][N];
// 求出 u 为根的子树的最长有向链的长度
int dfs(int u){
int ans = 0;
for(int i = 0; i < edges[u].size(); ++i){
int v = edges[u][i].v;
if(edges[u][i].d == 1) // u -> v
ans = max(ans, dfs(v)+1);
}
return ans;
}
bool input(){
bool have_data = false;
int a, b;
char ch;
n = 0;
memset(have_father,0,sizeof(have_father));
for(int i = 0; i < N; i++) edges[i].clear();
while(scanf("%d",&a) == 1 && a){
//printf("a = %d ", a);
have_data = true;
if(a > n) n = a;
while(scanf("%d%c",&b,&ch) == 2&&b){
//printf("%d%c ",b,ch);
if(b > n) n = b;
have_father[b] = 1;
if(ch == 'd'){
edges[a].push_back(Edge(a, b, 1));
edges[b].push_back(Edge(b, a, 2));
}
else if(ch == 'u'){
edges[a].push_back(Edge(a, b, 2));
edges[b].push_back(Edge(b, a, 1));
}
else{
edges[a].push_back(Edge(a, b, 0));
}
}
}
// 找根节点
if(have_data){
for(int i = 1; i <= n; ++i) if(!have_father[i]&&!edges[i].empty()) {
root = i; break;
}
}
return have_data;
}
bool dfs(int u, int fa){
for(int i = 0; i < edges[u].size(); ++i){
int v = edges[u][i].v;
if(v == fa) continue;
if(!dfs(v, u)) return false;
int d = edges[u][i].d;
// 有向边 u -> v
if(d == 1){
for(int i = 0; i <= down[v]; ++i) f[u][i] = INF;
}
// 有向边 v -> u
else if(d == 2){
for(int i = 0; i <= longest; ++i) f[u][i] = max(f[u][i], up[v]+1);
}
// 无向边 u -- v
else{
for(int i = 0; i <= down[v]; ++i) f[u][i] = max(f[u][i], up[v]+1);
}
}
bool ok = 0;
for(int i = 0; i <= longest; ++i){
if(f[u][i] + i <= longest){
ok = 1;
up[u] = min(up[u], f[u][i]);
down[u] = min(down[u], i);
}
}
return ok;
}
int main()
{
freopen("in.txt","r",stdin);
while(input()){
memset(up, 0x3f, sizeof(up));
memset(down, 0x3f, sizeof(down));
memset(f, 0, sizeof(f));
longest = 0;
for(int i = 1; i <= n; ++i) longest = max(longest, dfs(i));
if(dfs(root, -1)) printf("%d\n",longest+1);
else printf("%d\n",longest+2);
}
return 0;
}