1. KMP
情景:给出一个母串和一个字串,求字串在母串中出现的所有位置。
我们定义子串s有一个border串,它是一个非s串的子串t,满足t既是s的前缀也是s的后缀
记住思想:退而求其次。
int ne[1000005];
void solve(){
string s,t;//母串 匹配串
cin>>s>>t;
int n1 = s.length();
int n2 = t.length();
s = "?"+s;
t = "?"+t;
for(int i=2,j=0;i<=n2;i++){
while(j&&t[i]!=t[j+1]) j = ne[j];
if(t[i]==t[j+1]) j++;
ne[i]=j;
}
for(int i=1,j=0;i<=n1;i++){
while(j&&s[i]!=t[j+1])j=ne[j];
if(s[i]==t[j+1])j++;
if(j==n2) cout<<i-n2+1<<endl; //输出字串在母串中出现的位置
}
for(int i=1;i<=n2;i++) cout<<ne[i]<<" ";//输出s2的所有border串的长度
}
kmp的应用
-
[P3435 POI2006] OKR-Periods of Words - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
观察题面对周期的定义我们可以知道它和前后缀关系很紧密。
母串S的一个前缀X,A=X+X能够令S为A的子串。所以我们知道这就是前后缀相同的另一种描述方法,很容易想到kmp对吧
然后我们看要求是需要它这个X的长度尽量长,隐形要求就是令这个前缀尽量短。
但是kmp是找的是S的每一个idx的最长匹配前缀,但是我们在找最长前缀的时候不是有一系列的ne[](这个ne是越来越小的,是一种退而求其次的思想)嘛,所以我们的做法是:
对于每一个idx,我们一直对他求ne,知道ne[j]为0的前一个ne[j]不就是最短匹配长度了嘛,另外,我们找到后,可以直接令ne[idx]=j,也就是记忆化的思想,后面找再退到这个位置,不就可以直接得到最短匹配了吗,也是一种优化方法。最后令ans+=i-j然后进入下一个idx,遍历完整个字符串就好了。
int ne[1000005];
void solve(){
int n1;
cin>>n1;
string t;
cin>>t;
t = "?"+t;
for(int i=2,j=0;i<=n1;i++){
while(j&&t[i]!=t[j+1]) j = ne[j];
if(t[i]==t[j+1]) j++;
ne[i]=j;
}
//以上部分是求kmp做法中求ne数组的部分,和模板是一毛一样的。
LL ans = 0;
for(int i=2,j=2;i<=n1;i++,j=i){
while(ne[j]) j = ne[j];//求最短匹配长度
if(ne[i]) ne[i] = j;//修改ne[i]的值,当后面ne到这个i(指值)的时候,直接得出结果
ans+=i-j;
}
cout<<ans<<endl;
}
2. 最短路
dijkstra方法: 结合链式向前星食用
可以有环 但是只能处理正权边
一开始将所有距离都设置为一个很大的值比如0x3f
P4779 【模板】单源最短路径(标准版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
void dijkstra(int x){
// int a=1;
// debug(a)
memset(dist,0x3f,sizeof dist);
memset(vis,0,sizeof vis);
dist[x] = 0;
priority_queue<PII,std::vector<PII>,greater<PII>>heap;
heap.push({0,x});
while(!heap.empty()){
PII t = heap.top();
heap.pop();
int ver = t.ss,distance = t.ff;
if(vis[ver]) continue; //这个点已经访问过了
vis[ver] = 1;
for(int i=h[ver];i;i = ne[i]){
int j = e[i];
if(dist[j] > distance+w[i]){ //关键部分
dist[j] = distance + w[i]; //用短的路径覆盖长的路径
if(!vis[j]){
heap.push({dist[j],j});
}
}
}
}
for(int i =1;i<=n;i++){
if(i==x) cout<<"0 ";
else if(dist[i] == 0x3f3f3f3f) cout<<INT_MAX<<" ";
else cout<<dist[i]<<" ";
}
}
3. 树状数组
树状数组的作用:对一个区间的数修改及查询,内容有:单点修改,单点查询,区间修改,区间查询
本质上是利用位运算降低时间复杂度到nlogn
add和sum函数是固定的,查询和插入的方法是改变的。
//单点修改:
add(i,k) //i是位置 k是值
//区间修改:i,j
add(i,k);
add(j+1,-k);
//单点查询:k
sum(k);
//区间查询:i,j
sum(j)-sum(i-1);
int n,m;
int a[500005];
//lowbit(x) = x&-x
void add(int x,int num)
{
for(;x<=n;x+=x&-x)
a[x]+=num;
}
int sum(int x) //从下标1到 下标x的 和
{
int ans=0;
for(;x>0;x-=x&-x)
ans+=a[x];
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int k;
cin>>k;
add(i,k);
}
// for(int i=1;i<=n;i++) cout<<a[i]<<" "; 测试代码
// cout<<endl;
while(m--)
{
int cho,x,y;
cin>>cho>>x>>y;
if(cho==1) add(x,y);
else cout<<sum(y)-sum(x-1)<<endl;
}
return 0;
}
int n,m;
int a[500005],b[500005];
void add(int x,int num)
{
for(;x<=n;x+=x&-x) b[x]+=num;
}
int sum(int x)
{
int ans=0;
for(;x>0;x-=x&-x) ans+=b[x];
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
// b[i]=a[i]-a[i-1];
add(i,a[i]-a[i-1]);
}
while(m--)
{
int cho,x,y,k;
cin>>cho;
if(cho==1)
{
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
else
{
int k;cin>>k;
// int tmp=sum(k)-sum(k-1);
cout<<sum(k)<<endl;
}
}
return 0;
}
注意,单点修改+区间查询 和 区间查询+单点修改 所使用的是不一样的树状数组
前者使用的是前缀和的思想,修改的时候直接修改对应的值,查询的时候使用大范围减小范围得到一个区间的和
后者使用的是差分的思想,修改的时候使用区间修改的思想,查询到时候用前缀和的思想,因为sum就是前缀和的思想
但是为什么不直接用前缀和和差分呢?
因为它有最多n次查询,差分后查询每次还是需要至少n的复杂度的,这样会导致总复杂度为n2
一个方法就是如果是多次修改,最后一次查询,直接使用裸差分,
如果是多次的修改穿插查询,那么用树状数组吧。
另:区间修改+区间查询还是可以用树状数组的。
#include<iostream>
#include<cstdio>
#define MAXN 1000010
using namespace std;
int n,q,op,l,r,x;
int a[MAXN];
int c1[MAXN],c2[MAXN];
int lowbit(int x)
{
return x&-x;
}
void update(int x,int k)
{
int t=x;
for(;x<=n;x+=lowbit(x))
{
c1[x]+=k;
c2[x]+=k*(t-1);
}
}
int query(int x)
{
int ans=0,t=x;
for(;x>=1;x-=lowbit(x))
ans+=c1[x]*t-c2[x];
return ans;
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
update(i,a[i]-a[i-1]);
}
for(int i=1;i<=q;i++)
{
scanf("%d%d%d",&op,&l,&r);
if(op==1)
{
scanf("%d",&x);
update(l,x);
update(r+1,-x);
}
else
printf("%d\n",query(r)-query(l-1));
}
return 0;
}
另:有一个网上摘抄的树状数组解决逆序对的问题,但是还没有考证
[树状数组求逆序对 - wdvxdr - 博客园 (cnblogs.com)](https://www.cnblogs.com/wdvxdr/p/7300717.html#:~:text=树状数组就可以很好的做到这一点。 我们可以先开一个大小为a的最大值的数组t,每当读入一个数时,我们可以用桶排序的思想,将t [a [i]]加上1%2C然后我们统计t,~t [a [i]]的和ans,ans - 1(除掉这个数本身)就是在这个数前面有多少个数比它小。)
4. SPFA
这个算法可以处理负边权、负环的情况。
5. floyd
动态规划入门(
n3复杂度处理数量级为100的每个点对其他点的最短距离
也就是:任意点对的最短距离之和
[P2910 USACO08OPEN] Clear And Present Danger S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
void floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
mp[i][j] = min(mp[i][j],mp[i][k] + mp[k][j]);
}
}
}
}
利用folyd可以扩展出的问题
1. 求传递闭包
B3611 【模板】传递闭包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
void floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
mp[i][j] |= mp[i][k] & mp[k][j];
}
}
}
}
/*
4
0 0 0 1
1 0 0 0
0 0 0 1
0 1 0 0
|
|
v
1 1 0 1
1 1 0 1
1 1 0 1
1 1 0 1
*/
2. 传送门:复杂度太高怎么办?
[P6464 传智杯 #2 决赛] 传送门 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
详见提交代码和题解。
标签:int,ne,void,add,笔记,查询,2024,算法,ans From: https://www.cnblogs.com/Akimizuss101/p/18002963