当前位置: 首页 > news >正文

Dilworth 定理 学习笔记

文章目录

这个定理还是很重要的,一般用于偏序集上 求最小链划分求最大反链 的相互转换。

前置知识

偏序集

偏序集是由集合 S S S 和定义在 S S S 上的一个偏序关系 R R R 构成,记作 ( S , R ) (S, R) (S,R)

偏序关系

对于一个二元关系 R ⊂ S × S R \subset S \times S RS×S,如果满足:

  • 自反性 ∀ x ∈ S \forall x \in S xS x R x x \ R \ x x R x 成立。
  • 反对称性 ∀ x , y ∈ S \forall x, y \in S x,yS,若 x R y 且 y R x x \ R \ y 且 y \ R \ x x R yy R x,则有 x = y x = y x=y
  • 传递性 ∀ x , y , z ∈ S \forall x, y, z \in S x,y,zS,若 x R y , y R z x \ R \ y,y \ R \ z x R yy R z,则有 x R z x \ R \ z x R z

则称 R R R 是一个偏序关系。
显然 ≤ \leq 就是在 N N N 上的一个偏序关系, ( N , ≤ ) (N, \leq) (N,) 也是最简单的偏序集。

为了方便,后文均用 ≤ \leq 指代 R R R

其余一些定义

链:偏序集的一个 全序子集 称作 。一条链是一个集合,满足集合内的元素 两两可比
反链:一个反链也是一个子集,满足集合内的元素 两两不可比
最长链:偏序集的一个最大链称作 最长链
最长反链:偏序集的一个最大反链称作 最长反链
最小链划分:偏序集最少能被划分成多少条链。
最小反链划分:偏序集最少能被划分成多少条反链。

D i l w o r t h Dilworth Dilworth 定理

  • 对于任意 有限 偏序集,最小链划分 等于 最长反链的大小
  • 对于任意 有限 偏序集, 最小反链划分 等于 最长链的大小

证明对应用用处不大,等到以后有时间了再补吧。

应用

导弹拦截

题意:
给定一个正整数序列 { a i } \{a_i\} {ai},求最少能被划分成多少个 单调不降 的子序列。

分析:
定义二元组 ( i , a i ) (i, a_i) (i,ai) 为偏序集中的元素。定义二元关系 ≤ \leq 为:若 ( i , a i ) , ( j , a j ) (i, a_i), (j, a_j) (i,ai),(j,aj) 满足 i ≤ j 且 a i ≤ a j i \leq j 且 a_i \leq a_j ijaiaj 那么有 ( i , a i ) ≤ ( j , a j ) (i, a_i) \leq (j, a_j) (i,ai)(j,aj)
那么 ( { ( i , a i ) } , ≤ ) (\{(i, a_i)\}, \leq) ({(i,ai)},) 构成一个偏序集。我们要求的就是偏序集的最小链划分。
根据 D i l w o r t h Dilworth Dilworth 定理,最小链划分等于最长反链大小。在这种定义下两个元素 ( i , a i ) , ( j , a j ) (i, a_i), (j, a_j) (i,ai),(j,aj)不可比当且仅当 i ≤ j 且 a i > a j i \leq j 且 a_i > a_j ijai>aj。因此一条反链就是一个单调递减子序列,最长反链大小就是最长单调递减子序列长度。

「TJOI2015」组合数学

题意:
给定一张 n × m n \times m n×m 的网格图 a i , j a_{i, j} ai,j,第 i i i 行第 j j j 列有 a i , j a_{i, j} ai,j 个宝石。
每次你都从 ( 1 , 1 ) (1, 1) (1,1) 出发,只能向右或向下走,走到 ( n , m ) (n, m) (n,m) 停止,你可以将经过的位置上的宝石拿走一个。
问最少走多少次才能将所有宝石都拿走。

1 ≤ n , m ≤ 10 3 1 \leq n, m \leq 10^3 1n,m103

分析:
定义二元组 ( i , j ) (i, j) (i,j) 为偏序集中的元素。定义偏序关系 ≤ \leq 为:若 ( a ≤ c 且 b ≤ d ) 且 ( a < c 和 b < d 至少有一个成立 ) (a \leq c 且 b \leq d) 且 (a < c和b<d 至少有一个成立) (acbd)(a<cb<d至少有一个成立), 则称 ( a , b ) ≤ ( c , d ) (a, b) \leq (c, d) (a,b)(c,d)。也就是 ( a , b ) (a, b) (a,b) 能走至少一步到 ( c , d ) (c, d) (c,d),则有 ( a , b ) ≤ ( c , d ) (a, b) \leq (c, d) (a,b)(c,d)
将每个 ( i , j ) (i, j) (i,j) 复制 a i , j a_{i, j} ai,j 次,那么要求的就是偏序集的最小链划分。
根据 D i l w o r t h Dilworth Dilworth 定理,转化为求最长反链大小:在这里只需要满足任意两个元素相互不可至少走一步到达,那么一行显然最多只能选一个位置,并且贪心的肯定要把位置上的二元组都拿走。
因此可以 d p dp dp d p i , j dp_{i, j} dpi,j 表示从上到下考虑了前 i i i 行,当前最靠左的位置所在列为 j j j 的答案。转移是枚举 k > j k > j k>j,从 d p i − 1 , k dp_{i - 1, k} dpi1,k max ⁡ \max max。显然可以后缀和优化。
复杂度 O ( n m ) O(nm) O(nm)

CODE:

// 根据 Dilworth 定理,可以从右上角到左下角 dp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int T, n, m, a[N][N], dp[N][N]; // dp[i][j] 表示考虑到第 i 行, 当前在第 j 列的最大值
inline void solve() {scanf("%d%d", &n, &m);for(int i = 1; i <= n; i ++ ) for(int j = 1; j <= m; j ++ ) scanf("%d", &a[i][j]);memset(dp, 0, sizeof dp);for(int i = 1; i <= n; i ++ ) {int mx = 0;for(int j = m; j >= 1; j -- ) {dp[i][j] = dp[i - 1][j];mx = max(mx, dp[i - 1][j + 1]);dp[i][j] = max(dp[i][j], mx + a[i][j]);}}int res = 0;for(int i = 1; i <= m; i ++ ) res = max(res, dp[n][i]);cout << res << endl;
}
int main() {scanf("%d", &T);while(T -- ) solve();return 0;
}

[ABC237Ex] Hakata

题意:
给定字符串 S S S,问你最多从 S S S 中选出多少 回文子串,满足选出的任意两个回文子串 s , t s, t s,t,都不满足 s s s t t t 的子串 或 t t t s s s 的子串。

1 ≤ ∣ S ∣ ≤ 200 1 \leq |S| \leq 200 1S200,字符集为小写字母。

分析:
定义偏序关系 ≤ \leq 为:若回文子串 s s s t t t 的子串,则有 s ≤ t s \leq t st
那么答案就是将所有 本质不同 的回文子串取出来后的最长反链大小,根据 D i l w o r t h Dilworth Dilworth 定理转化为求最小链划分。
考虑将所有满足偏序关系的回文子串连一条从较短串到较长串的有向边,那么一定会形成一张 D A G DAG DAG。我们要做的就是将所有点划分成最少的集合,满足任意一个集合内的点都是一条路径的子集。
这等于于求这张 D A G DAG DAG可交最小路径覆盖。只需要先跑一遍传递闭包求出所有可达关系,重建图后求出 拆点后的二分图最大匹配,然后拿总点数减去最大匹配就是答案。
算起来复杂度好像不太对?但是我们有结论:长度为 n n n 的字符串本质不同回文子串数量为 O ( n ) O(n) O(n) 级别。
这样复杂度就对了, f l o y d floyd floyd 复杂度 O ( n 3 ) O(n^3) O(n3) D i n i c Dinic Dinic 求二分图最大匹配复杂度为 O ( m n = n 2.5 ) O(m\sqrt{n} = n^{2.5}) O(mn =n2.5)

CODE:

#include<bits/stdc++.h>
using namespace std;
const int N = 210;
const int M = N * 2;
const int INF = 1e8;
int c, l[M], r[M], L[N * N], R[N * N], odr[N * N], tot, n;
char s[N];
inline bool check(int l, int r) {while(l <= r) {if(s[l] != s[r]) return 0;l ++; r --;}return 1;
}
inline bool cmp(int x, int y) {int len = min(R[x] - L[x] + 1, R[y] - L[y] + 1);for(int i = 1; i <= len; i ++ ) {if(s[L[x] + i - 1] < s[L[y] + i - 1]) return 1;else if(s[L[y] + i - 1] < s[L[x] + i - 1]) return 0;}return R[x] - L[x] + 1 == len;
}
inline bool chk(int x, int y) {if(r[x] - l[x] > r[y] - l[y]) return 0;for(int i = l[y]; i <= r[y] - (r[x] - l[x] + 1) + 1; i ++ ) {bool flag = 1;for(int j = 1; j <= r[x] - l[x] + 1; j ++ ) {if(s[j + l[x] - 1] != s[i + j - 1]) {flag = 0; break;}}if(flag) return 1;}return 0;
}
bool bok[M];
struct edge {int v, last, cap;
} E[M * M + 4 * M];
int head[M], len;
inline void add(int u, int v, int cap) {E[len] = (edge) {v, head[u], cap}; head[u] = len ++;E[len] = (edge) {u, head[v], 0}; head[v] = len ++;
}
int node[M][2], S, T, nc;
int d[M], cur[M];
inline bool bfs(int s, int t) {memset(d, -1, sizeof d); d[s] = 0; cur[s] = head[s];queue< int > q; q.push(s);while(!q.empty()) {int u = q.front(); q.pop();for(int i = head[u]; ~i; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(d[ver] == -1 && cap) {d[ver] = d[u] + 1;if(ver == t) return 1;cur[ver] = head[ver];q.push(ver);}}}return 0;
}
int Find(int u, int t, int limit) {if(u == t) return limit;int res = 0;for(int i = cur[u]; ~i && limit; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(d[ver] == d[u] + 1 && cap) {int v = Find(ver, t, min(limit, cap));res += v, E[i].cap -= v, E[i ^ 1].cap += v, limit -= v;if(limit) d[ver] = -1;}cur[u] = i;}return res;
}
inline int dinic(int s, int t) {int res = 0, flow;while(bfs(s, t)) while(flow = Find(s, t, INF)) res += flow;return res;
}
int main() {scanf("%s", s + 1); n = strlen(s + 1);for(int l = 1; l <= n; l ++ ) for(int r = l; r <= n; r ++ ) if(check(l, r)) L[++ tot] = l, R[tot] = r;for(int i = 1; i <= tot; i ++ ) odr[i] = i;sort(odr + 1, odr + tot + 1, cmp);for(int i = 1; i <= tot; i ++ ) {if(i == 1 || !cmp(odr[i], odr[i - 1])) l[++ c] = L[odr[i]], r[c] = R[odr[i]];}memset(head, -1, sizeof head);S = ++ nc; T = ++ nc;for(int i = 1; i <= c; i ++ ) {node[i][0] = ++ nc; node[i][1] = ++ nc;add(S, node[i][0], 1); add(node[i][1], T, 1);}for(int i = 1; i <= c; i ++ ) {for(int j = 1; j <= c; j ++ ) {if(i != j && chk(i, j)) add(node[i][0], node[j][1], 1);}}printf("%d\n", c - dinic(S, T));return 0;
} 

[CTSC2008] 祭祀

题意:
给定一张 n n n 个点, m m m 条边的 D A G DAG DAG,选出最多的点满足选出的任意两点互不可达

  1. 输出最多能选多少个点。
  2. 构造一组选出最多点的方案。
  3. 对每个点判断它能否出现在一组选了最多点的方案中。

1 ≤ n ≤ 100 , 1 ≤ m ≤ 1000 1 \leq n \leq 100, 1 \leq m \leq 1000 1n100,1m1000

分析:
实际上就是求最长反链的大小,构造一组最长反链,判断每个点能否在一条最长反链中。
对于第一问,显然和上一题做法类似,不再赘述。

我们先来看第三问:
假设我们求出了最长反链大小为 k k k。如何判断 x x x 能否在一条最长反链中呢?
强制选 x x x,将所有能到达 x x x 或能被 x x x 到达的点都删掉。对于剩下的偏序集,再求一遍最大反链
的大小。如果此时求出的答案为 k − 1 k - 1 k1,那么 x x x 就可以出现在一条最长反链中,否则不行。

根据这个思路,好像也可以做第二问了:
每次枚举一个点 x x x 删掉然后做上面的判断,如果 x x x 可以在一条最大反链中,那么把 x x x 选中然后把所有与 x x x 有偏序关系的点删掉。否则直接把 x x x 删掉不影响构造答案。一直这样做知道构造一组方案。每个点显然只会被枚举删除一次,复杂度和上面那一问一样。

但是第二问还有更优的做法:

首先需要会 构造二分图的最大点独立集,如果不会可以参考我的这篇博客。
然后我们考虑对于 拆点后的二分图,求出一组 最大点独立集。如果一个点 x x x 被拆成的两个点 x L , x R x_L, x_R xL,xR 都在这个独立集中,那么就把 x x x 放到答案点集 S S S 中。

证明:

  • S S S 是一条反链

首先我们选出来的点 x x x 都满足 x L x_L xL 未被标记 且 x R x_R xR 被标记
考虑 S S S 中如果存在两个点 u , v u, v u,v 满足偏序关系,那么一定存在一条 u L → v R u_L \to v_R uLvR 的有向边。
如果 v R v_R vR非匹配点 那么 u L u_L uL 一定被标记,与条件矛盾。
如果 v R v_R vR匹配点 那么所有和它相连的点都被标记, u L u_L uL 也一定被标记,与条件矛盾。

因此不存在选出两个点具有偏序关系,所以 S S S 是一条反链。

  • S S S 是一条最长反链

设独立集为 I I I,那么 ∣ I ∣ = 2 n − k |I| = 2n - k I=2nk(最大独立集等于总点数减最大匹配数),考虑 I − S I - S IS 的含义:相当于有多少个点 x x x 满足 [ x L x_L xL 未被标记 和 x R x_R xR 被标记其中之一成立],因此 ∣ I − S ∣ ≤ n |I - S| \leq n ISn,所以 ∣ S ∣ ≥ ∣ I ∣ − n = n − k |S| \geq |I| - n = n - k SIn=nk。我们知道 S S S 的大小最大为 n − k n - k nk,所以 S S S 的大小就等于 n − k n - k nk,是一条最长反链。

然后就做完了,这样构造的复杂度 O ( m ) O(m) O(m)

总复杂度 O ( n m n = n 3.5 ) O(nm\sqrt{n} = n^{3.5}) O(nmn =n3.5)

CODE:

#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int N = 110;
const int M = 1010;
const int INF = 1e8;
int n, m, f[N][N];
bool vis[N * 2], bok[N];
namespace Flow { // 每次能删掉一个点,以及相关点,求最小可交路径覆盖 struct edge {int v, last, cap;} E[N * N + 4 * N];int head[N * 2], tot, S, T, node[N][2], nc;int d[N * 2], cur[N * 2];int era[N];inline void add(int u, int v, int cap) {E[tot] = (edge) {v, head[u], cap}; head[u] = tot ++;E[tot] = (edge) {u, head[v], 0}; head[v] = tot ++;}inline bool bfs(int s, int t) {memset(d, -1, sizeof d); d[s] = 0; cur[s] = head[s];queue< int > q; q.push(s);while(!q.empty()) {int u = q.front(); q.pop();for(int i = head[u]; ~i; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(d[ver] == -1 && cap) {d[ver] = d[u] + 1;if(ver == t) return true;cur[ver] = head[ver];q.push(ver);}}}return false;}int Find(int u, int t, int lim) {if(u == t) return lim;int res = 0;for(int i = cur[u]; lim && ~i; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(d[ver] == d[u] + 1 && cap) {int v = Find(ver, t, min(lim, cap));res += v; lim -= v; E[i].cap -= v; E[i ^ 1].cap += v;if(lim) d[ver] = -1;}cur[u] = i;}return res;}inline int dinic(int s, int t) {int res = 0, flow;while(bfs(s, t)) while(flow = Find(s, t, INF)) res += flow;return res;}inline int del(int x) { // 删掉 x 和所有 x 能到的以及能到 x 的 memset(head, -1, sizeof head); nc = 0; tot = 0;memset(era, 0, sizeof era);S = ++ nc, T = ++ nc; int cnt = 0;for(int i = 1; i <= n; i ++ ) {if(i == x || f[i][x] || f[x][i]) era[i] = 1, cnt ++;}for(int i = 1; i <= n; i ++ ) {if(!era[i]) {node[i][0] = ++ nc, node[i][1] = ++ nc;add(S, node[i][0], 1);add(node[i][1], T, 1);}}for(int i = 1; i <= n; i ++ ) {if(era[i]) continue;for(int j = 1; j <= n; j ++ ) {if(!era[j]) if(f[i][j]) add(node[i][0], node[j][1], 1);}}return n - cnt - dinic(S, T);	}
}
void dfs(int x) {if(vis[x]) return;vis[x] = 1;for(int i = Flow::head[x]; ~i; i = Flow::E[i].last) {int ver = Flow::E[i].v, cap = Flow::E[i].cap;if(ver == Flow::S || ver == Flow::T) continue;if(cap == 0) dfs(ver);}
}
int main() {scanf("%d%d", &n, &m);for(int i = 1; i <= m; i ++ ) {int u, v; scanf("%d%d", &u, &v);f[u][v] = 1;}for(int i = 1; i <= n; i ++ ) for(int j = 1; j <= n; j ++ ) for(int k = 1; k <= n; k ++ ) f[j][k] |= (f[j][i] & f[i][k]); int res = Flow::del(0); cout << res; cout << '\n';return 0;
}

CF590E Birthday

掉毛题目卡我半天。

题意:
给定 n n n 个仅包含 a , b a,b a,b 的字符串 { s i } \{s_i\} {si},保证它们互不相同。
你需要去掉尽量少的字符串,使得剩下的字符串中不存在某一个串是另一个串的子串。
输出答案和一组构造。

1 ≤ n ≤ 750 , ∑ ∣ s i ∣ ≤ 10 7 1 \leq n \leq 750, \sum |s_i| \leq 10^7 1n750,si107

分析:
偏序关系显然是子串关系,题目要求的也是最长反链大小及一组构造。
显然只要我们能求出子串关系然后就变得和上一题一模一样了。
显然是建立 A C A M ACAM ACAM 然后通过一些方法维护。这道题比较恶心,字符串总长度太大导致你甚至不能写任何关于 A C AC AC 自动机的 d f s dfs dfs 函数,否则就会爆栈。

具体维护方法是这样:建立 f a i l fail fail 指针时顺便对每个节点 p p p维护出 f a i l fail fail 树跳祖先第一个能跳到的某个字符串末尾状态,记作 p o s p pos_p posp。然后对每个字符串 i i i 重新在 A C AC AC 自动机跑一遍从根到末尾状态节点的路径,将经过的 p p p 对应的 p o s p pos_p posp i i i 连一条有向边。如果经过的 p p p 不是当前字符串末尾状态但是是另一个字符串的末尾也连一条有向边。最后跑一遍传递闭包即可。

复杂度 O ( n 3 + n 2.5 ) O(n^3 + n^{2.5}) O(n3+n2.5)

CODE:

// 又是这种构造最大反链的题 
#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
struct IO{static const int S=1<<21;char buf[S],*p1,*p2;int st[105],Top;~IO(){clear();}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}template<typename T>inline IO&operator >> (T&x){x=0;bool f=0;char ch=gc();while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();f?x=-x:0;return *this;}inline IO&operator << (const char c){pc(c);return *this;}template<typename T>inline IO&operator << (T x){if(x<0) pc('-'),x=-x;do{st[++st[0]]=x%10,x/=10;}while(x);while(st[0]) pc('0'+st[st[0]--]);return *this;}
} fin, fout;
const int M = 1e7 + 10;
const int N = 760;
const int INF = 1e8;
int n, et[M], l[N], r[N];
int q[M], g[N][N];
short s[M];
struct ACAM {int tot, pos[M], tr[M][2], fail[M], Top;inline void ins(int idx) {int p = 0;for(int i = l[idx]; i <= r[idx]; i ++ ) {if(!tr[p][s[i]]) tr[p][s[i]] = ++ tot;p = tr[p][s[i]];}et[p] = idx;}inline void build() {int l = 1, r = 0;for(int i = 0; i <= 1; i ++ ) if(tr[0][i]) q[++ r] = tr[0][i];while(l <= r) {int u = q[l ++]; for(int i = 0; i <= 1; i ++ ) {if(tr[u][i]) {fail[tr[u][i]] = tr[fail[u]][i];pos[tr[u][i]] = (et[fail[tr[u][i]]] ? et[fail[tr[u][i]]] : pos[fail[tr[u][i]]]);q[++ r] = tr[u][i];}else tr[u][i] = tr[fail[u]][i];}}}inline void get(int idx) {int p = 0;for(int i = l[idx]; i <= r[idx]; i ++ ) {p = tr[p][s[i]];if(pos[p]) g[idx][pos[p]] = 1;if(i != r[idx] && et[p]) g[idx][et[p]] = 1;}}
} AC;
struct Flow {struct edge {int v, last, cap;} E[N * N + 4 * N];int head[N * 2], tot, node[N][2], S, T, nc;int cur[N * 2], d[N * 2];bool bok[N * 2];inline void add(int u, int v, int cap) {E[tot] = (edge) {v, head[u], cap}; head[u] = tot ++;E[tot] = (edge) {u, head[v], 0}; head[v] = tot ++;}inline void build(int n) {memset(head, -1, sizeof head);S = ++ nc; T = ++ nc; for(int i = 1; i <= n; i ++ ) {node[i][0] = ++ nc; node[i][1] = ++ nc;add(S, node[i][0], 1); add(node[i][1], T, 1);}}inline bool bfs(int s, int t) {memset(d, -1, sizeof d); d[s] = 0;queue< int > q; q.push(s); cur[s] = head[s];while(!q.empty()) {int u = q.front(); q.pop();for(int i = head[u]; ~i; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(d[ver] == -1 && cap) {d[ver] = d[u] + 1;if(ver == t) return 1;cur[ver] = head[ver];q.push(ver);}}}return 0;}int Find(int u, int t, int limit) {if(u == t) return limit;int res = 0;for(int i = cur[u]; limit && ~i; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(d[ver] == d[u] + 1 && cap) {int v = Find(ver, t, min(cap, limit));res += v; limit -= v; E[i].cap -= v; E[i ^ 1].cap += v;if(limit) d[ver] = -1;}cur[u] = i;}return res;}inline int dinic(int s, int t) {int res = 0, flow;while(bfs(s, t)) {while(flow = Find(s, t, INF)) res += flow;}return res; }void dfs(int x) {if(bok[x]) return ; bok[x] = 1;for(int i = head[x]; ~i; i = E[i].last) {int ver = E[i].v, cap = E[i].cap;if(!cap) dfs(ver);}}inline void solve() { // 求出最大反链大小, 以及构造一组方案 int res = n - dinic(S, T);fout << res; fout.pc('\n'); //printf("%d\n", res);for(int i = 1; i <= n; i ++ ) {for(int j = head[node[i][1]]; ~j; j = E[j].last) {if(E[j].v == T && E[j].cap == 1) dfs(node[i][1]);}}for(int i = 1; i <= n; i ++ ) {if(!bok[node[i][0]] && bok[node[i][1]]) fout << i, fout.pc(' ');}fout.pc('\n');}
} f;
inline void floyd() {for(int i = 1; i <= n; i ++ ) for(int j = 1; j <= n; j ++ ) for(int k = 1; k <= n; k ++ ) g[j][k] |= (g[j][i] & g[i][k]);for(int i = 1; i <= n; i ++ ) for(int j = 1; j <= n; j ++ )if(i != j && g[i][j]) f.add(f.node[i][0], f.node[j][1], 1);
}
int main() {fin >> n;for(int i = 1; i <= n; i ++ ) {l[i] = r[i - 1] + 1; r[i] = r[i - 1]; char c = fin.gc();while(c != '\n') s[++ r[i]] = c - 'a', c = fin.gc();AC.ins(i);}AC.build(); f.build(n);for(int i = 1; i <= n; i ++ ) AC.get(i);floyd(); f.solve(); return 0;
}

相关文章:

  • 智能危险品搬运机器人市场报告:行业趋势与未来展望
  • qt常用控件--01
  • 对于网站业务安全SCDN都能够从哪些方面进行保护?
  • Kafka协议开发总踩坑?3步拆解二进制协议核心
  • IP 风险画像网络违规行为识别
  • 语音相关-浏览器的自动播放策略研究和websocket研究
  • Kafka线上集群部署方案:从环境选型到资源规划思考
  • C#学习日记
  • 基于存储过程的MySQL自动化DDL同步系统设计
  • GNU Octave 基础教程(2):第一个 Octave 程序
  • 作为运营,需要在账号中给用户提供什么?
  • 文件管理总结
  • HCIP-数据通信基础
  • python高校运动会数据分析管理系统
  • LINUX620 NFS
  • 空壳V3.0,免费10开!
  • PAI推理重磅发布模型权重服务,大幅降低冷启动与扩容时长
  • Qi无线充电:车载充电的便捷与安全之选
  • 多相机三维人脸扫描仪:超写实数字人模型制作“加速器”
  • vue3组件式开发示例
  • aspcms网站地图模板/百度网页提交入口
  • 国家信用信息企业公示系统官网/windows优化大师靠谱吗
  • 北京朝阳区做网站/seo是哪个国家
  • 网站建设合同验收/百度应用市场官网
  • 开一家做网站的公司/百度搜索关键词排名优化技术
  • 庆阳市建设局海绵城市网站/今日军事新闻头条打仗