Luogu4381 BZOJ1791 [IOI2008]島
このブログのプログラムロスdarkBZOJバレーと通じますが、DFSを使用すると、BZOJスタックの爆発につながることができます。(しかしBZOJは私が測定された方法がありません爆発しました)
フェイス質問
あなたは、公園、パーク訪問する準備が整いました\(N \)各島から島、地方自治体を\(私は\)他の島への長さを構築するために始め\(L_iを\)橋のが、橋これは、双方向の徒歩です。一方、各ペアの間にアイランド間のフェリーは、専用のコンタクト二つの島を有します。あなたが歩くことを好むかもしれボートへの相対。あなたは、できるだけ長く橋の全長を通過したいのですが、以下の制限を受け:
- あなたはツアーを開始するために、自分の島を選択することができます。
- いずれの島は複数回訪問することはできません。
- かかわらず、任意の時間に、あなたは島が現在存在することができますから(S \)\別には、島に行ったことはありませんでした\(D \) 。\(S \)する\(D \)以下の方法があります。
- ウォーキング:二つの島の間のブリッジがある場合にのみが可能です。この場合、橋の長さは、あなたが歩く距離の合計を蓄積します。
- フェリー:あなたはそこには、ブリッジの任意の組み合わせであると以前にフェリーで使用される場合にのみ可能、この方法を選択することができます(S \)を\しないで行きました\(D \)に到達可能かどうかをチェックし、あなたはすべての経路を検討すべきである場合には( )あなたが島を訪問した後のものを含みます。
すべてのブリッジを完了することができないかもしれない、あなたはすべての島を訪問する必要がないことに注意してください。
与えられたプログラムを書いてください\(N \)ブリッジ、ならびにそれらの長さ、上記ルールに従って、あなたが最大値とブリッジの走行長さを計算することができます。
ソリューション
問題の表面の分析によって、我々は、図は、リングの森の木を示していることが分かりました。(\ (N- \)ポイント\(\ N-)エッジは、図ユニコムを保証するものではありません)
私たちはそれらのそれぞれは、ツリーシンプルなリング・パスの最大長である答えます。このパスは、ツリーのベースリングの直径です。
この問題は、リング径の木に変換するために必要となります。分析することで、私たちは木のリングの直径は2例があることがわかりました。
- グループは、ツリー、木のリングの上に位置しています。
- 環状鎖グループツリーのパスに加えて、それらの間に2つのツリーリング。
最初のケースを探している、\(DP \)に求めています。
後者の場合、我々はリングを破らチェーンで動作しなければなりません。
詳細:
1.指輪を探す
この質問はリングは覚えていないことを見つけることです\(父\) :リングはこのようになりますので、
あなたは覚えている場合は、\(父\)リング未満を走りました。
私は、直接標識の方法は裏面を防ぐためにサイドを走っ使用しました。
ID特定のトピックの前縁は逆側が奇数である、でも建設側にあるので、あなたは、裏面を実行しないでください。
for (register int i = head[x]; i; i = e[i].nxt)
{
if (i & 1)
continue;
}
私たちは、トピックがもともとこれはマップ全体を横断していませんが、我々はリングトラバースああを実行する必要があり、そううまく互いに素設定、メンテナンスしていない、双方向の道路を設立したことがわかりました。
class FIND
{
public:
int fa[MAXN];
inline int find(int x)
{
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}
inline void merge(int x, int y)
{
x = find(x);
y = find(y);
if (x != y)
fa[y] = x;
}
inline void init()
{
for (register int i = 1; i <= n; ++i)
fa[i] = i;
}
} fd;
if (fd.find(i) == i)
{
now = i;
rd[0].push_back(now);
top = 0;
v[0] = 0;
dfs_round(i);
}
2.ツリーの直径
ツリーの深さを維持しながら、ツリーの方法を用いて、直径DP。
void dp(int x)
{
v[x] = 1;
for (register int i = head[x]; i; i = e[i].nxt)
{
register int y = e[i].ver;
if (v[y] || rond[y])
continue;
dp(y);
d[now] = max(d[now], f[x] + f[y] + e[i].edge);
f[x] = max(f[x], f[y] + e[i].edge);
l[now] = max(l[now], f[x]);
}
}
3.ツリーのベースリングの直径
最初は現在計算プラス鎖パスツリーの場合に決定されています。
DPリングリングに分割し、二本鎖に複製の問題を解決します。
使用\(L [i]は\)で表される\(Iは\)ツリーのルートの最長の鎖の長さ、(F [I] \)\表す\を(私は\)環上のパスポイントを切断します長さ。
列挙右点\(R&LTの\) 、左端点である場合(L \)\ときに回答を(ANS = \ MAX(ANS \ 、L [R] + L [L] + F [R] - [L] Fを)\)
言い換えれば、我々は、法的な範囲内に維持したい[I] \)F - \(L [i]が最小のポイントを。
右のポイントを列挙し、単調単調増加の使用は、左のポイントを維持するためにキュー。
参照ループ交通
- ヘッド状態は、チーム正当ではありません
- 現在のチームは最高の左エンドポイントヘッド素子である、答えを更新します
- 要素が尾を再生し、テール電流の要素よりも大きい場合
- チームへの現在の要素
for (register int i = 1; i <= len; ++i)
g[i] = g[i + len] = l[s[i]];
ans = 0;
for (register int i = 1; i < len * 2; ++i)
{
while (q.size() && i - q.front() >= len)
q.pop_front();
if (q.size())
{
ans = max(ans, f[i] - f[q.front()] + g[i] + g[q.front()]);
}
while (q.size() && g[i] - f[i] > g[q.back()] - f[q.back()])
q.pop_back();
q.push_back(i);
}
大雑把にこのように、コード内の特定のコメントがあります。
各ブロックDFSつのみ通信マップ全体を横断せず、再びVIS DFS配列を空にする必要がないので、これは、TLEにつながるが、図VISアレイが完全に横断した後、これは明らかである、ことを更に特に注目す。
コードを入れてください
#include <bits/stdc++.h>
using namespace std;
//快读
namespace fdata
{
inline char nextchar()
{
static const int BS = 1 << 21;
static char buf[BS], *st, *ed;
if (st == ed)
ed = buf + fread(st = buf, 1, BS, stdin);
return st == ed ? -1 : *st++;
}
template <typename Y>
inline void poread(Y &ret)
{
ret = 0;
char ch;
while (!isdigit(ch = nextchar()))
;
do
ret = ret * 10 + ch - '0';
while (isdigit(ch = nextchar()));
}
#undef nextcar
} // namespace fdata
using fdata::poread;
const int MAXN = 1e6 + 10;
int n;
struct node
{
int nxt, ver, edge;
} e[MAXN << 1];
int head[MAXN], tot = 1;
inline void add(const int &x, const int &y, const int &z)
{
e[++tot].ver = y;
e[tot].edge = z;
e[tot].nxt = head[x];
head[x] = tot;
}
//并查集 没错我封装了
class FIND
{
public:
int fa[MAXN];
inline int find(int x)
{
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}
inline void merge(int x, int y)
{
x = find(x);
y = find(y);
if (x != y)
fa[y] = x;
}
inline void init()
{
for (register int i = 1; i <= n; ++i)
fa[i] = i;
}
} fd;
#define int long long
//双端队列 没错我自己封装的
class QUE
{
private:
long long q[MAXN << 1];
int l = 1, r;
public:
inline bool size()
{
return r >= l;
}
inline void init()
{
l = 1, r = 0;
}
inline void push_back(const long long x)
{
q[++r] = x;
}
inline long long back()
{
return q[r];
}
inline void pop_back()
{
--r;
}
inline void pop_front()
{
++l;
}
inline long long front()
{
return q[l];
}
} q;
bool v[MAXN << 1];
int sta[MAXN], top;
int rond[MAXN], now;
//使用vector记录环上的点
vector<int> rd[MAXN];
//找环
inline bool dfs_round(int x)
{
//入栈
sta[++top] = x;
v[x] = 1;
for (register int i = head[x]; i; i = e[i].nxt)
{
//判断是不是反向边
if (i & 1)
continue;
register int y = e[i].ver;
//当前点跑到过 已经跑完了这个环 栈中到当前点点之间的点就是环上的点
if (v[y])
{
register int z = 0;
do
{
z = sta[top--];
assert(z);
rd[now].push_back(z);
rond[z] = now;
} while (z != y);
return 1;
}
if (dfs_round(y))
return 1;
}
sta[top--] = 0;
return 0;
}
long long f[MAXN << 1], s[MAXN << 1], d[MAXN], l[MAXN << 1];
long long g[MAXN << 1];
//求树的直径
void dp(int x)
{
v[x] = 1;
for (register int i = head[x]; i; i = e[i].nxt)
{
register int y = e[i].ver;
if (v[y] || rond[y])
continue;
dp(y);
d[now] = max(d[now], f[x] + f[y] + e[i].edge);
f[x] = max(f[x], f[y] + e[i].edge);
l[now] = max(l[now], f[x]);
}
}
long long ans;
int len;
//跑环
inline void dfs(int x, int nw)
{
for (register int i = head[x]; i; i = e[i].nxt)
{
register int y = e[i].ver;
if (v[i] || !rond[y])
continue;
v[i] = v[i ^ 1] = 1;
//边的长度
f[nw] = e[i].edge;
//环上的点,顺序记录便于DP
s[nw] = y;
dfs(y, nw + 1);
}
}
/*计算答案*/
inline long long sov(const int &now)
{
int len = rd[now].size();
s[1] = rd[now][0];
dfs(rd[now][0], 2);
q.init();
//断环成链并复制
for (register int i = 2; i <= len; ++i)
f[i + len + 1 - 1] = f[i];
//求前缀和,O(1)查询区间和
for (register int i = 1; i <= len * 2; ++i)
f[i] = f[i - 1] + f[i];
//将环上每棵树的深度信息复制
for (register int i = 1; i <= len; ++i)
g[i] = g[i + len] = l[s[i]];
ans = 0;
//DP求第二种情况
for (register int i = 1; i < len * 2; ++i)
{
while (q.size() && i - q.front() >= len)
q.pop_front();
if (q.size())
{
ans = max(ans, f[i] - f[q.front()] + g[i] + g[q.front()]);
}
while (q.size() && g[i] - f[i] > g[q.back()] - f[q.back()])
q.pop_back();
q.push_back(i);
}
//第一种情况,直接取max
for (register vector<int>::iterator it = rd[now].begin(); it != rd[now].end(); ++it)
{
ans = max(ans, d[*it]);
}
return ans;
}
signed main()
{
poread(n);
fd.init();
for (register int i = 1, y, z; i <= n; ++i)
{
//建图 维护连通性
poread(y), poread(z);
add(i, y, z);
add(y, i, z);
fd.merge(i, y);
}
for (register int i = 1; i <= n; ++i)
{
//遍历每一个连通块 求环
if (fd.find(i) == i)
{
now = i;
rd[0].push_back(now);
top = 0;
v[0] = 0;
dfs_round(i);
}
}
//整张图完全遍历 清空vis
memset(v, 0, sizeof(v));
//对每一个环上的点为根的树求直径和深度
for (vector<int>::iterator it = rd[0].begin(); it != rd[0].end(); ++it)
{
for (register vector<int>::iterator jt = rd[*it].begin(); jt != rd[*it].end(); ++jt)
{
now = *jt;
dp(*jt);
}
}
memset(v, 0, sizeof(v));
long long ANs = 0;
//对每一个连通块求解并统计答案
for (vector<int>::iterator it = rd[0].begin(); it != rd[0].end(); ++it)
{
ANs += sov(*it);
}
cout << ANs << endl;
return 0;
}