【生物】高中生物选修三:胚胎工程
[本文适用于2015 …
May all the beauty be blessed.
题目地址:洛谷:【P2962】[USACO09NOV]灯Lights – 洛谷、BZOJ:Problem 1770. — [Usaco2009 Nov]lights 燈
Bessie and the cows were playing games in the barn, but the power was reset and the lights were all turned off. Help the cows get all the lights back on so they can resume their games.
The N (1 <= N <= 35) lights conveniently numbered 1..N and their switches are arranged in a complex network with M (1 <= M <= 595) clever connection between pairs of lights (see below).
Each light has a switch that, when toggled, causes that light — and all of the lights that are connected to it — to change their states (from on to off, or off to on).
Find the minimum number of switches that need to be toggled in order to turn all the lights back on.
It’s guaranteed that there is at least one way to toggle the switches so all lights are back on.
贝希和她的闺密们在她们的牛棚中玩游戏。但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了。贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望。她希望您能够帮帮她,把所有的灯都给重新开起来!她才能继续快乐地跟她的闺密们继续玩游戏! 牛棚中一共有N(1 <= N <= 35)盏灯,编号为1到N。这些灯被置于一个非常複杂的网络之中。有M(1 <= M <= 595)条很神奇的无向边,每条边连接两盏灯。 每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。状态改变指的是:当一盏灯是开著的时候,这盏灯被关掉;当一盏灯是关著的时候,这盏灯被打开。 问最少要按下多少个开关,才能把所有的灯都给重新打开。 数据保证至少有一种按开关的方案,使得所有的灯都被重新打开。
输入格式:
* Line 1: Two space-separated integers: N and M.
* Lines 2..M+1: Each line contains two space-separated integers representing two lights that are connected. No pair will be repeated.
输出格式:
* Line 1: A single integer representing the minimum number of switches that need to be flipped in order to turn on all the lights.
输入样例#1:
5 6 1 2 1 3 4 2 3 4 2 5 5 3
输出样例#1:
3
There are 5 lights. Lights 1, 4, and 5 are each connected to both lights 2 and 3.
Toggle the switches on lights 1, 4, and 5.
考虑一个灯亮的时候,它和与它有边相连的灯的开关次数一定是奇数,那么我们可以把这个开关次数设成未知数,每个灯列一个异或方程,就得到了一个n元异或方程组。这个可以用高斯消元解决。然而如果有自由元的情况我们需要看一下怎么来安排它比较优,因此就用DFS来计算答案。
// Code by KSkun, 2018/3
#include <cstdio>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 105;
int n, m;
bool mat[MAXN][MAXN];
inline void gauss() {
for(int i = 1; i <= n; i++) {
int r = i;
for(int j = i + 1; j <= n; j++) {
if(mat[j][i]) {
r = j;
break;
}
}
if(r != i) {
for(int j = 1; j <= n + 1; j++) std::swap(mat[i][j], mat[r][j]);
}
for(int j = i + 1; j <= n; j++) {
if(mat[j][i]) {
for(int k = 1; k <= n + 1; k++) mat[j][k] ^= mat[i][k];
}
}
}
}
int ans = 1e9;
bool res[MAXN];
inline void dfs(int x, int tot) {
if(tot > ans) return;
if(!x) {
ans = std::min(ans, tot);
return;
}
if(mat[x][x]) {
res[x] = mat[x][n + 1];
for(int i = n; i > x; i--) {
if(mat[x][i]) res[x] ^= res[i];
}
if(res[x]) dfs(x - 1, tot + 1); else dfs(x - 1, tot);
} else {
res[x] = 0; dfs(x - 1, tot);
res[x] = 1; dfs(x - 1, tot + 1);
}
}
int u, v;
int main() {
n = readint(); m = readint();
while(m--) {
u = readint(); v = readint();
mat[u][v] = mat[v][u] = 1;
}
for(int i = 1; i <= n; i++) {
mat[i][n + 1] = 1;
mat[i][i] = 1;
}
gauss();
dfs(n, 0);
printf("%d", ans);
return 0;
}
题目地址:洛谷:【P3435】[POI2006]OKR-Periods of Words – 洛谷、BZOJ:Problem 1511. — [POI2006]OKR-Periods of Words
A string is a finite sequence of lower-case (non-capital) letters of the English alphabet. Particularly, it may be an empty sequence, i.e. a sequence of 0 letters. By A=BC we denotes that A is a string obtained by concatenation (joining by writing one immediately after another, i.e. without any space, etc.) of the strings B and C (in this order). A string P is a prefix of the string !, if there is a string B, that A=PB. In other words, prefixes of A are the initial fragments of A. In addition, if P!=A and P is not an empty string, we say, that P is a proper prefix of A.
A string Q is a period of Q, if Q is a proper prefix of A and A is a prefix (not necessarily a proper one) of the string QQ. For example, the strings abab and ababab are both periods of the string abababa. The maximum period of a string A is the longest of its periods or the empty string, if A doesn’t have any period. For example, the maximum period of ababab is abab. The maximum period of abc is the empty string.
Task Write a programme that: reads from the standard input the string’s length and the string itself,calculates the sum of lengths of maximum periods of all its prefixes,writes the result to the standard output.
一个串是有限个小写字符的序列,特别的,一个空序列也可以是一个串. 一个串P是串A的前缀, 当且仅当存在串B, 使得 A = PB. 如果 P A 并且 P 不是一个空串,那么我们说 P 是A的一个proper前缀. 定义Q 是A的周期, 当且仅当Q是A的一个proper 前缀并且A是QQ的前缀(不一定要是proper前缀). 比如串 abab 和 ababab 都是串abababa的周期. 串A的最大周期就是它最长的一个周期或者是一个空串(当A没有周期的时候), 比如说, ababab的最大周期是abab. 串abc的最大周期是空串. 给出一个串,求出它所有前缀的最大周期长度之和.。
输入格式:
In the first line of the standard input there is one integer k (1≤k≤1 000 000) – the length of the string. In the following line a sequence of exactly k lower-case letters of the English alphabet is written – the string.
输出格式:
In the first and only line of the standard output your programme should write an integer – the sum of lengths of maximum periods of all prefixes of the string given in the input.
输入样例#1:
8 babababa
输出样例#1:
24
想到KMP的fail数组的性质确实挺难,我们来探究下性质。
有字符串abababababab
,它的fail数组是0 0 1 2 3 4 5 6 7 8 9 10
。我们考虑前缀aba
,它最后一个字符的fail是1,说明有一个后缀与到第一个字符为止的前缀相同,把这个相同的部分删去以后的部分ab
就是它的一个周期。然后你再尝试一下abab
,你会得到相同的结论,而且你会发现i – fail[i]就是这个前缀的最小周期。
有了最小周期,怎么来找最大周期?首先fail为0的前缀是没有周期的,不用说。我们来考虑i和fail[i]周期的关系,删掉fail[i]前缀的多出来那部分(即一个长为fail[i]的后缀)得到的是它的一个周期,而实际上i前缀删掉那一部分也是它的一个周期。这个可以实践一下。
我们又知道,第一个fail不为0的位置求出来的最小周期同时也是它的最大周期,越往后最大周期越大,那么我们就把fail[i]给变成fail[fail[i]],这样肯定是更优的。最后统计每个位置的i – fail[i]即可。
// Code by KSkun, 2018/3
#include <cstdio>
typedef long long LL;
const int MAXN = 1000005;
int n, fail[MAXN];
char str[MAXN];
inline void calfail() {
int i = 2, j = 0;
fail[1] = 0;
for(; i <= n; i++) {
while(j && str[j + 1] != str[i]) j = fail[i];
if(str[j + 1] == str[i]) j++;
fail[i] = j;
}
}
int main() {
scanf("%d%s", &n, str + 1);
calfail();
for(int i = 1; i <= n; i++) {
if(fail[fail[i]]) fail[i] = fail[fail[i]];
}
LL ans = 0;
for(int i = 1; i <= n; i++) {
if(fail[i]) ans += i - fail[i];
}
printf("%lld", ans);
return 0;
}
题目地址:洛谷:【P2336】[SCOI2012]喵星球上的点名 – 洛谷、BZOJ:Problem 2754. — [SCOI2012]喵星球上的点名
a180285 幸运地被选做了地球到喵星球的留学生。他发现喵星人在上课前的点名现象非常有趣。
假设课堂上有 N 个喵星人,每个喵星人的名字由姓和名构成。喵星球上的老师会选择M 个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这个喵星人就必须答到。
然而,由于喵星人的字码过于古怪,以至于不能用 ASCII 码来表示。为了方便描述,a180285 决定用数串来表示喵星人的名字。
现在你能帮助 a180285 统计每次点名的时候有多少喵星人答到,以及 M 次点名结束后每个喵星人答到多少次吗?
输入格式:
现在定义喵星球上的字符串给定方法:
先给出一个正整数 L ,表示字符串的长度,接下来L个整数表示字符串的每个字符。
输入的第一行是两个整数 N 和 M。
接下来有 N 行, 每行包含第 i 个喵星人的姓和名两个串。 姓和名都是标准的喵星球上的字符串。
接下来有 M 行,每行包含一个喵星球上的字符串,表示老师点名的串。
输出格式:
对于每个老师点名的串输出有多少个喵星人应该答到。
然后在最后一行输出每个喵星人被点到多少次。
输入样例#1:
2 3 6 8 25 0 24 14 8 6 18 0 10 20 24 0 7 14 17 8 7 0 17 0 5 8 25 0 24 0 4 8 25 0 24 4 7 0 17 0 4 17 0 8 25
输出样例#1:
2 1 0 1 2
对于30%的数据,保证:
1 ≤ N ,M ≤ 1000 喵星人的名字总长不超过 4000,点名串的总长不超过 2000,
对于100%的数据,保证:
1 ≤ N ≤ 20000, 1 ≤ M ≤ 50000 喵星人的名字总长和点名串的总长分别不超过100000
保证喵星人的字符串中作为字符存在的数不超过10000
考虑一个AC自动机的暴力做法。首先因为字符集大小并不确定,而且可能很大,我们要考虑map存儿子。接着因为字符集大小不确定我们没办法加虚边了只能暴力跳fail。这导致了最后洛谷#11 TLE的惨案。
我们考虑把点名串扔进去,把姓名用一个非法字符连接起来来匹配。每匹配到一个点名串在点名串的计数器上+1,然后统计这个姓名匹配到了多少点名串。
记录该点是否被访问过的数组vis开的很大,最好不要memset,用一个栈记下修改过的节点最后改回去。
我加了一堆register,全部inline,能避免STL的改手写,加了fread,然而还是敌不过洛谷的#11 QAQ!
所以说这是个AC自动机暴力啦,我也不会fail树高论,也不会SA,就这样吧,以后学了SA再来A这题。
对不起,SA真的是能为所欲为的!
如果我们将AC自动机中所有fail指针(未路径压缩)反向建有向边,会得到一棵以0为根方向向叶子节点的有向树。利用fail树的有关性质,我们可以把字符串匹配问题转换成树上问题,优化复杂度。本题中也可以使用该种算法。
首先我们可以类似的在姓名中间加一个不会出现的字符,这里我用的是-1
,插入Trie树中,然后计算fail指针建立fail树。需要注意的是fail指针无法路径压缩,因此只能暴力跳fail来找。为了方便,后面建树的时候对节点编号+1处理了一下,避免0的特判。事实证明,这一操作为后面的处理带来了不少的麻烦,许多地方忘记+1换算了。有了fail树以后,我们要利用fail树的相关性质解决题目问题。
对于问题1,是求一个模式串出现在哪些文本串中了,这个问题可以转化为一个模式串的末尾点在fail树的子树中有多少不同文本串的节点。这是由于,文本串中出现过的模式串位置末尾的fail指针会指向这一模式串的末尾,由于反向,就变成了模式串末尾指向文本串节点了,那就是一个子树问题。这个问题可以转化成一个DFS序序列上的数颜色种数问题,这让我们想起了[SDOI2009]HH的项链一题,采用相同的树状数组+扫描线的做法即可求出。因此,我们需要标注每个节点属于哪个文本串这样一个颜色信息,并且处理出一个每个颜色有哪些点的链表,方便扫描线维护信息。如果文本串很多,我们不可能每次都枚举一下每个颜色,因此不如再开个链表记录一下每个点上有几种颜色。链表空间紧凑,适合用来做这种不确定空间且不需要随机查找的统计问题。另外还要处理出子树大小,方便确定查询区间。上述信息处理完毕以后,就是上面那个省选题的做法了。这一部分的复杂度是O(n \log n)的。
对于问题2,求一个文本串中出现过几种模式串。如果我们默认每个节点初始权值为0,而对每个模式串结尾节点的权值加1,则模式串到树根路径上的点权和就是这个点为结尾的后缀为模式串的个数,如果将文本串上每个节点到树根的路径求并求权值和,就是我们要的答案了。树链的并我们可以用树链剖分+线段树区间染色求和的方式来求,这样做则是O(n \log^2 n)的。如果想只有一个log,可以用将节点按照DFS序排序后的顺序统计信息,加入第i个节点的时候,减去i与i-1节点的LCA到树根的路径权值和,这样可以避免这一段公共路径权值和被计入两次,起到了去重的效果。求LCA采用倍增法,则复杂度可以降为O(n \log n)了。
但是我们仔细想一下,这些内容都很长。我们需要Trie树的插入和统计信息,需要两个链表分别维护一个节点上的颜色种类和一个颜色涉及的节点数,需要一个树状数组,需要一个树DFS统计树信息,需要一个倍增LCA的处理,需要一个离线处理区间颜色数的算法,需要一个按DFS序排序去重的计数算法。以上就是写出这道题的这种解法的所有需求,接下来就是一步一步地去实现它们了。
在实现过程中,遇到了种种问题,最困惑我的一点就是写代码的时候DFS序号和节点序号搞混了,以及节点序号+1忘记了,不过如果能够自己实现一遍,相信是很锻炼代码能力和调试能力的。这个题今天花了一天来做,收获也很大。
// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
struct io {
char buf[1 << 26], *s;
io() {
fread(s = buf, 1, 1 << 26, stdin);
}
inline int read() {
register int res = 0;
while(*s < '0' || *s > '9') s++;
while(*s >= '0' && *s <= '9') res = res * 10 + *s++ - '0';
return res;
}
} ip;
#define readint ip.read
const int MAXN = 100005, MAXL = 100005;
int n, m;
std::vector<int> names[MAXN], val[MAXL];
int fail[MAXL], cnt;
std::map<int, int> ch[MAXL];
int que[MAXL], l, r;
inline void insert(int id) {
register int len = readint(), p = 0;
for(register int i = 0; i < len; i++) {
int t = readint();
if(!ch[p][t]) ch[p][t] = ++cnt;
p = ch[p][t];
}
val[p].push_back(id);
}
inline void calfail() {
l = r = 0;
fail[0] = 0;
for(std::map<int, int>::iterator it = ch[0].begin(); it != ch[0].end(); it++) {
register int u = it->second;
que[r++] = u;
fail[u] = 0;
}
while(l != r) {
register int u = que[l++];
for(std::map<int, int>::iterator it = ch[u].begin(); it != ch[u].end(); it++) {
int t = it->first, v = it->second, p = fail[u];
while(p && !ch[p][t]) p = fail[p];
fail[v] = ch[p][t];
que[r++] = v;
}
}
}
int ctn[MAXN], qur[MAXN];
bool vis[MAXL];
int sta[MAXL], stot;
inline int calcnt(int p) {
register int res = 0;
for(; p; p = fail[p]) {
if(vis[p]) break;
vis[p] = true;
sta[++stot] = p;
for(register int i = 0; i < val[p].size(); i++) {
res++;
qur[val[p][i]]++;
}
}
return res;
}
inline void query(int id) {
register int p = 0;
stot = 0;
for(register int i = 0; i < names[id].size(); i++) {
int t = names[id][i];
while(p && !ch[p][t]) p = fail[p];
p = ch[p][t];
ctn[id] += calcnt(p);
}
for(register int i = 1; i <= stot; i++) {
vis[sta[i]] = false;
}
}
int main() {
n = readint(); m = readint();
for(register int i = 1; i <= n; i++) {
register int len = readint();
while(len--) names[i].push_back(readint());
names[i].push_back(-1);
len = readint();
while(len--) names[i].push_back(readint());
}
for(register int i = 1; i <= m; i++) {
insert(i);
}
calfail();
for(register int i = 1; i <= n; i++) {
query(i);
}
for(register int i = 1; i <= m; i++) {
printf("%d\n", qur[i]);
}
for(register int i = 1; i <= n; i++) {
printf("%d ", ctn[i]);
}
return 0;
}
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <map>
#include <queue>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 200005;
// 双向链表
struct LinkedList {
int head[MAXN], tail[MAXN], val[MAXN], pre[MAXN], nxt[MAXN], tot;
LinkedList() {
tot = 0;
}
// 在p所属的链表头部插入v这个值
inline void insert(int p, int v) {
tot++;
if(!tail[p]) tail[p] = tot;
val[tot] = v; if(head[p]) pre[head[p]] = tot; nxt[tot] = head[p]; head[p] = tot;
}
} col, col2;
// 树状数组
struct BinaryIndexTree {
int tr[MAXN];
inline int lowbit(int x) {
return x & -x;
}
inline void add(int x, int v) {
for(int i = x; i < MAXN; i += lowbit(i)) {
tr[i] += v;
}
}
inline int query(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i)) {
res += tr[i];
}
return res;
}
} bit;
// 字符集太大,用map存儿子省空间
std::map<int, int> ch[MAXN];
std::queue<int> que;
std::vector<int> gra[MAXN]; // fail树存在这里
int fail[MAXN], ecnt[MAXN], end[MAXN], tot;
// Trie树的插入,分别处理文本串和模式串
inline void insert(std::vector<int> &str, int id, bool isname) {
int p = 0;
for(int i = 0; i < str.size(); i++) {
int t = str[i];
if(!ch[p][t]) {
ch[p][t] = ++tot;
}
if(isname) {
// 统计每个节点上的颜色种类
col.insert(ch[p][t] + 1, id);
}
p = ch[p][t];
}
if(!isname) {
// 统计一个节点是多少模式串的结尾,模式串可能有多个相同不可用bool
ecnt[p + 1]++; end[id] = p + 1;
}
}
// 计算fail数组
inline void calfail() {
for(std::map<int, int>::iterator it = ch[0].begin(); it != ch[0].end(); it++) {
que.push(it->second);
gra[1].push_back(it->second + 1);
}
while(!que.empty()) {
int p = que.front(); que.pop();
for(std::map<int, int>::iterator it = ch[p].begin(); it != ch[p].end(); it++) {
int t = fail[p], q = it->second;
// 暴力跳fail计算
while(!ch[t][it->first] && t) t = fail[t];
fail[q] = ch[t][it->first];
gra[ch[t][it->first] + 1].push_back(q + 1);
que.push(q);
}
}
}
int anc[MAXN][20], siz[MAXN], dfn[MAXN], dep[MAXN], ptn[MAXN], sum[MAXN], clk;
// DFS处理fail树信息
inline void dfs(int u) {
siz[u] = 1; dfn[u] = ++clk; ptn[dfn[u]] = u;
sum[u] = sum[anc[u][0]] + ecnt[u];
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == anc[u][0]) continue;
dep[v] = dep[u] + 1;
anc[v][0] = u;
dfs(v);
siz[u] += siz[v];
}
}
int n, m;
struct Query {
int id, l, r;
inline bool operator<(const Query &rhs) const {
return l == rhs.l ? r < rhs.r : l < rhs.l;
}
} qur[MAXN];
// 第一个问题
int ans1[MAXN];
inline void main1() {
// 按DFS序处理出每个颜色的链表
for(int i = 1; i < MAXN; i++) {
for(int j = col.head[ptn[i]]; j; j = col.nxt[j]) {
int c = col.val[j];
col2.insert(c, i);
}
}
// 初始化询问以及对询问排序
for(int i = 1; i <= m; i++) {
qur[i] = Query {i, dfn[end[i]], dfn[end[i]] + siz[end[i]] - 1};
}
// 初始化树状数组,在每个颜色的初位置+1
for(int i = 1; i <= n; i++) {
bit.add(col2.val[col2.tail[i]], 1);
}
std::sort(qur + 1, qur + m + 1);
int lst = 1;
// 同[SDOI2009]HH的项链
for(int i = 1; i <= m; i++) {
if(lst != qur[i].l) {
for(int j = lst; j < qur[i].l; j++) {
for(int k = col.head[ptn[j]]; k; k = col.nxt[k]) {
int c = col.val[k];
bit.add(j, -1);
col2.tail[c] = col2.pre[col2.tail[c]];
if(col2.val[col2.tail[c]]) bit.add(col2.val[col2.tail[c]], 1);
}
}
lst = qur[i].l;
}
ans1[qur[i].id] = bit.query(qur[i].r);
}
for(int i = 1; i <= m; i++) {
printf("%d\n", ans1[i]);
}
}
// 倍增LCA
inline void preparelca() {
for(int j = 1; j < 20; j++) {
for(int i = 1; i < MAXN; i++) {
anc[i][j] = anc[anc[i][j - 1]][j - 1];
}
}
}
inline int querylca(int x, int y) {
if(dep[x] > dep[y]) std::swap(x, y);
int del = dep[y] - dep[x];
for(int i = 19; i >= 0; i--) {
if(del & (1 << i)) {
y = anc[y][i];
}
}
if(x == y) return x;
for(int i = 19; i >= 0; i--) {
if(anc[x][i] != anc[y][i]) {
x = anc[x][i]; y = anc[y][i];
}
}
return anc[x][0];
}
// 第二个问题
std::vector<int> name[MAXN];
int ans2[MAXN];
inline bool cmp(int a, int b) {
return dfn[a] < dfn[b];
}
inline void main2() {
for(int i = 1; i <= n; i++) {
int p = 0;
// 将字符串换成该字符串的节点
for(int j = 0; j < name[i].size(); j++) {
p = ch[p][name[i][j]];
name[i][j] = p + 1;
}
// 按DFS序计算树链的并的权值和
std::sort(name[i].begin(), name[i].end(), cmp);
for(int j = 0; j < name[i].size(); j++) {
ans2[i] += sum[name[i][j]];
if(j) ans2[i] -= sum[querylca(name[i][j], name[i][j - 1])];
}
}
for(int i = 1; i <= n; i++) {
printf("%d ", ans2[i]);
}
}
int len;
std::vector<int> s;
int main() {
n = readint(); m = readint();
for(int i = 1; i <= n; i++) {
s.clear();
len = readint();
while(len--) {
s.push_back(readint());
}
s.push_back(-1);
len = readint();
while(len--) {
s.push_back(readint());
}
insert(s, i, true);
name[i] = s;
}
for(int i = 1; i <= m; i++) {
s.clear();
len = readint();
while(len--) {
s.push_back(readint());
}
insert(s, i, false);
}
calfail();
dfs(1);
preparelca();
main1();
main2();
return 0;
}