Solution -「SDOI 2016」「洛谷 P4076」牆上的句子
阿新 • • 發佈:2021-01-09
\(\mathcal{Description}\)
Link.
(概括得說不清話了還是去看原題吧 qwq。
\(\mathcal{Solution}\)
首先剔除迴文串——它們一定對答案產生 \(1\) 的貢獻。我們稱一個句子是“正序”的,當且僅當句子的所有單詞同時滿足自己的字典序不小於翻轉後的字典序;“逆序”則當且僅當句子的所有單詞同時滿足自己的字典序嚴格大於翻轉後的字典序。從這條顯眼的性質入手:
此外觀察者發現,對每一行(列)來說,按照確定後的閱讀順序讀出的所有單詞同時滿足“自己的字典序不小於翻轉後的字典序”,或同時滿足“自己的字典序不大於翻轉後的字典序”。
也就是說任何一個句子都是“正序”或“逆序”的,而正序句子和正序句子同時選擇不會對答案額外貢獻,逆序句子亦然
在一張無向圖中,欽定一些點為黑色,一些點為白色,為其餘點染色,使得連線黑白兩色的邊數儘量少。
接下來就自然而然了:
- 源點 \(S\),匯點 \(T\),行 \(r_{1..n}\) 列 \(c_{1..m}\) 共 \(n+m\) 個點(為它們染色),每個正序單詞對應 \(w_s,w_r\),表示其正序閱讀和逆序閱讀兩種情況;
- \(S\) 連向被欽定正序(注意需要用句子正序方向和閱讀方向綜合判斷)的行/列結點,流量 \(+\infty\)(不可割);
- 能正序讀出 \(w\) 的所有行/列結點連向 \(w_s\),流量 \(+\infty\)
- \(w_s\) 連向 \(w_r\),流量為 \(1\)(可以被割,對答案貢獻 \(1\));
- \(w_r\) 連向所有能逆序讀出 \(w\) 的行/列結點,流量 \(+\infty\)(同理)。
- \(T\) 同理 \(S\)。
當然,單詞去重。建圖後跑最小割即可。複雜度 \(\mathcal O(\operatorname{Dinic}(n^2,n^3))\)(\(n,m\) 同階)。
\(\mathcal{Code}\)
/* Clearink */ #include <map> #include <set> #include <queue> #include <cstdio> #include <vector> #define rep( i, l, r ) for ( int i = l, repEnd##i = r; i <= repEnd##i; ++i ) #define per( i, r, l ) for ( int i = r, repEnd##i = l; i >= repEnd##i; --i ) typedef unsigned long long ULL; const int MAXN = 80, INF = 0x3f3f3f3f; const ULL BASE = 127; int n, m, node, S, T; int rdir[MAXN + 5], cdir[MAXN + 5]; int rcor[MAXN + 5], ccor[MAXN + 5]; char table[MAXN + 5][MAXN + 5]; std::set<ULL> parl; std::map<ULL, int> virn; inline int imin ( const int a, const int b ) { return a < b ? a : b; } struct MaxFlowGraph { static const int MAXND = 1e6, MAXEG = 1e6; int ecnt, head[MAXND + 5], S, T, bound, curh[MAXND + 5], d[MAXND + 5]; struct Edge { int to, flow, nxt; } graph[MAXEG * 2 + 5]; inline MaxFlowGraph (): ecnt ( 1 ) {} inline void clear () { ecnt = 1; for ( int i = 0; i <= bound; ++i ) head[i] = 0; } inline void restore () { for ( int i = 2; i <= ecnt; i += 2 ) { graph[i].flow += graph[i ^ 1].flow; graph[i ^ 1].flow = 0; } } inline void link ( const int s, const int t, const int f ) { graph[++ecnt].to = t, graph[ecnt].flow = f, graph[ecnt].nxt = head[s]; head[s] = ecnt; } inline Edge& operator [] ( const int k ) { return graph[k]; } inline void operator () ( const int s, const int t, const int f ) { #ifdef RYBY printf ( "%d %d ", s, t ); if ( f == INF ) puts ( "INF" ); else printf ( "%d\n", f ); #endif link ( s, t, f ), link ( t, s, 0 ); } inline bool bfs () { static std::queue<int> que; for ( int i = 0; i <= bound; ++i ) d[i] = -1; d[S] = 0, que.push ( S ); while ( !que.empty () ) { int u = que.front (); que.pop (); for ( int i = head[u], v; i; i = graph[i].nxt ) { if ( graph[i].flow && !~d[v = graph[i].to] ) { d[v] = d[u] + 1; que.push ( v ); } } } return ~d[T]; } inline int dfs ( const int u, const int iflow ) { if ( u == T ) return iflow; int ret = 0; for ( int& i = curh[u], v; i; i = graph[i].nxt ) { if ( graph[i].flow && d[v = graph[i].to] == d[u] + 1 ) { int oflow = dfs ( v, imin ( iflow - ret, graph[i].flow ) ); ret += oflow, graph[i].flow -= oflow, graph[i ^ 1].flow += oflow; if ( ret == iflow ) break; } } if ( !ret ) d[u] = -1; return ret; } inline int calc ( const int s, const int t ) { S = s, T = t; int ret = 0; for ( ; bfs (); ret += dfs ( S, INF ) ) { for ( int i = 0; i <= bound; ++i ) curh[i] = head[i]; } return ret; } } graph; struct Word { std::vector<char> str; inline int order () const { if ( str.empty () ) return 0; for ( int l = 0, r = str.size () - 1; ~r; ++l, --r ) { if ( str[l] < str[r] ) return 1; if ( str[r] < str[l] ) return -1; } return 0; } inline ULL hash () const { if ( str.empty () ) return 0; ULL ret = 0; rep ( i, 0, str.size () - 1 ) ret = ret * BASE + ( str[i] - 'A' + 1 ); return ret; } inline ULL rhash () const { if ( str.empty () ) return 0; ULL ret = 0; per ( i, str.size () - 1, 0 ) ret = ret * BASE + ( str[i] - 'A' + 1 ); return ret; } }; inline int virid ( const Word& tmp, const bool r ) { ULL h = r ? tmp.rhash () : tmp.hash (); if ( !virn.count ( h ) ) { virn[h] = ++node; graph ( node, node + 1, 1 ); return ++node - !r; } return virn[h] + r; } inline void initCorDir () { static Word tmp; rep ( i, 1, n ) table[i][0] = table[i][m + 1] = '_'; rep ( i, 1, m ) table[0][i] = table[n + 1][i] = '_'; rep ( i, 1, n ) { for ( int l = 1, r; l <= m; l = r + 1 ) { tmp.str.clear (); for ( r = l; table[i][r] != '_'; tmp.str.push_back ( table[i][r++] ) ); if ( l == r ) continue; int type = tmp.order (); if ( !type ) parl.insert ( tmp.hash () ); else { rcor[i] = type; int id; if ( rcor[i] == 1 ) { graph ( i, id = virid ( tmp, 0 ), INF ); graph ( id + 1, i, INF ); } else { graph ( id = virid ( tmp, 1 ), i, INF ); graph ( i, id - 1, INF ); } } } } rep ( i, 1, m ) { for ( int l = 1, r; l <= n; l = r + 1 ) { tmp.str.clear (); for ( r = l; table[r][i] != '_'; tmp.str.push_back ( table[r++][i] ) ); if ( l == r ) continue; int type = tmp.order (); if ( !type ) parl.insert ( tmp.hash () ); else { ccor[i] = type; int id; if ( ccor[i] == 1 ) { graph ( i + n, id = virid ( tmp, 0 ), INF ); graph ( id + 1, i + n, INF ); } else { graph ( id = virid ( tmp, 1 ), i + n, INF ); graph ( i, id - 1, INF ); } } } } } inline void clear () { /* * node, rcor, ccor, graph are cleared. * */ virn.clear (), parl.clear (); } int main () { // freopen ( "wall.in", "r", stdin ); // freopen ( "wall.out", "w", stdout ); int cas; for ( scanf ( "%d", &cas ); cas--; ) { clear (); scanf ( "%d %d", &n, &m ); S = n + m + 1, T = node = S + 1; rep ( i, 1, n ) scanf ( "%d", &rdir[i] ), rcor[i] = 0; rep ( i, 1, m ) scanf ( "%d", &cdir[i] ), ccor[i] = 0; rep ( i, 1, n ) scanf ( "%s", table[i] + 1 ); initCorDir (); rep ( i, 1, n ) if ( rdir[i] ) { if ( rcor[i] * rdir[i] >= 0 ) graph ( S, i, INF ); else graph ( i, T, INF ); } rep ( i, 1, m ) if ( cdir[i] ) { if ( ccor[i] * cdir[i] >= 0 ) graph ( S, i + n, INF ); else graph ( i + n, T, INF ); } graph.bound = node; int flw = graph.calc ( S, T ); printf ( "%d\n", flw * 2 + ( int ) parl.size () ); graph.clear (); } return 0; }