\(\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_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;
}

Solution -「SDOI 2016」「洛谷 P4076」墙上的句子的更多相关文章

  1. 「区间DP」「洛谷P1043」数字游戏

    「洛谷P1043」数字游戏 日后再写 代码 /*#!/bin/sh dir=$GEDIT_CURRENT_DOCUMENT_DIR name=$GEDIT_CURRENT_DOCUMENT_NAME ...

  2. Solution -「APIO 2016」「洛谷 P3643」划艇

    \(\mathcal{Description}\)   Link & 双倍经验.   给定 \(n\) 个区间 \([a_i,b_i)\)(注意原题是闭区间,这里只为方便后文描述),求 \(\ ...

  3. Solution -「JSOI 2019」「洛谷 P5334」节日庆典

    \(\mathscr{Description}\)   Link.   给定字符串 \(S\),求 \(S\) 的每个前缀的最小表示法起始下标(若有多个,取最小的).   \(|S|\le3\time ...

  4. Solution -「洛谷 P4372」Out of Sorts P

    \(\mathcal{Description}\)   OurOJ & 洛谷 P4372(几乎一致)   设计一个排序算法,设现在对 \(\{a_n\}\) 中 \([l,r]\) 内的元素排 ...

  5. Solution -「POI 2010」「洛谷 P3511」MOS-Bridges

    \(\mathcal{Description}\)   Link.(洛谷上这翻译真的一言难尽呐.   给定一个 \(n\) 个点 \(m\) 条边的无向图,一条边 \((u,v,a,b)\) 表示从 ...

  6. 「洛谷4197」「BZOJ3545」peak【线段树合并】

    题目链接 [洛谷] [BZOJ]没有权限号嘤嘤嘤.题号:3545 题解 窝不会克鲁斯卡尔重构树怎么办??? 可以离线乱搞. 我们将所有的操作全都存下来. 为了解决小于等于\(x\)的操作,那么我们按照 ...

  7. 「洛谷3338」「ZJOI2014」力【FFT】

    题目链接 [BZOJ] [洛谷] 题解 首先我们需要对这个式子进行化简,否则对着这么大一坨东西只能暴力... \[F_i=\sum_{j<i} \frac{q_iq_j}{(i-j)^2}-\s ...

  8. 「BZOJ2733」「洛谷3224」「HNOI2012」永无乡【线段树合并】

    题目链接 [洛谷] 题解 很明显是要用线段树合并的. 对于当前的每一个连通块都建立一个权值线段树. 权值线段树处理操作中的\(k\)大的问题. 如果需要合并,那么就线段树暴力合并,时间复杂度是\(nl ...

  9. 「洛谷3870」「TJOI2009」开关【线段树】

    题目链接 [洛谷] 题解 来做一下水题来掩饰ZJOI2019考炸的心情QwQ. 很明显可以线段树. 维护两个值,\(Lazy\)懒标记表示当前区间是否需要翻转,\(s\)表示区间还有多少灯是亮着的. ...

随机推荐

  1. Go语言系列之知识框架

    一.Go基础入门知识 二.变量和基本数据类型 三.流程控制语句 四.数组和切片 五.map的声明和使用 六.函数func方法 七.指针和地址 八.结构体 九.接口interface 十.并发神器gor ...

  2. js字符串首字母大写的不同写法

    写法一: let name = 'hello' name.charAt(0).toUpperCase() + name.slice(1) 写法二: let name = 'hello' name.sl ...

  3. 《剑指offer》面试题61. 扑克牌中的顺子

    问题描述 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的.2-10为数字本身,A为1,J为11,Q为12,K为13,而大.小王为 0 ,可以看成任意数字.A 不能视为 14. 示例 ...

  4. leetcode 206. 反转链表 及 92. 反转链表 II

    206. 反转链表 问题描述 反转一个单链表. 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1-> ...

  5. Linuxqq shell脚本安装后的卸载

    官方下载和帮助页面: 传送门 linuxqq_2.0.0-b1 的时候,并没有发布 MIPS64 的 DEB 包,只能用 .sh 安装,需要手动删除卸载.愚人节发布的 beta2 新增了 MIPS64 ...

  6. [Altium Designer 学习]怎样添加3D模型

    对于为给PCB添加3D模型,很多人觉得这是个绣花针的活,中看不中用.在我看来这也未必,特别是常用的3D模型能在网上下载的今天,只需要几个简单的操作,就能使你的PCB更加赏心悦目.除此之外,3D模型还有 ...

  7. C++与lua交互之C++访问lua

    假设lua中: name="gzw" id=17010805 sex=man tab={ num=100, str="hello" } foo_one=func ...

  8. nginx 和uwsgi的区别与作用

    在介绍nginx和uwsgi的区别和作用之前我们先介绍一下几个概念 1.WSGI WSGI的全称是Web Server Gateway Interface(Web服务器网关接口),它不是服务器.pyt ...

  9. Shell 脚本进阶,经典用法及其案例

    一.条件选择.判断 1.条件选择if (1)用法格式 if 判断条件 1 ; then 条件为真的分支代码 elif 判断条件 2 ; then 条件为真的分支代码 elif 判断条件 3 ; the ...

  10. CPU飙升排查

    怎么排查CPU飙升 线上有些系统,本来跑的好好的,突然有一天就会出现报警,CPU使用率飙升,然后重启之后就好了.例如,多线程操作一个线程不安全的list往往就会出现这种现象.那么怎么定位到具体的代码范 ...