結果
| 問題 |
No.1804 Intersection of LIS
|
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2024-05-19 16:41:23 |
| 言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
WA
|
| 実行時間 | - |
| コード長 | 20,992 bytes |
| コンパイル時間 | 5,775 ms |
| コンパイル使用メモリ | 295,412 KB |
| 最終ジャッジ日時 | 2025-02-21 15:59:19 |
|
ジャッジサーバーID (参考情報) |
judge5 / judge1 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 2 WA * 1 |
| other | MLE * 1 -- * 36 |
ソースコード
#ifndef HIDDEN_IN_VS // 折りたたみ用
// 警告の抑制
#define _CRT_SECURE_NO_WARNINGS
// ライブラリの読み込み
#include <bits/stdc++.h>
using namespace std;
// 型名の短縮
using ll = long long; using ull = unsigned long long; // -2^63 ~ 2^63 = 9 * 10^18(int は -2^31 ~ 2^31 = 2 * 10^9)
using pii = pair<int, int>; using pll = pair<ll, ll>; using pil = pair<int, ll>; using pli = pair<ll, int>;
using vi = vector<int>; using vvi = vector<vi>; using vvvi = vector<vvi>; using vvvvi = vector<vvvi>;
using vl = vector<ll>; using vvl = vector<vl>; using vvvl = vector<vvl>; using vvvvl = vector<vvvl>;
using vb = vector<bool>; using vvb = vector<vb>; using vvvb = vector<vvb>;
using vc = vector<char>; using vvc = vector<vc>; using vvvc = vector<vvc>;
using vd = vector<double>; using vvd = vector<vd>; using vvvd = vector<vvd>;
template <class T> using priority_queue_rev = priority_queue<T, vector<T>, greater<T>>;
using Graph = vvi;
// 定数の定義
const double PI = acos(-1);
int DX[4] = {1, 0, -1, 0}; // 4 近傍(下,右,上,左)
int DY[4] = {0, 1, 0, -1};
int INF = 1001001001; ll INFL = 4004004003094073385LL; // (int)INFL = INF, (int)(-INFL) = -INF;
// 入出力高速化
struct fast_io { fast_io() { cin.tie(nullptr); ios::sync_with_stdio(false); cout << fixed << setprecision(18); } } fastIOtmp;
// 汎用マクロの定義
#define all(a) (a).begin(), (a).end()
#define sz(x) ((int)(x).size())
#define lbpos(a, x) (int)distance((a).begin(), std::lower_bound(all(a), x))
#define ubpos(a, x) (int)distance((a).begin(), std::upper_bound(all(a), x))
#define Yes(b) {cout << ((b) ? "Yes\n" : "No\n");}
#define rep(i, n) for(int i = 0, i##_len = int(n); i < i##_len; ++i) // 0 から n-1 まで昇順
#define repi(i, s, t) for(int i = int(s), i##_end = int(t); i <= i##_end; ++i) // s から t まで昇順
#define repir(i, s, t) for(int i = int(s), i##_end = int(t); i >= i##_end; --i) // s から t まで降順
#define repe(v, a) for(const auto& v : (a)) // a の全要素(変更不可能)
#define repea(v, a) for(auto& v : (a)) // a の全要素(変更可能)
#define repb(set, d) for(int set = 0, set##_ub = 1 << int(d); set < set##_ub; ++set) // d ビット全探索(昇順)
#define repis(i, set) for(int i = lsb(set), bset##i = set; i >= 0; bset##i -= 1 << i, i = lsb(bset##i)) // set の全要素(昇順)
#define repp(a) sort(all(a)); for(bool a##_perm = true; a##_perm; a##_perm = next_permutation(all(a))) // a の順列全て(昇順)
#define uniq(a) {sort(all(a)); (a).erase(unique(all(a)), (a).end());} // 重複除去
#define EXIT(a) {cout << (a) << endl; exit(0);} // 強制終了
#define inQ(x, y, u, l, d, r) ((u) <= (x) && (l) <= (y) && (x) < (d) && (y) < (r)) // 半開矩形内判定
// 汎用関数の定義
template <class T> inline ll powi(T n, int k) { ll v = 1; rep(i, k) v *= n; return v; }
template <class T> inline bool chmax(T& M, const T& x) { if (M < x) { M = x; return true; } return false; } // 最大値を更新(更新されたら true を返す)
template <class T> inline bool chmin(T& m, const T& x) { if (m > x) { m = x; return true; } return false; } // 最小値を更新(更新されたら true を返す)
template <class T> inline T getb(T set, int i) { return (set >> i) & T(1); }
template <class T> inline T smod(T n, T m) { n %= m; if (n < 0) n += m; return n; } // 非負mod
// 演算子オーバーロード
template <class T, class U> inline istream& operator>>(istream& is, pair<T, U>& p) { is >> p.first >> p.second; return is; }
template <class T> inline istream& operator>>(istream& is, vector<T>& v) { repea(x, v) is >> x; return is; }
template <class T> inline vector<T>& operator--(vector<T>& v) { repea(x, v) --x; return v; }
template <class T> inline vector<T>& operator++(vector<T>& v) { repea(x, v) ++x; return v; }
#endif // 折りたたみ用
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#ifdef _MSC_VER
#include "localACL.hpp"
#endif
//using mint = modint1000000007;
using mint = modint998244353;
//using mint = modint; // mint::set_mod(m);
namespace atcoder {
inline istream& operator>>(istream& is, mint& x) { ll x_; is >> x_; x = x_; return is; }
inline ostream& operator<<(ostream& os, const mint& x) { os << x.val(); return os; }
}
using vm = vector<mint>; using vvm = vector<vm>; using vvvm = vector<vvm>; using vvvvm = vector<vvvm>; using pim = pair<int, mint>;
#endif
#ifdef _MSC_VER // 手元環境(Visual Studio)
#include "local.hpp"
#else // 提出用(gcc)
inline int popcount(int n) { return __builtin_popcount(n); }
inline int popcount(ll n) { return __builtin_popcountll(n); }
inline int lsb(int n) { return n != 0 ? __builtin_ctz(n) : -1; }
inline int lsb(ll n) { return n != 0 ? __builtin_ctzll(n) : -1; }
template <size_t N> inline int lsb(const bitset<N>& b) { return b._Find_first(); }
inline int msb(int n) { return n != 0 ? (31 - __builtin_clz(n)) : -1; }
inline int msb(ll n) { return n != 0 ? (63 - __builtin_clzll(n)) : -1; }
#define dump(...)
#define dumpel(v)
#define dump_list(v)
#define dump_mat(v)
#define input_from_file(f)
#define output_to_file(f)
#define Assert(b) { if (!(b)) { vc MLE(1<<30); EXIT(MLE.back()); } } // RE の代わりに MLE を出す
#endif
//【最長増加部分列(復元)】O(n log n)
/*
* 数列 a[0..n) の(狭義)最長増加部分列の長さを返す.またその一例の添字列を lis に構成する.
*
*(セグメント木で高速化したインライン DP)
*/
pii op_lis(pii a, pii b) { return max(a, b); }
pii e_lis() { return { 0, -1 }; } // max の単位元が -INF でなく 0 であることに注意
template <class T>
dsu LIS_val_to_length(const vector<T>& a, vi* lis = nullptr) {
// verify : https://judge.yosupo.jp/problem/longest_increasing_subsequence
int n = sz(a);
// a を座標圧縮した結果を b に格納する.
vector<T> a_uniqed(a); uniq(a_uniqed); int m = sz(a_uniqed);
vi b(n); rep(i, n) b[i] = lbpos(a_uniqed, a[i]);
// dp_i[j] : b[0..i] で右端の値が j であるような最長増加部分列の長さとそのときの右端位置
segtree<pii, op_lis, e_lis> dp(m);
// prv[j] : 右端が b[i] の最長増加部分列について,右端の 1 つ前の要素の位置(DP 復元用)
//(インライン DP を行うので,これを持たずに DP テーブルから復元しようとすると失敗する.)
vi prv(n, -1);
//(例)b[0..5) = [3, 1, 2, 2, 0] のとき
// dp_0[0..3) = [0, 0, 0, 0]
// dp_1[0..3) = [0, 0, 0, 1] (max(0, 0, 0) + 1 = 1)
// dp_2[0..3) = [0, 1, 0, 1] (max(0) + 1 = 1)
// dp_3[0..3) = [0, 1, 2, 1] (max(0, 1) + 1 = 2)
// dp_4[0..3) = [0, 1, 2, 1] (max(0, 1) + 1 = 2)
// dp_5[0..3) = [1, 1, 2, 1] (max() + 1 = 1)
dsu d(n);
// j = b[i] を順に見ていく
rep(i, n) {
dump("--- i:", i, "---");
int j = b[i];
// j を右端にもてるのは,それまでの右端が j 未満のもののみ.
// よってその中での最長増加部分列の長さを求め,それに 1 を加える.
auto [len, pos] = dp.prod(0, j);
dump(" len, pos:", len, pos);
len++;
// j を右端とするより長いものが作れれば更新する.
// dp[j] 以外は更新されることはないので,更新は O(log n) で終わる.
// この性質が dp テーブルのインライン化と相性が良い.
auto [plen, ppos] = dp.get(j);
dump("plen, ppos:", plen, ppos);
if (plen == len) {
if (ppos != -1) d.merge(i, ppos);
dp.set(j, { len, i });
prv[i] = pos;
}
if (len > plen) {
dp.set(j, { len, i });
prv[i] = pos;
}
dump(dp);
}
dump(prv);
// 右端の値を任意としたときの最長増加部分列の長さを得る.
auto [len, pos] = dp.prod(0, m);
// DP 復元を行う.
if (lis != nullptr) {
lis->clear();
while (pos != -1) {
lis->emplace_back(pos);
pos = prv[pos];
}
reverse(all(*lis));
}
return d;
}
/*
1
------------------------------
8
1 1 1 1 2 2 2 2
-1 -1 -1 -1 0 -1 -1 -1
*/
void WA() {
int n;
cin >> n;
vi a(n);
cin >> a;
vi lis;
auto d = LIS_val_to_length(a, &lis);
dump(lis); dump(d);
vi ok(n);
repe(i, lis) ok[d.leader(i)] = 1;
vi res;
rep(i, n) {
if (ok[d.leader(i)]) {
res.push_back(i + 1);
}
}
int K = sz(res);
rep(k, K) cout << res[k] << " \n"[k == K - 1];
}
//【重み付きグラフの辺】
/*
* to : 行き先の頂点番号
* cost : 辺の重み
*/
struct WEdge {
// verify : https://judge.yosupo.jp/problem/shortest_path
int to; // 行き先の頂点番号
ll cost; // 辺の重み
WEdge() : to(-1), cost(-INFL) {}
WEdge(int to, ll cost) : to(to), cost(cost) {}
// プレーングラフで呼ばれたとき用
operator int() const { return to; }
#ifdef _MSC_VER
friend ostream& operator<<(ostream& os, const WEdge& e) {
os << '(' << e.to << ',' << e.cost << ')';
return os;
}
#endif
};
//【重み付きグラフ】
/*
* WGraph g
* g[v] : 頂点 v から出る辺を並べたリスト
*
* verify : https://judge.yosupo.jp/problem/shortest_path
*/
using WGraph = vector<vector<WEdge>>;
//【格子 DAG の座標圧縮(真に増加)】O(n log n)
/*
* DAG G = (V, E) を,V = [0..n),E は以下の規則で定まるものとする:
* 辺 i→j をもつ ⇔ x[i] < x[j] かつ y[i] < y[j]
* 各頂点間の移動可能性が G と等しい DAG を返す(頂点 [0..n) は G と対応する.)
*/
template <class T>
Graph lattice_DAG_compression(const vector<T>& x, const vector<T>& y) {
// verify : https://atcoder.jp/contests/arc165/tasks/arc165_f
int n = sz(x);
map<T, vi> x_to_is;
rep(i, n) x_to_is[x[i]].emplace_back(i);
vvi k_to_is;
repea(tmp, x_to_is) k_to_is.emplace_back(move(tmp.second));
int K = sz(k_to_is);
Graph g(n); int id = n;
function<void(int, int)> rf = [&](int l, int r) {
if (r - l == 1) return;
int m = (l + r) / 2;
vector<tuple<T, int, int>> yik;
repi(k, l, m - 1) repe(i, k_to_is[k]) yik.emplace_back(2 * y[i] + 1, i, k);
repi(k, m, r - 1) repe(i, k_to_is[k]) yik.emplace_back(2 * y[i], i, k);
sort(all(yik));
int pid = -1;
for (auto& [y, i, k] : yik) {
g.push_back(vi());
if (pid != -1) g[pid].push_back(id);
pid = id;
if (k < m) g[i].push_back(id);
else g[id].push_back(i);
id++;
}
rf(l, m);
rf(m, r);
};
rf(0, K);
return g;
}
//【スコア最大パス(頂点スコア)】O(n + m)
/*
* 頂点 i にスコア w[i] の与えられた DAG g のパス(長さ 0 も可)で,
* 各頂点からのパスの最大スコアを格納したリストを返す.
*/
vl highest_score_path(const Graph& g, const vl& w) {
int n = sz(g);
// sc[s] : 頂点 s からのパスの最大スコア
vl sc(n, 0); vb seen(n);
// 貰う DP
function<ll(int)> dfs = [&](int s) {
if (seen[s]) return sc[s];
seen[s] = true;
// s → t と進む場合
repe(t, g[s]) chmax(sc[s], dfs(t));
sc[s] += w[s];
return sc[s];
};
// 各頂点 s についての情報を計算する.
rep(s, n) dfs(s);
return sc;
}
//【逆グラフ】O(n + m)
/*
* 有向グラフ g の辺の向きを逆にしたグラフを返す.
*/
Graph reverse_graph(const Graph& g) {
// verify : https://atcoder.jp/contests/nikkei2019-qual/tasks/nikkei2019_qual_d
int n = sz(g);
Graph g_rev(n);
rep(s, n) repe(t, g[s]) g_rev[t].push_back(s);
return g_rev;
}
//【参照付きグラフの辺】
/*
* int from : 始点
* int to : 終点
* int id : 辺番号
* bool dir : 順方向か
*/
struct IEdge {
// verify : https://judge.yosupo.jp/problem/cycle_detection_undirected
int from; // 始点
int to; // 終点
int id; // 辺番号
bool dir; // 順方向か
IEdge() : from(-1), to(-1), id(-1), dir(true) {}
IEdge(int from, int to, int id, bool dir = true) : from(from), to(to), id(id), dir(dir) {}
// プレーングラフで呼ばれたとき用
operator int() const { return to; }
#ifdef _MSC_VER
friend ostream& operator<<(ostream& os, const IEdge& e) {
os << '(' << e.from << "→" << e.to << ',' << "id:" << e.id << ',' << (e.dir ? "fwd" : "rev") << ')';
return os;
}
#endif
};
//【参照付きグラフ】
/*
* IGraph g
* g[v] : 頂点 v から出る辺を並べたリスト
*
* verify : https://judge.yosupo.jp/problem/cycle_detection_undirected
*/
using IGraph = vector<vector<IEdge>>;
//【lowlink】
/*
* Lowlink(IGraph g) : O(n + m)
* 参照付き無向グラフ g(多重辺可,自己ループ可)で初期化する.
*
* bool articulation_pointQ(int s) : O(1)
* 頂点 s が関節点かを返す.
* 関節点:その頂点を取り除くとグラフの連結成分が増える頂点
*
* vi get_articulation_points() : O(n)
* g の関節点の昇順リストを返す.
*
* bool bridgeQ(int j) : O(1)
* 辺 j が橋かを返す.
* 橋:その辺を取り除くとグラフの連結成分が増える辺
*
* vi get_bridges() : O(m)
* g の橋の番号の昇順リストを返す.
*
* vvi get_two_edge_connected_components() : O(n + m)
* g を二重辺連結成分分解し,二重辺連結成分の頂点集合のリストを返す.
* 二重辺連結成分:任意の 1 辺を取り除いても連結な部分グラフ
*
* vvi get_two_vertex_connected_components() : O(n + m)
* g を二重頂点連結成分分解し,二重頂点連結成分の辺集合の番号のリストを返す.
* 二重頂点連結成分:任意の 1 頂点を取り除いても連結な極大部分グラフ
* 制約:g は自己ループをもたない.
* 注意:孤立点のみからなる二重頂点連結成分は辺をもたないので検出されない.
*
* bool connectedQ(int s, int t) : O(1)
* 頂点 s, t が連結かを返す.
*
* bool separated_by_bridgeQ(int s, int j, int t) : O(1)
* 頂点 s, t 間に橋 j が存在するかを返す.
*/
class Lowlink {
// 参考 : https://kntychance.hatenablog.jp/entry/2022/09/16/161858
int n, m;
IGraph g;
// in[s] : DFS で頂点 s を初めて探索した時刻(連結成分間の移動には INF 時間かかる)
vl in;
// low[s] : s から DFS 木を逆走せず後退辺を高々 1 回用いて到達できる頂点 t についての min in[t]
// 後退辺とは,DFS でなぞられなかった g の辺のことをいう.
vl low;
// in_e[j] : DFS で辺 j を行きがけに探索した時刻
// out_e[j] : DFS で辺 j を帰りがけに探索した時刻
vl in_e, out_e;
// is_ap[s] : 頂点 s が関節点か
vb is_ap;
// is_bg[j] : 辺 j が橋か
vb is_bg;
public:
// 参照付き無向グラフ g(多重辺可,自己ループ可)で初期化する.
Lowlink(const IGraph& g) : n(sz(g)), m(-1), g(g), in(n, -1), low(n), is_ap(n) {
// verify : https://atcoder.jp/contests/abc301/tasks/abc301_h
rep(s, n) repe(t, g[s]) chmax(m, t.id);
m++;
in_e.resize(m), out_e.resize(m);
is_bg.assign(m, false);
int rt; // 走査中の連結成分の根
ll time = 0; // 現在時刻
// DFS 木をなぞる.
function<void(int, int)> dfs = [&](int s, int id) {
// s を最初に訪れた
in[s] = low[s] = time++;
int child_cnt = 0; // DFS 木における子の個数
repe(t, g[s]) {
// 戻る辺と自己ループは通らない.
//(自己ループは連結性に影響を与えないので無視できる)
if (t.id == id || t == s) continue;
// t を既に訪れていた場合
if (in[t] != -1) {
// s→t が後退辺のとき:
// t から DFS 木の辺を辿って他の頂点 v に行けたとしても
// 必ず in[t] < in[v] となっているので low[s] = in[t] で確定.
// t→s が後退辺のとき:
// s→t は DFS 木に対するショートカットとなるので
// 必ず low[s] ≦ in[s] < in[t] となるから更新不要.
// chmin を使えば両方同時に対応可能である.
chmin(low[s], in[t]);
}
// t をまだ訪れていない場合
else {
// 再帰的になぞりにいく.
in_e[t.id] = time++;
dfs(t, t.id);
out_e[t.id] = time++;
// s→t は DFS 木の辺なので low[t] で low[s] を更新する.
chmin(low[s], low[t]);
// s→t を渡ってしまうと DFS 木の s の先祖に帰れないなら s-t は橋である.
if (in[s] < low[t]) is_bg[t.id] = true;
// t から DFS 木の s の真の先祖に帰れないなら s は t にとって関節点である.
//(ただし s が根の場合は後で例外処理する.)
is_ap[s] = is_ap[s] || (in[s] <= low[t]);
child_cnt++;
}
}
// s が根の場合,子が 2 つ以上ないと関節点にはなり得ない.
if (s == rt) is_ap[s] = (child_cnt >= 2);
};
// 各連結成分ごとに適当な点を始点(根)として DFS を行い,in と low を求める.
rep(s, n) {
if (in[s] != -1) continue;
// 連結成分を跨ぐには INF 時間かかる
time = (time / INF + 1) * INF;
// s を根として DFS し,同連結成分内の in と low を定める.
rt = s;
dfs(s, -1);
}
}
// 頂点 s が関節点かを返す.
bool articulation_pointQ(int s) {
return is_ap[s];
}
// g の関節点の昇順リストを返す.
vi get_articulation_points() {
// verify : https://onlinejudge.u-aizu.ac.jp/courses/library/5/GRL/all/GRL_3_A
vi aps;
rep(s, n) if (is_ap[s]) aps.push_back(s);
return aps;
}
// 辺 j が橋かを返す.
bool bridgeQ(int j) {
return is_bg[j];
}
// g の橋の番号の昇順リストを返す.
vi get_bridges() {
// verify : https://onlinejudge.u-aizu.ac.jp/courses/library/5/GRL/all/GRL_3_B
vi bgs;
rep(j, m) if (is_bg[j]) bgs.push_back(j);
return bgs;
}
// g を二重辺連結成分分解し,二重辺連結成分の頂点集合のリストを返す.
vvi get_two_edge_connected_components() {
// verify : https://judge.yosupo.jp/problem/two_edge_connected_components
// ccs : 連結成分の頂点のリスト
vvi ccs; vb seen(n);
function<void(int, int)> dfs = [&](int s, int p) {
ccs.back().push_back(s);
seen[s] = true;
repe(t, g[s]) {
// 親には戻らない.探索済の頂点には進まない.橋は渡らない.
if (t == p || seen[t] || is_bg[t.id]) continue;
dfs(t, s);
}
};
rep(s, n) {
if (seen[s]) continue;
ccs.push_back(vi());
dfs(s, -1);
}
return ccs;
}
// g を二重頂点連結成分分解し,二重頂点連結成分の辺集合の番号のリストを返す.
vvi get_two_vertex_connected_components() {
// verify : https://judge.yosupo.jp/problem/biconnected_components
// ccs : 連結成分の辺のリスト
vvi ccs; vb seen(m);
// s : 注目頂点
// ap : 探索を開始した関節点または根
// k : 何番目の二重頂点連結成分を検出中か
function<void(int, int, int)> dfs = [&](int s, int ap, int k) {
repe(t, g[s]) {
// 探索済の辺には進まない.
if (seen[t.id]) continue;
seen[t.id] = true;
// s が t にとっての関節点であるような辺 s-t の先は別の二重頂点連結成分である.
if (in[s] <= low[t]) {
ccs.push_back(vi{ t.id });
dfs(t, s, sz(ccs) - 1);
continue;
}
// 辺を記録する.
ccs[k].push_back(t.id);
// 後退辺でなければ先を探索する.
if (in[s] < in[t]) dfs(t, ap, k);
}
};
rep(s, n) repe(t, g[s]) {
if (seen[t.id]) continue;
seen[t.id] = true;
ccs.push_back(vi{ t.id });
dfs(t, s, sz(ccs) - 1);
}
return ccs;
}
bool connectedQ(int s, int t) {
return in[s] / INF == in[t] / INF;
}
bool separated_by_bridgeQ(int s, int j, int t) {
// verify : https://atcoder.jp/contests/abc301/tasks/abc301_h
if (!connectedQ(s, t)) return false;
if (!is_bg[j]) return false;
bool bs = in_e[j] < in[s] && in[s] < out_e[j];
bool bt = in_e[j] < in[t] && in[t] < out_e[j];
return bs != bt;
}
};
int main() {
// input_from_file("input.txt");
// output_to_file("output.txt");
int n;
cin >> n;
vi p(n);
cin >> p;
vi x(n);
iota(all(x), 0);
auto g = lattice_DAG_compression(x, p);
int N = sz(g);
vl w(N);
rep(i, n) w[i] = 1;
auto d = highest_score_path(g, w);
dump(d);
auto gR = reverse_graph(g);
auto dR = highest_score_path(gR, w);
dump(dR);
ll len_max = 0;
rep(i, n) chmax(len_max, d[i]);
dump(len_max);
Graph g2(g);
g2.push_back(vi());
g2.push_back(vi());
rep(i, n) {
if (d[i] == len_max) g2[N].push_back(i);
if (dR[i] == len_max) g2[i].push_back(N + 1);
}
IGraph g3(N + 2); int id = 0;
rep(i, N + 2) repe(j, g2[i]) {
g3[i].push_back({ i, j, id, 1 });
g3[j].push_back({ j, i, id, 0 });
id++;
}
Lowlink G3(g3);
auto arts = G3.get_articulation_points();
while (!arts.empty()) {
if (arts.back() >= n) arts.pop_back();
else break;
}
int K = sz(arts);
cout << K << endl;
rep(k, K) cout << p[arts[k]] << " \n"[k == K - 1];
}