文章目录
数据结构
复杂度分析
输入输出
结构体优先队列
倍增
样我们需要枚举长度d,又因为1≤N≤500000,所以可以考虑到二分范围,但当T比较小的时候,需要的d较小而二分到小和大值都比较慢,所以为了解决这个问题可以使用倍增的方法,即d从1开始,如果合适 d ∗ * ∗=2,否则d/=2.
字符串
字典树
#include <bits/stdc++.h>
using namespace std;
#define eps 1e-5
const int mx=100100;
char ch[mx];
int a[mx][30],n,m,cnt[mx],tot;
void insert()
{
int s=0;//头结点设为0
for(int i=0;i<strlen(ch);i++){
if(a[s][ch[i]-'a']==0){
a[s][ch[i]-'a']=++tot; //记录下个字符的位置
}
s=a[s][ch[i]-'a']; //记录当前指针
}
cnt[s]++; //统计个数
}
int query(){
int ans=0,p=0;
for(int i=0;i<strlen(ch);i++){
p=a[p][ch[i]-'a'];
if(p==0) return ans;
ans+=cnt[p];
//printf("%d\n",cnt[p]);
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%s",ch);
insert();
}
for(int i=0;i<m;i++){
scanf("%s",ch);
printf("%d\n",query());
}
return 0;
}
哈夫曼树
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
using namespace std;
pair<ll,int>t;
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
ll ans=0,x;
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++){
scanf("%lld",&x);
q.push({x,0});
}
while((n-1)%(k-1))
q.push({0,0}),n++;
while(q.size()>1){
ll sum=0;
int mx=0;
for(int i=0;i<k;i++){
sum+=q.top().first;
mx=max(mx,q.top().second);
q.pop();
}
ans+=sum;
//printf("%lld\n",ans);
q.push({sum,mx+1});
}
printf("%lld\n%d\n",ans,q.top().second);
return 0;
}
哈希
hash的时候注意取余,一般是比输入值略小的素数。
//数值
const ll mod=99991;
int n,flag=1;
ll x[100010][7];
vector<ll >q[99991];
ll f(int pos){
ll sum=0,mul=1;
for(int i=0;i<6;i++){
sum=(sum+x[pos][i])%mod;
mul=(mul*x[pos][i])%mod;
}
return (sum+mul)%mod;
}
//字符串
scanf("%s",ch+1);
int a,b,x,y,n,len=strlen(ch+1);
val[0]=1; //因为是左移的位数所以初始值为1
for(int i=1;i<=len;i++){
sum[i]=sum[i-1]*131+ch[i]-'a'+1; //ch[i]-'a'+1防止出现某一位为0的情况
val[i]=val[i-1]*131;
}
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%d%d%d",&a,&b,&x,&y);
if(sum[b]-sum[a-1]*val[b-a+1]==sum[y]-sum[x-1]*val[y-x+1]) //注意补位
printf("Yes\n");
else
printf("No\n");
}
return 0;
数学相关
矩阵快速幂
1.0
const int N=10;
int tmp[N][N];
void multi(int a[][N],int b[][N],int n)
{
memset(tmp,0,sizeof tmp);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
for(int k=0;k<n;k++)
tmp[i][j]+=a[i][k]*b[k][j];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
a[i][j]=tmp[i][j];
}
int res[N][N];
void Pow(int a[][N],int n)
{
memset(res,0,sizeof res);//n是幂,N是矩阵大小
for(int i=0;i<N;i++)
res[i][i]=1;
while(n)
{
if(n&1)
multi(res,a,N);//res=res*a;复制直接在multi里面实现了;
multi(a,a,N);//a=a*a
n>>=1;
}
}
2.0
struct Mat
{
LL m[101][101];
};//存储结构体
Mat a,e; //a是输入的矩阵,e是输出的矩阵
Mat Mul(Mat x,Mat y)
{
Mat c;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
c.m[i][j] = 0;
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
for(int k=1;k<=n;++k){
c.m[i][j] = c.m[i][j]%mod + x.m[i][k]*y.m[k][j]%mod;
}
}
}
return c;
}
Mat pow(Mat x,LL y)//矩阵快速幂
{
Mat ans = e;
while(y){
if(y&1) ans = Mul(ans,x);
x = Mul(x,x);
y>>=1;
}
return ans;
}
期望:
我们有一个长为L的pokey(是杏子,幻视ing),现有一个程序,如果L>d,则在L上等可能的选择任意一点,吃掉左边,得到新的L,然后重复此程序,求程序执行次数的期望。
即长为L的木棍,当L>d时,每次等可能的去掉X(0<X<=L),求操作次数的期望。
设f(x)为长度为x的pokey的操作次数的期望。
则f(x)=0 (x<=d)
f(x)=1+
∫
0
d
f
(
t
)
d
t
/
x
\int_{0}^{d} f(t) \,dt/x
∫0df(t)dt/x+
∫
d
x
f
(
t
)
d
t
/
x
\int_{d}^{x} f(t) \,dt/x
∫dxf(t)dt/x (x<=d) (因为长度小于等于d时就不再继续了,所以第一个积分值为0)
=1+(F(x)-F(d)) /x (因为取[d,x]是等可能的所有除以x)
则
f
(
x
)
˙
\dot{f(x)}
f(x)˙=
(
x
∗
F
(
x
)
˙
−
(
F
(
x
)
−
F
(
d
)
)
)
/
(
x
2
)
(x*\dot{F(x)}-(F(x)-F(d)))/(x^2)
(x∗F(x)˙−(F(x)−F(d)))/(x2) (求导)
=
(
x
∗
f
(
x
)
−
∫
d
x
f
(
t
)
d
t
)
/
(
x
2
)
(x*f(x)-\int_{d}^{x} f(t) \,dt)/(x^2)
(x∗f(x)−∫dxf(t)dt)/(x2)
=
(
x
∗
f
(
x
)
−
x
∗
f
(
x
)
+
x
)
/
(
x
2
)
(x*f(x)-x*f(x)+x)/(x^2)
(x∗f(x)−x∗f(x)+x)/(x2)
=1/x
所以
f
(
x
)
=
l
n
x
+
c
f(x)=lnx+c
f(x)=lnx+c
lim
x
→
+
d
f
(
x
)
\lim_{x\rightarrow+d} f(x)
limx→+df(x)=1(因为L=d的时执行)
所以c=
1
−
l
n
d
1-lnd
1−lnd
则得到最终答案
f
(
x
)
=
l
n
x
−
l
n
d
+
1
f(x)=lnx-lnd+1
f(x)=lnx−lnd+1
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t,n,a,b;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
long long ans=0;
for(int i=0;i<n;i++){
scanf("%d%d",&a,&b);
ans+=(long long)a*b;
}
printf("%lld\n",ans);
}
return 0;
}
倍数
给出正整数 n 和 m,统计满足以下条件的正整数对 (a, b) 的数量:
- 1 <= a <= n, 1 <= b <= m;
- a × b 是 2016 的倍数。
因为( a ∗ b a*b a∗b)%mod=0 <=> (a%mod) ∗ * ∗ (b%mod)%mod.
所以 ( n ∗ m n*m n∗m)%2016=0我们可以改写成 (a%2016) ∗ * ∗ (b%2016)%2016=0
然后我们将式子写成( a ∗ 2016 + x a*2016+x a∗2016+x) ∗ * ∗ ( b ∗ 2016 + y b*2016+y b∗2016+y)%2016=0 =>
( a ∗ b ∗ 201 6 2 + x ∗ b ∗ 2016 + y ∗ a ∗ 2016 + x ∗ y a*b*2016^2+x*b*2016+y*a*2016+x*y a∗b∗20162+x∗b∗2016+y∗a∗2016+x∗y)%2016=0
所有上式能否成立取决于x*y是否为2016的倍数
而当x>2016时可以改写成( x / 2016 ∗ 2016 + x x/2016*2016+x x/2016∗2016+x%2016)(此处/为向下取整)
所以x、y的范围就变成了[1,2016]
又因为当 x ∗ y x*y x∗y%2016=0时,a和b可以取任意不超过范围的整数,所以方案则有(n-i)/2016+1中(1代表a=0时,(n-i)/2016代表a非0时)(b同理)
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
long long ans=0;
for(int i=1;i<=min(2016,n);i++){
for(int j=1;j<=min(2016,m);j++){
if(i*j%2016==0){
long long a=(n-i)/2016+1;
long long b=(m-j)/2016+1;
ans+=a*b;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
快速乘
long long pw(long long a,long long b){
long long ans=1;
while(b){
if(b&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans%mod;
}
差分约束
每次操作对[L,R]的所有数进行+1操作,求最后有多少个奇数。
注意左右端点和具体L、R。
图论
最短Hamilton路径
给定一张 n个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
数据保证
1≤n≤20
a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。
因为从0开始到n-1结束,且每个点只走一遍。
又因为不超过20,所以可以用二进制的01表示每个点是否走过
例如:010011 表示走过了1号、2号、5号节点
然后可以得出状态转移方程x[i][j]=min(x[i][j],x[i^(1<<j)][k]+a[k][j]);
x[i][j]表示状态i的时候停留在j点的最短路径(状态i转换成二进制后则为走过的路径)
例如:x[5][3]表示目前经过1号点和3号点,现在停留在3号点的最短路径
注意点:x数组的一维要开到1<<20;需要赋初值。
(i>>j)&1:判断的是j点是否已经在i路径里了
((i^(1<<j))>>k&1):判断的是不包含j点的路径是否含有k
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int inf= 0x3f3f3f3f;
int x[1<<20][20];
int main()
{
int a[50][50],n;
scanf("%d",&n);
memset(x,inf,sizeof(x));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
scanf("%d",&a[i][j]);
}
x[1][0]=0;
for(int i=1;i<(1<<n);i++){
for(int j=0;j<n;j++){
if((i>>j)&1){
for(int k=0;k<n;k++){
if((i^(1<<j))>>k&1){
x[i][j]=min(x[i][j],x[i^(1<<j)][k]+a[k][j]);
}
}
}
}
}
printf("%lld\n",x[(1<<n)-1][n-1]);
return 0;
}
DFS判环
int vis[300010],tim,pre[300010];
struct stu{
int v;
};
vector<stu> e[300010];
vector<int> temp;
void add(int a,int b){
stu t;
t.v=b;
e[a].push_back(t);
}
void dfs(int u){
vis[u]=++tim; //时间戳
for(int i=0;i<e[u].size();i++){
int v=e[u][i].v;
if(v==pre[u]) continue;
if(!vis[v]){
pre[v]=u;
dfs(v);
}
else if(vis[v]>vis[u]){
int t=v;
int now=0;
while(t!=u){
now++;
t=pre[t];
}
now++;
temp.push_back(now);
}
}
}
int main()
{
for(int i=1;i<=n;i++){
if(!vis[i])
dfs(i);
}
for(int i=0;i<temp.size();i++){
int v=temp[i];
ans=ans*(pw(2,v)-1)%998244353;
m-=v;
}
}
BFS搜路径
Bobo 有一个 n 个点,m 条边的有向无环图(即对于任意点 v,不存在从点 v 开始、点 v 结束的路径)。
为了方便,点用 1,2,…,n 编号。设
c
o
u
n
t
(
x
,
y
)
c
o
u
n
t
(
x
,
y
)
\mathrm{count}(x, y)count(x,y)
count(x,y)count(x,y) 表示点 x 到点 y 不同的路径数量(规定
c
o
u
n
t
(
x
,
x
)
=
0
\mathrm{count}(x, x) = 0
count(x,x)=0),Bobo 想知道链
∑
i
=
1
n
∑
j
=
1
n
c
o
u
n
t
(
i
,
j
)
⋅
a
i
⋅
b
j
\sum_{i=1}^{n}\sum_ {j=1}^{n}count(i,j)⋅a i⋅b j
∑i=1n∑j=1ncount(i,j)⋅ai⋅bj,除以 (
1
0
9
10^9
109+7)的余数。其中,
a
i
,
b
j
a_i, b_j
ai,bj是给定的数列。
根据
a
∗
b
+
c
∗
b
=
(
a
+
c
)
∗
b
a*b+c*b=(a+c)*b
a∗b+c∗b=(a+c)∗b这一性质,如果存在类似x->y->z这种路,
a
[
x
]
∗
b
[
y
]
+
a
[
x
]
b
[
z
]
+
a
[
y
]
b
[
z
]
a[x]*b[y]+a[x]b[z]+a[y]b[z]
a[x]∗b[y]+a[x]b[z]+a[y]b[z],可以写成
a
[
x
]
∗
b
[
y
]
+
(
a
[
x
]
+
a
[
y
]
)
∗
b
[
z
]
a[x]*b[y]+(a[x]+a[y])*b[z]
a[x]∗b[y]+(a[x]+a[y])∗b[z]即x->z这条路可以在求y->z时求出。
这样我们把起点(即入度为0的点)入队开始搜索,找到他的所有出度,并使出度点的a[V]加上入度的点a[u],然后如果当前出度点的入度为0了,就把它也入队。
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
ll a[100010],b[100010],in[100010];
vector<int> v[100010];
queue<int> q;
int main()
{
int n,m,x,y;
while(scanf("%d%d",&n,&m)!=EOF){
for(int i=0;i<=n;i++)
v[i].clear();
memset(in,0,sizeof(in));
long long ans=0;
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i],&b[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
v[x].push_back(y);
in[y]++;
}
for(int i=1;i<=n;i++){
if(!in[i])
q.push(i);
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<v[u].size();i++){
int v1=v[u][i];
ans=(ans+ksc(a[u],b[v1]))%mod;
a[v1]=(a[v1]+a[u])%mod;
in[v1]--;
if(!in[v1]){
q.push(v1);
}
}
}
printf("%lld\n",ans);
}
return 0;
}
SLF优化的SPFA
//SLF优化的SPFA
#include <bits/stdc++.h>
using namespace std;
struct stu
{
int next;
int cost;
};
vector<stu> tu[100100];
int dis[25010],vis[25010];
void add(int a,int b,int c){
stu t;
t.next=b;
t.cost=c;
tu[a].push_back(t);
}
void spfa(int s)
{
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
deque<int> q; //双端队列
vis[s]=1;
dis[s]=0;
q.push_back(s);
while(!q.empty()){
int x=q.front();
vis[x]=0;
q.pop_front();
for(int i=0;i<tu[x].size();i++){
int v=tu[x][i].next;
int c=tu[x][i].cost;
if(dis[v]>dis[x]+c){
dis[v]=dis[x]+c;
if(!vis[v]){
vis[v]=1;
if(q.size()&&dis[v]<dis[q.front()])
q.push_front(v);
else
q.push_back(v);
}
}
}
}
}
int main()
{
int t,r,p,s,a,b,c;
scanf("%d%d%d%d",&t,&r,&p,&s);
for(int i=0;i<r;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
for(int i=0;i<p;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
spfa(s);
for(int i=1;i<=t;i++){
if(dis[i]==0x3f3f3f3f)
printf("NO PATH\n");
else
printf("%d\n",dis[i]);
}
return 0;
}
多层图
在郊区有 N 座通信基站,P 条双向电缆,第 i 条电缆连接基站Ai和Bi。
特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。
现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费Li。
电话公司正在举行优惠活动。
农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。
农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。
求至少用多少钱可以完成升级。
如果不看k条免费的部分,那本题就是求一个从1到N的最短路,然后得到这条路径上最大的边权值,即求的是最短路上的最大边。
然后加上k条免费的部分,这使得每一条边都有0和C(C为原来的边权)两种选择。这样我就可以建分层图,即建立一个新的维度使每条边都有两个分支
#include <bits/stdc++.h>
using namespace std;
int n,m,k;
struct stu{
int next;
int c;
};
vector<stu> p[20020];
int dis[1010][1010],vis[1010];
void add(int a,int b,int c){
stu t;
t.c=c;
t.next=b;
p[a].push_back(t);
}
void spfa(int s){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
queue<int> t;
vis[s]=1;
dis[s][0]=0;
t.push(s);
while(!t.empty()){
int x=t.front();
t.pop();
vis[x]=0;
for(int i=0;i<p[x].size();i++){
int v=p[x][i].next;
int c=p[x][i].c;
int w=max(dis[x][0],c);
if(dis[v][0]>w){
dis[v][0]=w;
if(!vis[v]){
t.push(v);
vis[v]=1;
}
}
for(int j=1;j<=k;j++){
w=min(dis[x][j-1],max(dis[x][j],c));
if(dis[v][j]>w){
dis[v][j]=w;
if(!vis[v]){
t.push(v);
vis[v]=1;
}
}
}
}
}
}
int main()
{
int a,b,c;
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
spfa(1);
int ans=1e9;
for(int i=0;i<=k;i++)
ans=min(ans,dis[n][i]);
if(ans==1e9)
ans=-1;
printf("%d\n",ans);
return 0;
}