头文件
#include<bits/stdc++.h>
#define intn long long
#define ls(k) (k)<<1
#define inf 2147483647
#define re register
#define rs(k) (k)<<1|1
#define _0for(i, a) for(int i = 0; i < (a); ++i)
#define _1for(i, a) for(int i = 1; i <=(a); ++i)
#define lowbit(x) ((x)&(-x))
#define debug(x) \
(void)(cerr << "L" << __LINE__\
<< " : " << #x << " = " \
<< (x) << endl )
using namespace std;
inline int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int ninf = 0xc0c0c0c0;
const int N = 100001;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
}
图论
拓扑排序
统计入度,入度为0入栈,递推并更新入度,入度为0入栈,栈空结束
queue<int>q;
int tot=0;
for(int i=1;i<=numb;i++)
{
if(!ind[i])
{
q.push(i);
dist[i]=p[i];
}
}
while(!q.empty())
{
int k=q.front();q.pop();
for(int i=0;i<g1[k].size();i++)
{
int v=g1[k][i];
dist[v]=max(dist[v],dist[k]+p[v]);
ind[v]--;
if(ind[v]==0)q.push(v);
}
}
缩点与割点
缩点
void tarjan(int x)
{
dfn[x]=low[x]=++num;
ins[x]=1;
st.push(x);
for(int i=0;i<g[x].size();i++)
{
int q=g[x][i];
if(dfn[q]==0)
{
tarjan(q);
low[x]=min(low[q],low[x]);
}
else if(ins[q]==1)
{
low[x]=min(low[x],dfn[q]);
}
}
if(dfn[x]==low[x])
{
numb++;
int p;
do
{
p=st.top();
st.pop();
bl[p]=numb;
nums[numb]++;
ins[p]=0;
}
while(x!=p);
}
}
最短路
djs
#include<bits/stdc++.h>
#define re register
using namespace std;
struct edge
{
int to,cost;
};
vector<edge>g[500005];//定义路径结构体
int n,m,s;
int dis[500005];
struct node//定义堆结构体
{
//(如果看不懂)https://www.cnblogs.com/ZERO-/p/9347296.html
int u,d;
bool operator<(const node&rhs)const
{
return d>rhs.d;
}
};
inline void djs()
{
for(re int i=1;i<=n;i++)dis[i]=2147483647;
dis[s]=0;
priority_queue<node>Q;//初始化
node a ={s,0};
Q.push(a);//第一个node
while(!Q.empty())
{
node fr=Q.top();Q.pop();
int u=fr.u,d=fr.d;
//取出并记录
if(d!=dis[u])continue;//避免处理无用数据,也就是dis[u]已经更新,之前未更新数据直接出栈,比如有一组数据 2 5,但是后面又入栈一组数据2 3,则2 5是无用数据
for(re int j=0;j<g[u].size();j++)
{
int tm=g[u][j].to;
if(dis[u]+g[u][j].cost<dis[tm])
{
dis[tm]=dis[u]+g[u][j].cost;
Q.push((node){tm,dis[tm]});
}
}
}
}
int main()
{
cin>>n>>m>>s;
int x;
for (re int i=1;i<=m;i++)
{
edge tmp;
cin>>x>>tmp.to>>tmp.cost;
g[x].push_back(tmp);
}
djs();
for (re int i=1;i<=n;i++)
printf("%d ",dis[i]);
return 0;
}
spfa
struct edge
{
int v,w;
};
vector<edge>g[10001];
int d[10001];
int times[10001];
bool vis[10001];
queue<int>q;
int n;
void spfa(int s)
{
memset(vis,0,sizeof(vis));
memset(d,inf,sizeof(d));
memset(times,0,sizeof(times));
while(!q.empty())q.pop();
q.push(s);
vis[s]=1;
d[s]=0;
times[s]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
for(int j=0;j<g[now].size();j++)
{
edge p=g[now][j];
if(d[p.v]>d[now]+p.w)
{
d[p.v]=d[now]+p.w;
times[p.v]++;
debug(times[p.v]);
if(times[p.v]>n)
{
puts("YES");
return;
}
if(!vis[p.v])
{
q.push(p.v);
vis[p.v]=1;;
}
}
}
}
puts("NO");
}
main(void)
{
int t=read(),m;
for(re int i=1;i<=t;i++)
{
n=read();
m=read();
int u,v,w;
for(re int j=1;j<=m;j++)
{
edge t;
u=read(),v=read(),w=read();
t.v=v;
t.w=w;
g[u].push_back(t);
t.v=u;
if(w>=0)g[v].push_back(t);
}
spfa(1);
for(int j=1;j<=n;j++)
g[j].clear();
}
}
二叉树
已知前序中序
char c[100000];
int cnt;
int l;
string a,b;
void ss(int l1,int r1,int l2,int r2)
{
if(l1>r1||l2>r2)return ;
cnt--;//不断地找根
c[cnt]=b[l2];
for(int i=l1;i<=r1;i++)
{
if(a[i]==b[l2])//中序中的根
{
int lr=r1-i;
int ll=i-l1;
ss(i+1,r1,l2+1+ll,r2);//搜右
ss(l1,i-1,l2+1,r2-lr);//搜左
}
}
}
main(void)
{
cin>>a>>b;//a中,b前
l=cnt=a.length();
ss(0,cnt-1,0,cnt-1);
for(int i=0;i<l;i++)
{
cout<<c[i];
}
}
已知前序后序求中序的可能
char a[10002],b[10002];
int main()
{
scanf("%s%s",&a,&b);
int len=strlen(a),ans=1;
for(int i=0;i<=len-2;i++)
for(int j=0;j<=len-1;j++)
if(b[j]==a[i]&&b[j-1]==a[i+1]);
cout<<(1<<(ans+1));
return 0;
}
并查集
食物链这道题是要求维护
x
x
x吃
y
y
y,
y
y
y吃
z
z
z,
z
z
z吃
x
x
x的关系,那么我们将并查集扩大三倍,表示三个族群,则可以得到相互的关系
(
x
,
y
)
(x,y)
(x,y)同类,
(
x
,
y
+
n
)
(x,y+n)
(x,y+n),x吃y,
(
x
+
n
,
y
+
2
n
)
(x+n,y+2n)
(x+n,y+2n),x吃y,
(
x
+
2
n
,
y
)
(x+2n,y)
(x+2n,y)x吃y
种类并查集
int f[150005];
int find(int x)
{
if(f[x]==x)return x;
f[x]=find(f[x]);
return f[x];
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
f[fx]=fy;
}
main(void)
{
int n,k,jia=0;
int x,y,cmd;
n=read();
k=read();
for(int i=1;i<=3*n;i++)f[i]=i;
for(int i=1;i<=k;i++)
{
cmd=read();
x=read();
y=read();
if(x>n||y>n)jia++;
else if(cmd==2&&(x==y||find(x)==find(y)||find(y)==find(x+n)))jia++; //x、y是同类或者y吃x 不可以
else if(cmd==1&&(find(x)==find(y+n)||find(y)==find(x+n)))jia++;//x吃y或者y吃x 不可以
else
{
if(cmd==1)
{
merge(find(x),find(y));
merge(find(x+n),find(y+n));
merge(find(x+2*n),find(y+2*n));
}
else
{
merge(find(x),find(y+n));
merge(find(x+n),find(y+2*n));
merge(find(x+2*n),find(y));
}
}
}
cout<<jia;
}
权值并查集
const int maxn=50010;
int f[maxn];
int rf[maxn];
//rex[x]是x与祖先f[x]的关系
//0同类,1吃,2被吃
int find(int x)
{
if(f[x]==x)return x;
int t=f[x];
f[x]=find(f[x]);
rf[x]=(rf[x]+rf[t])%3;
return f[x];
}
void merge(int x,int y,int re)
{
f[x]=y;//必须更新到祖先再使用这个函数,否则会顶掉原f[x]
rf[x]=re;
}
main(void)
{
int n,k,jia=0;
n=read();
k=read();
_1for(i,n)f[i]=i;
_1for(p,k)
{
int re,x,y;
re=read();
x=read();
y=read();
if(x>n||y>n)jia++;
else if(re==2&&x==y)jia++;
else if(find(x)==find(y))//已经有了关系
{
if(re-1!=(rf[x]-rf[y]+3)%3)jia++;//向量运算的思路
}
else
{
int newre=(re-1+rf[y]-rf[x]+3)%3;
merge(find(x),find(y),newre);
}
}
cout<<jia;
}
最小生成树
k算法
int f[1000];
int ans=0;
int ansm=0;
struct node
{
int u,v,w;
}a[100005];
bool cmp(node a,node b)
{
return a.w<b.w;
}
int find(int x)
{
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
main(void)
{
int n,m;
cin>>n>>m;
_1for(i,n)f[i]=i;
_1for(i,m)
{
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a+1,a+1+m,cmp);
int cnt=0;
_1for(i,m)
{
if(cnt==n-1)break;
int x=find(a[i].u);
int y=find(a[i].v);
if(x!=y)
{
f[x]=y;
ans+=a[i].w;
cnt++;
ansm=max(ansm,a[i].w);
}
}
cout<<n-1<<" "<<ansm;
}
p算法
const int inf=999999;
int mp[400][400];
int n;
int visit[400];
int dist[400];
int ans=0;
int prime(int cur)
{
int index;
int sum=0;
visit[cur]=1;
_1for(i,n)dist[i]=mp[cur][i];
for(int i=1;i<=n-1;i++)
{
int mincost=inf;
_1for(j,n)
{
if(!visit[j]&&dist[j]<mincost)
{
mincost=dist[j];
index=j;
}
}
debug(index);
visit[index]=1;
sum+=mincost;
ans=max(ans,mincost);
_1for(j,n)
{
if(!visit[j]&&dist[j]>mp[index][j])
{
dist[j]=mp[index][j];
}
}
}
return sum;
}
main(void)
{
int m,x,y,z;
cin>>n>>m;
_1for(i,n)
_1for(j,n)
mp[i][j]=inf;
_1for(i,m)
{
cin>>x>>y>>z;
mp[x][y]=z;
mp[y][x]=z;
}
prime(1);
cout<<n-1<<" "<<ans;
}
最大二分图匹配
给定一个二分图,其左部点的个数为 n,右部点的个数为 m,边数为 e,求其最大匹配的边数。
左部点从 1 至 n 编号,右部点从 1 至 m 编号。
const int maxn=1005;
int n,m,t;
int mch[maxn],vis[maxn];
vector<int >g[maxn];
bool dfs(int u,int tag)
{
if(vis[u]==tag)return false;//应该是避免绕圈
vis[u]=tag;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(mch[v]==0||dfs(mch[v],tag))//如果这个点未被匹配或者原先匹配的点可以找到别的匹配点
{
mch[v]=u;
return true;
}
}
return false;
}
main(void)
{
n=read();
m=read();
t=read();
while(t--)
{
int u=read();
int v=read();
g[u].push_back(v);
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(dfs(i,i))
{
ans++;
}
}
cout<<ans;
}
数论
积性函数指对于所有互质的整数a和b有性质f(ab)=f(a)f(b)的数论函数。
狄利克雷卷积
(
f
∗
g
)
(
n
)
=
∑
d
∣
n
f
(
d
)
g
(
n
d
)
(f * g)(n)=\sum_{d \mid n} f(d) g\left(\frac{n}{d}\right)
(f∗g)(n)=∑d∣nf(d)g(dn)
性质1:
两个积性函数的狄利克雷卷积还是积性函数。
证明:感性理解一下,两个互质的下标乘起来,相当于两个和式乘起来,因为是互质的相当于枚举到了乘积的所有约数,因为和式里面都是积性函数所以乘起来也是积性函数。
性质2:
三大运算规律:结合律,交换律,分配律。
常见运算
除数函数:
σ
x
(
n
)
=
∑
d
∣
n
d
x
\sigma_{x}(n)=\sum_{d \mid n} d^{x}
σx(n)=∑d∣ndx
约数个数函数:
d
(
n
)
=
∑
d
∣
n
1
d(n)=\sum_{d \mid n} 1
d(n)=∑d∣n1 约数和函数:
σ
(
n
)
=
∑
d
n
d
\sigma(n)=\sum_{d n} d
σ(n)=∑dnd
元函数:
e
(
n
)
=
[
n
=
1
]
(
{
1
,
0
,
0
,
0
,
0
…
.
.
.
}
)
e(n)=[n=1](\{1,0,0,0,0 \ldots . . .\})
e(n)=[n=1]({1,0,0,0,0…...})
恒等函数:
I
(
n
)
=
1
(
{
1
,
1
,
1
,
1
,
1
…
…
}
)
I(n)=1(\{1,1,1,1,1 \ldots \ldots\})
I(n)=1({1,1,1,1,1……})
单位函数:
ε
(
n
)
=
n
(
{
1
,
2
,
3
,
4
,
5
…
…
}
)
\varepsilon(n)=n(\{1,2,3,4,5 \ldots \ldots\})
ε(n)=n({1,2,3,4,5……})
欧拉函数:
ϕ
(
n
)
\phi(n)
ϕ(n) 莫比乌斯函数:
μ
(
n
)
\mu(n)
μ(n)
μ ( i ) = { 1 , i = 1 ( − 1 ) k , i = p 1 ∗ p 2 ∗ … ∗ p k 0 , rest \mu(i)=\left\{\begin{array}{c}1, i=1 \\ (-1)^{k}, i=p 1 * p 2 * \ldots * p k \\ 0, \text { rest }\end{array}\right. μ(i)=⎩⎨⎧1,i=1(−1)k,i=p1∗p2∗…∗pk0, rest
ε
=
ϕ
∗
I
⇔
n
=
∑
d
∣
n
ϕ
(
d
)
\varepsilon=\phi * I \Leftrightarrow n=\sum_{d \mid n} \phi(d)
ε=ϕ∗I⇔n=∑d∣nϕ(d)
d
=
I
∗
I
⇔
d
(
n
)
=
∑
d
∣
n
1
d=I * I \Leftrightarrow d(n)=\sum_{d \mid n} 1
d=I∗I⇔d(n)=∑d∣n1
σ
=
ε
∗
I
⇔
σ
(
n
)
=
∑
d
∣
n
d
\sigma=\varepsilon * I \Leftrightarrow \sigma(n)=\sum_{d \mid n} d
σ=ε∗I⇔σ(n)=∑d∣nd
e
=
I
∗
μ
⇔
[
n
=
=
1
]
=
∑
d
∣
n
μ
(
d
)
e=I * \mu \Leftrightarrow[n==1]=\sum_{d \mid n} \mu(d)
e=I∗μ⇔[n==1]=∑d∣nμ(d)
ϕ
=
ε
∗
μ
⇔
ϕ
(
n
)
∑
d
∣
n
μ
(
d
)
∗
n
d
\phi=\varepsilon * \mu \Leftrightarrow \phi(n) \sum_{d \mid n} \mu(d) * \frac{n}{d}
ϕ=ε∗μ⇔ϕ(n)∑d∣nμ(d)∗dn
莫比乌斯反演
若
g
(
n
)
=
∑
d
∣
n
f
(
d
)
g(n)=\sum_{d \mid n} f(d)
g(n)=d∣n∑f(d)
则
f
(
n
)
=
∑
d
∣
n
μ
(
d
)
g
(
n
d
)
f(n)=\sum_{d |n} \mu(d) g\left(\frac{n}{d}\right)
f(n)=d∣n∑μ(d)g(dn)
证明:这里需要用到前面提到的性质:
μ
∗
I
=
ϵ
\mu * I=\epsilon
μ∗I=ϵ
给出的条件等价于
g
=
f
∗
I
g=f * I
g=f∗I
所以
g
∗
μ
=
f
∗
I
∗
μ
=
f
∗
ϵ
=
f
g * \mu=f * I * \mu=f * \epsilon=f
g∗μ=f∗I∗μ=f∗ϵ=f 即
g
∗
μ
=
f
g * \mu=f
g∗μ=f 即 结论
杜教筛
g
(
1
)
S
(
n
)
=
∑
i
=
1
n
(
f
∗
g
)
(
i
)
−
∑
i
=
2
n
g
(
i
)
S
(
⌊
n
i
⌋
)
g(1) S(n)=\sum_{i=1}^{n}(f * g)(i)-\sum_{i=2}^{n} g(i) S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)
g(1)S(n)=∑i=1n(f∗g)(i)−∑i=2ng(i)S(⌊in⌋)
求解
S
(
n
)
S(n)
S(n),可以设定
f
f
f和
g
g
g。
(
1
)
μ
(
n
)
(1)\mu(n)
(1)μ(n)前缀和
考虑到莫比乌斯函数的性质
μ
∗
I
=
ϵ
,
\mu * I=\epsilon,
μ∗I=ϵ, 自然想到取
f
=
μ
,
g
=
I
,
f
∗
g
=
ϵ
f=\mu, g=I, f * g=\epsilon
f=μ,g=I,f∗g=ϵ
(
2
)
φ
(2)\varphi
(2)φ 的前缀和
考虑到
φ
\varphi
φ 的性质
φ
∗
I
=
i
d
,
\varphi * I=i d,
φ∗I=id, 取
f
=
φ
,
g
=
I
,
f
∗
g
=
i
d
f=\varphi, g=I, f * g=i d
f=φ,g=I,f∗g=id
(3)
∑
i
=
1
n
φ
(
i
)
⋅
i
\sum_{i=1}^{n} \varphi(i) \cdot i
∑i=1nφ(i)⋅i
令
f
=
φ
⋅
i
d
,
g
=
i
d
,
f=\varphi \cdot i d, g=i d,
f=φ⋅id,g=id, 考虑迪利克雷卷积的形式得到
(
f
∗
g
)
(
n
)
=
∑
d
∣
n
(
φ
(
d
)
⋅
d
)
⋅
(
n
d
)
=
(f * g)(n)=\sum_{d \mid n}(\varphi(d) \cdot d) \cdot\left(\frac{n}{d}\right)=
(f∗g)(n)=∑d∣n(φ(d)⋅d)⋅(dn)=
n
∑
d
∣
n
φ
(
d
)
=
n
2
n \sum_{d \mid n} \varphi(d)=n^{2}
n∑d∣nφ(d)=n2
即
(
f
∗
g
)
(
i
)
=
i
2
(f * g)(i)=i^{2}
(f∗g)(i)=i2
这样就可以快速求得
(
f
∗
g
)
(
i
)
(f * g)(i)
(f∗g)(i) 的前缀和
n
(
n
+
1
)
(
2
n
+
1
)
6
\frac{n(n+1)(2 n+1)}{6}
6n(n+1)(2n+1)
const int N=1000009;
const int mod=1e9+7;
int pre[N];
int phi[N];
int vis[N];
int prime[N];
int _2,_6;
unordered_map<int,int>mp;
void init()
{
phi[1]=1;
int cnt=0;
for(int i=2;i<=N;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt&&prime[j]*i<=N;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
}
int getsum(int n)
{
if(n<=N)return pre[n];
if(mp.count(n))return mp[n];
int res=n*(n+1)%mod*(2*n+1)%mod*_6%mod;
for(int l=2,r;l<=n;l=r+1)
{
r=n/(n/l);
res-=(l+r)*(r-l+1)/2%mod*getsum(n/l)%mod;
}
return mp[n]=res%mod;
}
int qpow(int a,int b,int mod)
{
if(b==0)return 1;
if(b%2==0)
{
int temp=qpow(a,b/2,mod)%mod;
return temp*temp%mod;
}
else
{
return qpow(a,b-1,mod)*a%mod;
}
}
main(void)
{
init();
for(int i=1;i<=N;i++)
{
pre[i]=(pre[i-1]+i*phi[i])%mod;
}
int t=read();
_2=qpow(2,mod-2,mod);//2乘法逆元
_6=qpow(6,mod-2,mod);//6乘法逆元
while(t--)
{
int n=read();
int a=read();
int b=read();
printf("%lld\n",(getsum(n)-1+mod)%mod*_2%mod);
}
}
min_25筛
求2-n质数之和
#include <bits/stdc++.h>
#define int long long
#define LL long long
using namespace std;
const int N = 6000010;
int prime[N], id1[N], id2[N], flag[N], ncnt, m;
LL g[N], sum[N], a[N], T,n;
namespace Min25 {
inline int ID(LL x) {
return x <= T ? id1[x] : id2[n / x];
}
inline LL calc(LL x) {
return x * (x + 1) / 2 - 1;
}
inline LL f(LL x) {
return x;
}
inline void init() {
T = sqrt(n + 0.5);
for (int i = 2; i <= T; i++) {
if (!flag[i]) prime[++ncnt] = i, sum[ncnt] = sum[ncnt - 1] + i;
for (int j = 1; j <= ncnt && i * prime[j] <= T; j++) {
flag[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
for (LL l = 1; l <= n; l = n / (n / l) + 1) {
a[++m] = n / l;
if (a[m] <= T) id1[a[m]] = m; else id2[n / a[m]] = m;
g[m] = calc(a[m]);
}
for (int i = 1; i <= ncnt; i++)
for (int j = 1; j <= m && (LL)prime[i] * prime[i] <= a[j]; j++)
g[j] = g[j] - (LL)prime[i] * (g[ID(a[j] / prime[i])] - sum[i - 1]);
}
inline LL solve(LL x) {
if (x <= 1) return x;
return n = x, init(), g[ID(n)];
}
}
main(void) {
LL t;scanf("%lld",&t);
while(t--)
{
memset(prime,0,sizeof(prime));
memset(prime,0,sizeof(prime));
memset(id1,0,sizeof(prime));
memset(id2,0,sizeof(prime));
memset(flag,0,sizeof(prime));
memset(g,0,sizeof(g));
memset(sum,0,sizeof(g));
memset(a,0,sizeof(g));
ncnt=0;
m=0;
T=0;
n=0;
scanf("%lld", &n);
LL mod;scanf("%lld",&mod);
printf("%lld\n", (Min25::solve(n+1)-2+(3+n+1)*(n-1)/2));
printf("%lld\n", Min25::solve(n));
}
}
矩阵快速幂求数列
矩阵快速幂可以在很短的时间内完成矩阵的运算,在求数列的某一项时有很好的作用。举例:斐波那契数列
a
[
1
]
=
1
a[1]=1
a[1]=1,
a
[
2
]
=
1
a[2]=1
a[2]=1,从第三项开始,
a
[
n
]
=
a
[
n
−
1
]
+
a
[
n
−
2
]
a[n]=a[n-1]+a[n-2]
a[n]=a[n−1]+a[n−2]
可以得到式子
(
a
[
n
]
a
[
n
−
1
]
)
=
(
1
1
1
0
)
(
a
[
n
−
1
]
a
[
n
−
2
]
)
\left( \begin{array}{c} a\left[ n \right]\\ a\left[ n-1 \right]\\ \end{array} \right) =\left( \begin{matrix} 1& 1\\ 1& 0\\ \end{matrix} \right) \left( \begin{array}{c} a\left[ n-1 \right]\\ a\left[ n-2 \right]\\ \end{array} \right)
(a[n]a[n−1])=(1110)(a[n−1]a[n−2])
(
a
[
n
]
a
[
n
−
1
]
)
=
(
1
1
1
0
)
n
−
2
(
a
[
2
]
a
[
1
]
)
n
≥
3
\left( \begin{array}{c} a\left[ n \right]\\ a\left[ n-1 \right]\\ \end{array} \right) =\left( \begin{matrix} 1& 1\\ 1& 0\\ \end{matrix} \right) ^{n-2}\left( \begin{array}{c} a\left[ 2 \right]\\ a\left[ 1 \right]\\ \end{array} \right) \ \ \ n\ge 3
(a[n]a[n−1])=(1110)n−2(a[2]a[1]) n≥3
下面考虑变式的情况,
a
[
1
]
=
a
a[1]=a
a[1]=a,
a
[
2
]
=
b
a[2]=b
a[2]=b,
a
[
n
]
=
2
∗
a
[
n
−
1
]
+
a
[
n
−
2
]
+
n
3
a[n]=2*a[n-1]+a[n-2]+n^3
a[n]=2∗a[n−1]+a[n−2]+n3 , 当
n
>
2
n>2
n>2
i
4
=
i
4
+
4
n
3
+
6
n
2
+
4
n
+
1
;
i^4=i^4+4n^3+6n^2+4n+1;
i4=i4+4n3+6n2+4n+1;
(
a
[
n
]
a
[
n
+
1
]
(
n
+
1
)
4
(
n
+
1
)
3
(
n
+
1
)
2
(
n
+
1
)
1
)
=
(
0
1
0
0
0
0
0
2
1
1
4
6
4
1
0
0
1
4
6
4
1
0
0
0
1
3
3
1
0
0
0
0
1
2
1
0
0
0
0
0
1
1
0
0
0
0
0
0
1
)
(
a
[
n
−
1
]
a
[
n
]
n
4
n
3
n
2
n
1
)
\left( \begin{array}{l} a\left[ n \right]\\ a\left[ n+1 \right]\\ \left( n+1 \right) ^4\\ \left( n+1 \right) ^3\\ \left( n+1 \right) ^2\\ \left( n+1 \right)\\ 1\\ \end{array} \right) =\left( \begin{matrix} 0& 1& 0& 0& 0& 0& 0\\ 2& 1& 1& 4& 6& 4& 1\\ 0& 0& 1& 4& 6& 4& 1\\ 0& 0& 0& 1& 3& 3& 1\\ 0& 0& 0& 0& 1& 2& 1\\ 0& 0& 0& 0& 0& 1& 1\\ 0& 0& 0& 0& 0& 0& 1\\ \end{matrix} \right) \left( \begin{array}{l} a\left[ n-1 \right]\\ a\left[ n \right]\\ n^4\\ n^3\\ n^2\\ n\\ 1\\ \end{array} \right)
⎝⎜⎜⎜⎜⎜⎜⎜⎜⎛a[n]a[n+1](n+1)4(n+1)3(n+1)2(n+1)1⎠⎟⎟⎟⎟⎟⎟⎟⎟⎞=⎝⎜⎜⎜⎜⎜⎜⎜⎜⎛0200000110000001100000441000066310004432100111111⎠⎟⎟⎟⎟⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎜⎜⎜⎜⎛a[n−1]a[n]n4n3n2n1⎠⎟⎟⎟⎟⎟⎟⎟⎟⎞
(
a
[
n
]
a
[
n
+
1
]
(
n
+
1
)
4
(
n
+
1
)
3
(
n
+
1
)
2
(
n
+
1
)
1
)
=
(
0
1
0
0
0
0
0
2
1
1
4
6
4
1
0
0
1
4
6
4
1
0
0
0
1
3
3
1
0
0
0
0
1
2
1
0
0
0
0
0
1
1
0
0
0
0
0
0
1
)
n
−
1
(
a
[
1
]
a
[
2
]
1
6
8
4
2
1
)
\left( \begin{array}{l} a\left[ n \right]\\ a\left[ n+1 \right]\\ \left( n+1 \right) ^4\\ \left( n+1 \right) ^3\\ \left( n+1 \right) ^2\\ \left( n+1 \right)\\ 1\\ \end{array} \right) =\left( \begin{matrix} 0& 1& 0& 0& 0& 0& 0\\ 2& 1& 1& 4& 6& 4& 1\\ 0& 0& 1& 4& 6& 4& 1\\ 0& 0& 0& 1& 3& 3& 1\\ 0& 0& 0& 0& 1& 2& 1\\ 0& 0& 0& 0& 0& 1& 1\\ 0& 0& 0& 0& 0& 0& 1\\ \end{matrix} \right) ^{n-1}\left( \begin{array}{l} a\left[ 1 \right]\\ a\left[ 2 \right]\\ 16^{}\\ 8^{}\\ 4\\ 2\\ 1\\ \end{array} \right)
⎝⎜⎜⎜⎜⎜⎜⎜⎜⎛a[n]a[n+1](n+1)4(n+1)3(n+1)2(n+1)1⎠⎟⎟⎟⎟⎟⎟⎟⎟⎞=⎝⎜⎜⎜⎜⎜⎜⎜⎜⎛0200000110000001100000441000066310004432100111111⎠⎟⎟⎟⎟⎟⎟⎟⎟⎞n−1⎝⎜⎜⎜⎜⎜⎜⎜⎜⎛a[1]a[2]168421⎠⎟⎟⎟⎟⎟⎟⎟⎟⎞
const int mod=2147493647;
struct node
{
int m[10][10];
};
node mul(node a,node b)
{
node ans;
memset(ans.m,0,sizeof(ans.m));
for(int i=1;i<=7;i++)
for(int j=1;j<=7;j++)
{
for(int k=1;k<=7;k++)
{
ans.m[i][j]=(ans.m[i][j]+a.m[i][k]*b.m[k][j]%mod)%mod;
}
}
return ans;
}
node ksm(node a,int b)
{
node res;//单位向量
memset(res.m,0,sizeof(res.m));
for(int i=1;i<=7;i++)
res.m[i][i]=1;
if(b==0)return res;
if(b%2==0)
{
node temp=ksm(a,b/2);
return mul(temp,temp);
}
else
{
return mul(a,ksm(a,b-1));
}
}
main(void)
{
int t=read();
while(t--)
{
int n=read();
node a;
memset(a.m,0,sizeof(a.m));
int b[10][10];
b[1][1]=read();
b[2][1]=read();
b[3][1]=16;
b[4][1]=8;
b[5][1]=4;
b[6][1]=2;
b[7][1]=1;
a.m[1][2]=1;
a.m[2][1]=2,a.m[2][2]=1,a.m[2][3]=1,a.m[2][4]=4,a.m[2][5]=6,a.m[2][6]=4,a.m[2][7]=1;
a.m[3][3]=1,a.m[3][4]=4,a.m[3][5]=6,a.m[3][6]=4,a.m[3][7]=1;
a.m[4][4]=1,a.m[4][5]=3,a.m[4][6]=3,a.m[4][7]=1;
a.m[5][5]=1,a.m[5][6]=2;a.m[5][7]=1;
a.m[6][6]=1,a.m[6][7]=1;
a.m[7][7]=1;
if(n==1)
{
printf("%lld\n",b[1][1]%mod);
}
else if(n==2)
{
printf("%lld\n",b[2][1]%mod);
}
else
{
node index=ksm(a,n-1);
int ans=0;
for(int i=1;i<=7;i++)
ans+=index.m[1][i]*b[i][1];
printf("%lld\n",ans%mod);
/*
for(int i=1;i<=7;i++)
{
for(int j=1;j<=7;j++)
{
printf("%lld ",index.m[i][j]);
}
printf("\n");
}
*/
}
}
}
乘法逆元
费马小定理
递推公式
因为
⌊
p
i
⌋
∗
i
=
p
−
p
m
o
d
i
\lfloor\frac{p}{i}\rfloor*i=p-p\ mod\ i
⌊ip⌋∗i=p−p mod i
即
⌊
p
i
⌋
∗
i
=
−
p
m
o
d
i
\lfloor\frac{p}{i}\rfloor*i=-p\ mod\ i
⌊ip⌋∗i=−p mod i
因此
i
−
1
=
(
−
p
m
o
d
i
)
−
1
∗
⌊
p
i
⌋
i^{-1}=(-p\ mod\ i)^{-1}*\lfloor\frac{p}{i}\rfloor
i−1=(−p mod i)−1∗⌊ip⌋
int ans[1000]={0,1};
main(void)
{
int n,p;
cin>>n>>p;
printf("1\n");
for(int i=2;i<=n;i++)
{
ans[i]=(p-(p/i))*ans[p%i]%p;
}
cout<<ans[666]<<endl;
}
扩展欧几里得
( a , b ) = a x + b y (a,b)=ax+by (a,b)=ax+by
int exgcd(int a, int b, int& x, int& y) {
if(a < b) return exgcd(b, a, y, x);
if(b == 0) {
x = 1; y = 0;
return a;
} else {
int x1;
int d = exgcd(b, a % b, x1, x);
y = x1 - a / b * x;
return d;
}
}
大数
高精度加
string add(string str1,string str2)
{
string str;
int len1=str1.length();
int len2=str2.length();//得到长度
if(len1<len2)//短的补0
{
for(int i=1;i<=len2-len1;i++)
str1="0"+str1;
}
else
{
for(int i=1;i<=len1-len2;i++)
str2="0"+str2;
}
len1=str1.length();
int cf=0;
int temp;
for(int i=len1-1;i>=0;i--)//从第一位开始相加
{
temp=str1[i]-'0'+str2[i]-'0'+cf;
cf=temp/10;//储存进位
temp%=10;//储存当前位
str=char(temp+'0')+str;//构造答案str
}
if(cf!=0)str=char(cf+'0')+str;
return str;
}
高精减
string add(string str1,string str2)
{
string str;
int len1=str1.length();
int len2=str2.length();//得到长度
if(len1<len2)//短的补0
{
for(int i=1;i<=len2-len1;i++)
str1="0"+str1;
}
else
{
for(int i=1;i<=len1-len2;i++)
str2="0"+str2;
}
len1=str1.length();
int cf=0;
int temp;
for(int i=len1-1;i>=0;i--)//从第一位开始相加
{
temp=str1[i]-'0'+str2[i]-'0'+cf;
cf=temp/10;//储存进位
temp%=10;//储存当前位
str=char(temp+'0')+str;//构造答案str
}
if(cf!=0)str=char(cf+'0')+str;
return str;
}
a
a
a 与
m
m
m 互质时,
a
φ
(
m
)
≡
1
m
o
d
m
a^{\varphi(m)} \equiv 1 \quad \bmod m
aφ(m)≡1modm
φ
(
x
)
=
x
∏
i
=
1
n
(
1
−
1
p
i
)
\varphi(x)=x \prod_{i=1}^{n}\left(1-\frac{1}{p_{i}}\right)
φ(x)=x∏i=1n(1−pi1)
a
b
m
o
d
m
a^{b} \ mod \ m
ab mod m
b
≥
φ
(
m
)
b \geq \varphi(m)
b≥φ(m) 时
,
a
b
≡
a
(
b
m
o
d
φ
(
m
)
)
+
φ
(
m
)
m
o
d
m
, a^{b} \equiv a^{(b \bmod \varphi(m))+\varphi(m)} \bmod m
,ab≡a(bmodφ(m))+φ(m)modm
string s;
int m;
int qpow(int a,int n)
{
if(n==0)return 1;
else if(n%2==1)
return qpow(a,n-1)*a%m;
else
{
int temp=qpow(a,n/2)%m;
return temp*temp%m;
}
}
int phi(int n)
{
int ans=n;
for(int i=2;i<=sqrt(n);i++)
{
if(n%i==0)
{
ans=ans/i*(i-1);//这是一个p,注意先除再乘,防止炸int
while(n%i==0) n/=i; //把质数次方因子筛没了,就不会错了
}
}
if(n>=2) ans=ans/n*(n-1);//最后有可能剩下
return ans;
}
main(void)
{
int a;
cin>>a>>m>>s;
int l=s.size();
int b=0;
int flag=0;
int ph=phi(m);
// debug(ph);
for(int i=0;i<l;i++)
{
b=b*10+s[i]-'0';
if(b>=ph)
{
flag=1;
b%=ph;
}
// debug(b);
}
if(flag)
{
b+=ph;
}
int ans=1;
cout<<qpow(a,b);
}
阶乘预处理与快速幂
阶乘预处理
void init(){
int i;
fac[0]=1;
inv[0]=1;
fac[1]=1;
inv[1]=1;
for (i=2;i<50010;i++){
fac[i]=((fac[i-1]*i)%inf+inf)%inf;
inv[i]=(qpow(fac[i],inf-2)+inf)%inf;
}
}
阶乘
C
n
m
=
n
!
m
!
(
n
−
m
)
!
C_{n}^{m}=\frac{n!}{m!\left( n-m \right) !}
Cnm=m!(n−m)!n!
int C(int n,int m){
if (n<m||m<0) return 0;
return (fac[n]*inv[m]%inf*inv[n-m]%inf+inf)%inf;
}
搜索
一个网格与其周围的八个网格相连,而一组相连的网格视为一个水坑。约翰想弄清楚他的田地已经形成了多少水坑。给出约翰田地的示意图,确定当中有多少水坑。
inline void bfs(int a,int b)
{
queue<node>q;
node t;
t.x=a;t.y=b;
q.push(t);
flag[a][b]=cnt;//标记
while(!q.empty())
{
t=q.front();
q.pop();
// debug(ans);
for(int i=1;i<=8;i++)
{
int nx=t.x+xx[i];
int ny=t.y+yy[i];
if(nx<1||ny<1||nx>n||ny>m||flag[nx][ny]!=0||ma[nx][ny]!='W')
{
continue;
}
flag[nx][ny]=cnt;
node next;
next.x=nx;
next.y=ny;
q.push(next);
}
}
}
void dfs(int xx,int yy)
{
if(xx>n||yy>m||xx<1||yy<1)return;
if(a[xx][yy]!=1)return;
a[xx][yy]=ans;
for(int i=1;i<=8;i++)
if(vis[xx+x[i]][yy+y[i]]==0)
{
vis[xx+x[i]][yy+y[i]]=1;
dfs(xx+x[i],yy+y[i]);
vis[xx+x[i]][yy+y[i]]=0;
}
}
dp
树状dp
直系上司不能与下属同去。
int dp[6005][2];//dp[i][0]是不要i的最值,dp[i][1]是要i的最值
vector<int >g[6005];
int a[6005];
int m[6005];
int find(int now,int pre)
{
int fa,son;
int len=g[now].size();
dp[now][1]=a[now];
dp[now][0]=0;
for(int i=0;i<len;i++)
{
if(g[now][i]==pre)continue;//避免重复
son=find(g[now][i],now);//得到儿子的dp值,儿子其实是g[now][i],没有return也可以
dp[now][1]+=dp[son][0];
dp[now][0]+=max(dp[son][0],dp[son][1]);
}
return now;
}
main(void)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int x,y;
for(int i=1;i<=n-1;i++)
{
cin>>y>>x;
g[x].push_back(y);//双向建边,但是要注意不能往复遍历,因此函数要记录上一个的遍历
g[y].push_back(x);
}
int f=find(1,0);//从1开始遍历
printf("%d",max(dp[f][0],dp[f][1]));//因为dp[f][]是最后遍历的(先是儿子),所以输出这个即可
}
状压dp
农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地。
John想知道,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
const int p=100000000;
int m,n,a[20];
int dp[20][1000000];
main(void)
{
int m=read();
int n=read();
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
int x=read();
a[i]=(a[i]<<1)+!x;//初始要求
}
//(1<<12)-1
for(int i=1;i<=m;i++)//第几行
{
for(int s=0;s<=(1<<n)-1;s++)//这一行的状态合适吗
{
if(s&a[i]||s&(s<<1))continue;
if(i==1)dp[i][s]=1;//存储行数与状态下的方案数
for(int s1=0;s1<=(1<<n)-1;s1++)//枚举上一个状态
{
if(s1&a[i-1]||s1&(s1<<1))continue;
if(s1&s)continue;
(dp[i][s]+=dp[i-1][s1]%p)%=p;
}
}
}
int ans=0;
for(int s=0;s<=(1<<n)-1;s++)
{
(ans+=dp[m][s]%p)%=p;
}
cout<<ans%p;
//失败的情况
// 0
// 1
}
线段树
维护区间加法和乘法
const int maxn=200010;
int p,a[maxn],sum[4*maxn],mul[4*maxn],add[4*maxn];
inline void qm1(int k)
{
sum[ls(k)]%=p;
mul[ls(k)]%=p;
add[ls(k)]%=p;
sum[rs(k)]%=p;
mul[rs(k)]%=p;
add[rs(k)]%=p;
}
inline void build(int k,int l,int r)
{
mul[k]=1;
if(l==r)
{
sum[k]=a[l];
return ;
}
int mid=(l+r)/2;
build(ls(k),l,mid);
build(rs(k),mid+1,r);
sum[k]=sum[ls(k)]+sum[rs(k)];
}
inline void pushdown(int k,int l,int r)
{
int mid=(l+r)/2;
sum[ls(k)]*=mul[k];
sum[rs(k)]*=mul[k];
sum[ls(k)]+=add[k]*(mid-l+1);
sum[rs(k)]+=add[k]*(r-mid);
mul[ls(k)]*=mul[k];
mul[rs(k)]*=mul[k];
add[rs(k)]=add[rs(k)]*mul[k]+add[k];
add[ls(k)]=add[ls(k)]*mul[k]+add[k];
qm1(k);
add[k]=0;
mul[k]=1;
}
inline void ADD(int k,int l,int r,int x,int y,int v)
{
if(x<=l&&y>=r)
{
add[k]+=v;
sum[k]+=v*(r-l+1);
qm1(k);
return ;
}
pushdown(k,l,r);
int mid=(l+r)/2;
if(x<=mid)ADD(ls(k),l,mid,x,y,v);
if(y>mid)ADD(rs(k),mid+1,r,x,y,v);
sum[k]=sum[ls(k)]+sum[rs(k)];
}
inline void MUL(int k,int l,int r,int x,int y,int v)
{
if(x<=l&&y>=r)
{
add[k]*=v;//规定的规则是先加后乘,想要表示先乘后加必须要把加法标记更新
mul[k]*=v;
sum[k]*=v;
qm1(k);
return ;
}
pushdown(k,l,r);
int mid=(l+r)/2;
if(x<=mid)MUL(ls(k),l,mid,x,y,v);
if(y>mid)MUL(rs(k),mid+1,r,x,y,v);
sum[k]=sum[ls(k)]+sum[rs(k)];
}
inline int check(int k,int l,int r,int x,int y)
{
if(x<=l&&y>=r)return sum[k];
pushdown(k,l,r);
int mid=(l+r)/2;
int ans=0;
if(x<=mid)ans+=check(ls(k),l,mid,x,y);
if(y>mid)ans+=check(rs(k),mid+1,r,x,y);
return ans%p;
}
main(void)
{
int n,m,cmd,x,y,k;
scanf("%lld%lld%lld",&n,&m,&p);
_1for(i,n)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
_1for(j,m)
{
scanf("%lld%lld%lld",&cmd,&x,&y);
if(cmd==1)
{
scanf("%lld",&k);
MUL(1,1,n,x,y,k);
}
if(cmd==2)
{
scanf("%lld",&k);
ADD(1,1,n,x,y,k);
}
if(cmd==3)
{
printf("%lld\n",check(1,1,n,x,y)%p);
}
}
}
lca与树剖
dfs
void dfs1(int u)//预处理siz son dep fa
{
siz[u]=1;//包含u的子树
son[u]=0;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa[u])continue;
dep[v]=dep[u]+1;//更新子节点
fa[v]=u;
dfs1(v);
siz[u]+=siz[v];//更新v后,累加到u
if(siz[v]>siz[son[u]])son[u]=v;//更新重儿子
}
}
void dfs2(int u,int rt)//预处理id top rk
{
id[u]=++cnt;
//rk[index]=u;
wt[cnt]=w[u];
top[u]=rt;
if(son[u])dfs2(son[u],rt);//先搜重儿子
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
维护树链
预处理过后要对链进行维护,因为每段链是连续的,一般选择线段树维护区间和,实现区间查询和区间更新,即将得到的
i
d
[
]
id[]
id[]和
w
t
[
]
wt[]
wt[]进行建树
inline int qRange(int x,int y){
int ans=0;
while(top[x]!=top[y]){//当两个点不在同一条链上
if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点
res=0;
query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
ans+=res;
ans%=mod;//按题意取模
x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
}
//直到两个点处于一条链上
if(dep[x]>dep[y])swap(x,y);//把x点深度更深的那个点
res=0;
query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
ans+=res;
return ans%mod;
}
inline void updRange(int x,int y,int k){//同上
k%=mod;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,1,n,id[top[x]],id[x],k);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
update(1,1,n,id[x],id[y],k);
}
inline int qSon(int x){
res=0;
query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1
return res;
}
inline void updSon(int x,int k){//同上
update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
树链求lca
inline int lca(int x,int y)
{
while(top[x]!=top[y])//从深的点向上跳,直到x和y在一条链上
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);公共祖先即为链上深度浅的结点
return x;
}
字符串
马拉车算法
char a[12000000];
int R[23000000];
int manacher()
{
string s="$#";
int l1=strlen(a);
for(int i=0;i<l1;i++)
{
s+=a[i];
s+='#';
}
int len=2*l1+1;
int mx=0,id=0;
int max_len=0;
for(int i=1;i<len;i++)
{
if(i<mx)
{
R[i]=min(R[2*id-i],mx-i);
}
else
R[i]=1;
while(s[i-R[i]]==s[i+R[i]])
{
R[i]++;
}
if(mx<i+R[i])
{
id=i;
mx=i+R[i];
}
max_len=max(max_len,R[i]-1);
}
return max_len;
}
main(void)
{
scanf("%s",&a);
cout<<manacher();
}
kmp算法
比较字符串
int j;
j=0;//j可以看做表示当前已经匹配完的模式串的最后一位的位置
//如果看不懂,你也可以理解为j表示模式串匹配到第几位了
for(int i=1;i<=la;i++)
{
while(j&&b[j+1]!=a[i])j=kmp[j];
//如果失配 ,那么就不断向回跳,直到可以继续匹配
if (b[j+1]==a[i]) j++;
//如果匹配成功,那么对应的模式串位置++
if (j==lb)
{
cout<<i-lb+1<<endl;
j=kmp[j];
//继续匹配
}
}
求kmp
j=0;
for (int i=2;i<=lb;i++)
{
while(j&&b[i]!=b[j+1])
//此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
j=kmp[j];
//通过自己匹配自己来得出每一个点的kmp值
if(b[j+1]==b[i])j++;
kmp[i]=j;
//i+1失配后应该如何跳
}
思路:利用kmp数组的含义来解。
kmp数组中储存的是这个字符串前缀和后缀中相同字符串的最长长度
1.一个串的最小循环节长度:len - kmp[len]。
2.若len%(len-kmp[len]) == 0, 则这个字符串的最小周期为len-kmp[len]。一定要注意前提是 len % (len - next[len]) == 0,否则不存在循环周期。