JZM 的印象笔记 (卷积,分块)
题面
题目背景
大名鼎鼎的 OI 天花板选手 JZM 对自己的好伙伴——印象笔记有些生疏了
题目描述
作为一名 OI 选手,他的笔记中的字母只包含数字0和1。
 JZM 在印象笔记中找到了一行
N
N
N 个字母的印象语录,而 JZM 打算朗读一段长度为
M
M
M 的话语。由于 JZM 记得不是很清楚,他需要从印象语录的某一个位置开始,对照着印象语录进行朗读。当印象语录上的当前字符和 JZM 话语中即将朗读的字符不一样时,JZM 就会跳过这对不一样的字符继续朗读下一个位置,我们称之为发生一次停顿。JZM 所要朗读的话语有些段落文辞优美,有些段落儒雅随和。具体地,该话语中第
i
i
i 个字符有优美度
f
i
f_i
fi,如果在第
i
i
i 个字符上发生停顿,JZM 的容忍度就会减少
f
i
f_i
fi 。JZM 的初始容忍度为
K
K
K,当他的容忍度降至 0 以下时,JZM 就不想继续朗读了。
例如,印象语录为 1101110,而 JZM 则打算从第 3 个字符开始对照,朗读 110011,
K
=
1
K=1
K=1,
f
=
{
1
,
1
,
4
,
5
,
1
,
3
}
f=\{1,1,4,5,1,3\}
f={1,1,4,5,1,3} 。
过程中,印象语录的第三个字符 0 与 JZM 所要朗读的第一字符 1 不同,于是他发生了一次停顿,容忍度减少 1 。
之后,语录中的第四个字符 1 和话语的第二个字符 1 相同,顺利朗读,进入下一对。
印象语录的第五个字符 1 与 JZM 所要朗读的第三个字符 0 不同,于是他又发生了一次停顿,容忍度减少 4,变为 -4,于是 JZM 停下了朗读。
我们把 JZM 停下朗读时当前进行到印象语录中字符的位置称为终止位置,上面这个例子中, JZM 的终止位置为 5 。特别地,假如 JZM 从第
i
i
i 个位置开始顺利朗读完了
M
M
M 个字符,他的终止位置是
i
+
M
i+M
i+M;假如读到一半印象语录结束了,他的终止位置是
N
+
1
N+1
N+1 。
JZM 此时要断章取义,他想尽可能顺利地朗读话语,于是身为 OI 领头羊的他把这个小任务交了你,希望你能告诉他,对于每一个可能的朗读起始位置 1 到 N ,他的终止位置会是哪里。
输入格式
第一行三个整数
N
         
M
         
K
N\;M\;K
NMK 表示印象语录的长度,JZM 朗读话语的长度和 JZM 的容忍度。
 第二行
N
N
N 个
0
/
1
0/1
0/1 字符,为印象语录。
 第三行
M
M
M 个
0
/
1
0/1
0/1 字符,为 JZM 朗读的话语。
 第三行
M
M
M 个整数,为话语中每一个字符的优美度。
输出格式
输出一行
N
N
N 个整数,第
i
i
i 个整数表示对于朗读起始位置
i
i
i ,JZM 的终止位置会是哪里。
Sample Input
20 10 15
10011111001101010110
1011110101
2 1 4 7 4 8 3 6 4 7
Sample Output
11 12 13 13 11 13 17 15 19 15 20 18 20 21 21 21 21 21 21 21
数据范围
对于测试点1:
N
,
M
≤
1
0
3
N,M \leq 10^3
N,M≤103
 对于测试点2~3:
K
=
0
K=0
K=0
 对于测试点4 :
K
≤
100
K\leq 100
K≤100
 对于测试点5~6:话语中所有字符均为 1
 对于测试点7~10 : 无特殊限制
 对于所有的数据,
1
≤
N
,
M
≤
1
0
5
,
         
0
≤
K
≤
1
0
8
,
         
1
≤
f
i
≤
1
0
3
1\leq N,M\leq 10^5\;,\;0\leq K\leq 10^8\;,\;1\leq f_i\leq10^3
1≤N,M≤105,0≤K≤108,1≤fi≤103
 时间限制 2 s,空间限制 64 MB。
题解
考试中,我正敲着 K ≤ 100 的部分分,调试着后缀数组+ RMQ 的百行大代码,忽然听见对面的大佬们在小声讨论:
\;\;\;\;
“你用的是 FFT 还是 NTT ? 我觉得 FFT 可能有点慢。”
NTT ? 哪一到题?T1 跟 NTT 有什么关系?
\;\;\;\;
“你开的块大小是多少?我开了 3000”
分块?・∀・?怎么又跟分块扯上关系了?
罢了罢了,肯定不是这道题。
于是我就打了前面四个点的暴力,相比起他们的 AC,我只拿了 50 pts(多了十分?看下文)
如果限定终止位置在哪里,我们就可以想想怎么快速地算容忍度会降多少,是否降到 0 以下。
 设话语为 S 字符串 ,印象语录为 T 字符串 ,
a
n
s
i
ans_i
ansi 代表第
i
i
i 个位置开始匹配完整个字符串 S 后消耗的容忍度,则:
a
n
s
i
=
∑
j
=
1
m
(
S
[
i
+
j
−
1
]
         
x
o
r
         
T
[
j
]
)
⋅
f
j
ans_i=\sum_{j=1}^{m}(S[i+j-1]\,xor\,T[j])\cdot f_j
ansi=j=1∑m(S[i+j−1]xorT[j])⋅fj
这是笔者进行的基础的题面翻译,大佬们是怎么跟 NTT 扯上关系的呢?
我们想,异或的本质是同 0 异 1,互异的情况只有 0,1 和 1,0 (这不废话)
于是考虑这两种情况算贡献,顺便把
i
+
j
−
1
i+j-1
i+j−1 变成
k
         
,
(
k
−
j
=
i
−
1
)
k\;,(k-j=i-1)
k,(k−j=i−1):
a
n
s
i
=
∑
k
−
j
=
i
−
1
f
j
(
[
S
k
=
1
]
[
T
j
=
0
]
+
[
S
k
=
0
]
[
T
j
=
1
]
)
ans_i=\sum_{k-j=i-1}f_j([S_k=1][T_j=0] + [S_k=0][T_j=1])
ansi=k−j=i−1∑fj([Sk=1][Tj=0]+[Sk=0][Tj=1])
其中的
[
S
k
=
1
]
[S_k=1]
[Sk=1] 是个条件表达式,
S
k
=
1
S_k=1
Sk=1 则值为 1,否则为 0,或许这样还是不太明显,我们再变:
a
n
s
i
=
∑
k
−
j
=
i
−
1
[
S
k
=
1
]
⋅
(
f
j
[
T
j
=
0
]
)
+
∑
k
−
j
=
i
−
1
[
S
k
=
0
]
⋅
(
f
j
[
T
j
=
1
]
)
ans_i=\sum_{k-j=i-1}[S_k=1]\cdot (f_j[T_j=0]) + \sum_{k-j=i-1}[S_k=0]\cdot (f_j[T_j=1])
ansi=k−j=i−1∑[Sk=1]⋅(fj[Tj=0])+k−j=i−1∑[Sk=0]⋅(fj[Tj=1])
两个卷积!(只需把数组倒一倒)这该是多么强的直觉才能发现是卷积啊
所以我们就可以在
(
N
+
M
)
log
N
(N+M)\log N
(N+M)logN 的时间求出从每个位置开始、匹配完整条话语的容忍度下降值。
那怎么想到分块的呢?
同样地,我们可以在
(
N
+
B
)
log
N
(N+B)\log N
(N+B)logN 的时间求出从每个位置开始、匹配完话语中一段长为
B
B
B 的块的容忍度下降值。
那么考虑将 S 分
M
B
\frac{M}{B}
BM 个为大小为
B
B
B 的块,从前到后 NTT 计算每个块的卷积,假如某个位置已经不能再匹配完这个块(容忍度在这个块降到 0 以下),则
O
(
B
)
O(B)
O(B) 地算出它在哪里终止。
大概复杂度在
O
(
N
⋅
M
B
log
N
+
N
⋅
B
)
O(N\cdot\frac{M}{B}\log N+N\cdot B)
O(N⋅BMlogN+N⋅B) 左右,因此理论上
B
=
N
log
N
B=\sqrt{N\log N}
B=NlogN
 最优,考虑暴力常数小,我们就取
B
=
m
a
x
(
M
30
,
1
)
B=max(\frac{M}{30},1)
B=max(30M,1) 就行了,最后还得卡卡常。
CODE(正解)
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 998244353;
int n,m,i,j,s,o,k,B;
int f[MAXN];
char ss[MAXN],s1[MAXN],s2[MAXN<<1];
int QM(int x) {return x>=MOD ? (x-MOD) : x;}
int qkpow(int a,int b) {
	int res = 1;
	while(b > 0) {
		if(b & 1) res = res *1ll* a % MOD;
		a = a *1ll* a % MOD;b >>= 1;
	}return res;
}
int xm[MAXN<<2],rev[MAXN<<2],om;
void NTT(int *s,int n,int op) {
	for(int i = 1;i < n;i ++) {
		rev[i] = ((rev[i>>1]>>1) | ((i & 1) ? (n>>1):0));
		if(rev[i] < i) swap(s[rev[i]],s[i]);
	}
	om = qkpow(3,(MOD-1)/n); xm[0] = 1;
	if(op < 0) om = qkpow(om,MOD-2);
	for(int i = 1;i <= n;i ++) xm[i] = xm[i-1] *1ll* om % MOD;
	for(int k = 2,t=(n>>1);k <= n;k<<=1,t>>=1) {
		for(int j = 0;j < n;j += k) {
			for(int i = j,l = 0;i < j+(k>>1);i ++,l += t) {
				int A = s[i],B = s[i+(k>>1)];
				s[i] = (A + B *1ll* xm[l] % MOD) % MOD;
				s[i+(k>>1)] = (A +MOD- B *1ll* xm[l] % MOD) % MOD;
			}
		}
	}
	if(op < 0) {
		int invn = qkpow(n,MOD-2);
		for(int i = 0;i < n;i ++) s[i] = s[i] *1ll* invn % MOD;
	}return ;
}
int kk[MAXN],rl[MAXN];
int aa[MAXN<<2],bb[MAXN<<2];
int C[MAXN<<2],D[MAXN<<2];
int rpos(int pos,int st,int kk) {
	for(int j = st;j <= m && pos <= n;j ++) {
		if(ss[pos] != s1[j]) kk -= f[j];
		if(kk < 0) break;
		pos ++;
	}
	return pos;
}
int main() {
	freopen("mata.in","r",stdin);
	freopen("mata.out","w",stdout);
	n = read(); m = read(); k = read();
	B = max(1,m/30);
	scanf("%s",ss + 1);
	scanf("%s",s1 + 1);
	LL sm = 0;
	for(int i = 1;i <= m;i ++) f[i] = read(),sm += f[i];
	if(k >= sm) {
		for(int i = 1;i <= n;i ++) {
			printf("%d ",min(n+1,i+m));
		}ENDL;
		return 0;
	}
	int le = 1; while(le <= n+B) le <<= 1;
	for(int i = 1;i <= n;i ++) {
		kk[i] = k;
		aa[i] = (ss[n-i+1] == '1' ? 1:0);
		bb[i] = aa[i] ^ 1;
	}
	NTT(aa,le,1); NTT(bb,le,1);
	for(int i = 1;i <= m;i += B) {
		int rr = min(i+B-1,m);
		for(int j = 0;j <= le;j ++) C[j]=D[j]=0;
		for(int j = 1,j2 = i;j2 <= rr;j2 ++,j ++) {
			C[j] = (s1[j2] == '1' ? f[j2]:0);
			D[j] = f[j2] - C[j];
		}
		NTT(C,le,1);NTT(D,le,1);
		for(int j = 0;j < le;j ++) C[j] = C[j] *1ll* bb[j] % MOD,D[j] = D[j] *1ll* aa[j] % MOD;
		NTT(C,le,-1);NTT(D,le,-1);
		for(int j = 1;j <= n+1-i;j ++) {
			int ad = n-j+3-i;
			if(!rl[j]) {
				if(kk[j] - C[ad] - D[ad] < 0) {
					rl[j] = rpos(j+i-1,i,kk[j]);
				}
				kk[j] -= C[ad]+D[ad];
			}
		}
	}
	for(int i = 1;i <= n;i ++) {
		if(kk[i] >= 0) rl[i] = min(n+1,i+m);
		printf("%d ",rl[i]);
	}ENDL;
	return 0;
}
彩蛋
为什么我会得 50 pts 呢?我看了下结果,有 40 的有 60 的,就我和一个正解打爆的人得了 50 。
我过了最后一个点,0.2 s,正解跑 1.5 s。
这是因为我特判了一种情况:当
K
≥
∑
f
i
K\geq \sum f_i
K≥∑fi 时,每个位置都可以随便匹配,因此对于位置
i
i
i 只用输出
min
(
i
+
M
,
N
+
1
)
\min(i+M,N+1)
min(i+M,N+1) ,如果是暴力的话,这个点可以卡掉那些认为 K 很小可以中途退出的人,也可以卡掉常数不是很优的正解,关键是可以卡掉边界处理不好的人。
但我把它特判了 。
@偶耶XJX !快更新《出题人心理学》!有新点子!
50 pts 代码(可以拍拍后缀数组+rmq):
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
int f[MAXN];
char ss[MAXN],s1[MAXN],s2[MAXN<<1];
int sa[MAXN<<1],rk[MAXN<<1],ht[MAXN<<1],h[MAXN<<1];
int nx[MAXN<<1],hd[MAXN<<1],tl[MAXN<<1],pr[MAXN<<2];
int ins(int i,int x) {return hd[i]==0 ? (hd[i]=x):(nx[tl[i]]=x);}
void INIT_SA(char *s,int *sa,int *rk,int n) {
	for(int i=1;i<=n;i++)sa[i]=rk[i]=nx[i]=hd[i]=tl[i]=pr[i+n]=0;
	for(int i = 1;i <= n;i ++) {
		nx[tl[(int)s[i]] = ins((int)s[i],i)] = 0;
	}
	int cn = 0,nm = 0;
	for(int i = 0;i <= 256;i ++) {
		int p = hd[i];
		if(p) nm ++;
		while(p) {
			sa[++ cn] = p;
			rk[p] = nm;
			if(p == tl[i]) break;
			p = nx[p];
		} hd[i] = tl[i] = 0;
	}
	for(int ii = 1;ii < n;ii <<= 1) {
		for(int i = 1;i <= n;i ++) pr[i] = rk[i],rk[i]=0;
		for(int i = n-ii+1;i <= n;i ++) {
			nx[tl[pr[i]] = ins(pr[i],i)] = 0;
		}
		for(int i = 1;i <= n;i ++) {
			if(sa[i] <= ii) continue;
			nx[tl[pr[sa[i]-ii]] = ins(pr[sa[i]-ii],sa[i]-ii)] = 0;
		}
		int cn = 0,nm = 0;
		for(int i = 1;i <= n;i ++) {
			int p = hd[i],pp = 0;
			while(p) {
				sa[++ cn] = p;
				rk[p] = (!pp || pr[p+ii] != pr[pp+ii] ? (++nm):nm);
				if(p == tl[i]) break;
				pp = p; p = nx[p];
			} hd[i] = tl[i] = 0;
		}
	}return ;
}
void INIT_HEIGHT(char *s,int *sa,int *rk,int *hi,int n) {
	hi[0] = 0; sa[0] = 0;
	for(int i = 1;i <= n;i ++) {
		int k = sa[rk[i]-1];if(!k) {hi[i]=0;continue;}
		hi[i] = max(0,hi[i-1]-1);
		while(s[i+hi[i]] == s[k+hi[i]]) hi[i] ++;
	}return ;
}
int dp[MAXN<<1][20]; // 18
int hb[MAXN<<1];
int lcp(int l,int r) {
	if(l > r) swap(l,r);
	if(l == r) return n+m;
	int le = hb[r-l];
	return min(dp[l+1][le],dp[r-(1<<le)+1][le]);
}
int main() {
	freopen("mata.in","r",stdin);
	freopen("mata.out","w",stdout);
	n = read(); m = read(); k = read();
	scanf("%s",ss + 1);
	scanf("%s",s1 + 1);
	LL sm = 0;
	for(int i = 1;i <= m;i ++) f[i] = read(),sm += f[i];
	bool flag1 = 1;
	for(int i = 1;i <= n;i ++) if(ss[i] != '1') flag1 = 0;
	for(int i = 1;i <= m;i ++) if(s1[i] != '1') flag1 = 0;
	if(n <= 1000 && m <= 1000) {
		for(int i = 1;i <= n;i ++) {
			int kk = k,pos = i;
			for(int j = 1;j <= m && pos <= n;j ++) {
				if(ss[pos] != s1[j]) kk -= f[j];
				if(kk < 0) break;
				pos ++;
			}
			printf("%d ",pos);
		}ENDL;
		return 0;
	}
	if(flag1 || k >= sm) {
		for(int i = 1;i <= n;i ++) {
			printf("%d ",min(n+1,i+m));
		}ENDL;
		return 0;
	}
	if(k <= 100) {
		for(int i = 1;i <= n;i ++) s2[i] = ss[i];
		for(int i = 1;i <= m;i ++) s2[n+i+1] = s1[i];
		s2[n+1] = '2'; s2[n+m+2] = '3';
		int nle = n+m+1; hb[1] = 0;
		for(int i = 2;i <= nle;i ++) hb[i] = hb[i>>1]+1;
		INIT_SA(s2,sa,rk,nle);
		INIT_HEIGHT(s2,sa,rk,ht,nle);
		for(int i = 1;i <= nle;i ++) dp[i][0] = h[i] = ht[sa[i]];
		for(int i = nle;i > 0;i --) {
			for(int j = 1;j <= 18 && i+(1<<j)-1<=nle;j ++) {
				dp[i][j] = min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
			}
		}
		for(int i = 1;i <= n;i ++) {
			int ad = i-1,le = 0,la = 0;
			int st = 0,kk = k;
			while(la <= m && i-1+la <= n && kk >= 0) {
				le = lcp(rk[ad+1],rk[n+st+2]);
				ad += le+1; la += le+1;
				st += le+1; kk -= f[la];
			}
			printf("%d ",i+la-1);
		}ENDL;
		return 0;
	}
	return 0;
}
JZM 的印象笔记 (卷积,分块)的更多相关文章
- [Tensorflow] Cookbook - Retraining Existing CNNs models - Inception Model
		From: https://github.com/jcjohnson/cnn-benchmarks#alexnet 先大概了解模型,再看如果加载pre-training weight. 关于retai ... 
- Inception in CNN
		之前也写过GoogLeNet的笔记.但那个时候对Inception有些似懂非懂,这周又一次看了一遍,觉得有了新的体会,特地又一次写一篇博客与它再续前缘. 本文属于论文笔记性质.特此声明. Networ ... 
- 从零搭建Pytorch模型教程(三)搭建Transformer网络
		 前言 本文介绍了Transformer的基本流程,分块的两种实现方式,Position Emebdding的几种实现方式,Encoder的实现方式,最后分类的两种方式,以及最重要的数据格式的介绍. ... 
- CC countari & 分块+FFT
		题意: 求一个序列中顺序的长度为3的等差数列. SOL: 对于这种计数问题都是用个数的卷积来进行统计.然而对于这个题有顺序的限制,不好直接统计,于是竟然可以分块?惊为天人... 考虑分块以后的序列: ... 
- CC Arithmetic Progressions (FFT + 分块处理)
		转载请注明出处,谢谢http://blog.csdn.net/ACM_cxlove?viewmode=contents by---cxlove 题目:给出n个数,选出三个数,按下标顺序形成等差数 ... 
- 卷积神经网络的变种: PCANet
		前言:昨天和大家聊了聊卷积神经网络,今天给大家带来一篇论文:pca+cnn=pcanet.现在就让我带领大家来了解这篇文章吧. 论文:PCANet:A Simple Deep Learning Bas ... 
- SSE图像算法优化系列十一:使用FFT变换实现图像卷积。
		本文重点主要不在于FFT的SSE优化,而在于使用FFT实现快速卷积的相关技巧和过程. 关于FFT变换,有很多参考的代码,特别是对于长度为2的整数次幂的序列,实现起来也是非常简易的,而对于非2次幂的序列 ... 
- 狄利克雷卷积&莫比乌斯反演总结
		狄利克雷卷积&莫比乌斯反演总结 Prepare 1.\([P]\)表示当\(P\)为真时\([P]\)为\(1\),否则为\(0\). 2.\(a|b\)指\(b\)被\(a\)整除. 3.一 ... 
- 优化基于FPGA的深度卷积神经网络的加速器设计
		英文论文链接:http://cadlab.cs.ucla.edu/~cong/slides/fpga2015_chen.pdf 翻译:卜居 转载请注明出处:http://blog.csdn.net/k ... 
随机推荐
- 1.数据结构《Pytorch神经网络高效入门教程》Deeplizard
			当移动一个数组或向量时,我们需要一个索引:二维数组/矩阵需要两个索引, 比如说标量是零维张量,数组/向量/矢量是一维张量,矩阵是是二维张量,n维数组是n维张量. 如果我们被告知, 假设有一个张量t, ... 
- springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑)
			springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑) 写在前面:  富文本编辑器,Multi-function Text Editor, 简称 MTE, 是一 ... 
- Java学习-第一阶段-第一节:Java概述
			JAVA概述 Java版本 原网址(https://www.oracle.com/java/technologies/java-se-support-roadmap.html) Oracle 将仅将某 ... 
- Elasticsearch 在地理信息空间索引的探索和演进
			vivo 互联网服务器团队- Shuai Guangying 本文梳理了Elasticsearch对于数值索引实现方案的升级和优化思考,从2015年至今数值索引的方案经历了多个版本的迭代,实现思路从最 ... 
- IP寻址与规划
			一.IP寻址和子网划分 IP地址的主机部分可被分为三种地址:网络地址.主机地址和定向广播地址. 网络地址是网络号中的第一个地址.它用来将网络内的其他所有网段唯一标识为一个网段或广播域.定向广播地址是网 ... 
- NC200211 装备合成
			NC200211 装备合成 题目 题目描述 牛牛有 \({x}\) 件材料 \({a}\) 和 \({y}\) 件材料 \({b}\) ,用 \({2}\) 件材料 \({a}\) 和 \({3}\) ... 
- Django【执行查询】(二)
			官方Django3.2 文档:https://docs.djangoproject.com/en/3.2/topics/db/queries/ 本文大部分内容参考官方3.2版本文档撰写,仅供学习使用 ... 
- mobaxterm会话同步
			前言 之前用过MobaXterm,想不起来为啥不用了.后面主要还是用xshell,最近又在用WindTerm,WindTerm还不错,奈何有不少的Bug,所以又来研究一下MobaXterm 下午摸索了 ... 
- DENIED Redis is running in protected mode because protected mode is enabled
			DENIED Redis is running in protected mode because protected mode is enabled redisson连接错误 Unable to i ... 
- Odoo14 一些好用的开源的模块
			# odoo14中一些好用的开源的模块 1.intero_reload_form 刷新按钮(页面数据刷新,而不是按F5刷新整个页面) 2.ms_magic_button 弹框下拉选项 3.sessio ... 
