您的位置:首页 > 其它

[置顶] NOIP之前在做什么?有没有空呢?可以来打板子吗?

2017-11-04 18:50 423 查看

N logN求最大上升子序列(LIS)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int rise[999999];
int a[1100],cnt=1;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
rise[1]=a[1];
for(int i=2;i<=n;i++)
if(a[i]>rise[cnt])
rise[++cnt]=a[i];
else
{
int pos=lower_bound(rise+1,rise+cnt+1,a[i])-rise;
rise[pos]=a[i];
}
printf("%d",cnt);
}


质数线性筛

用于各种需要素数的题中,很重要的工具

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 1e6+1
using namespace std;
bool vis[11000000];
int prime[1100000],cnt;
int main()
{
vis[1]=1;

for(int i=2;i<=N;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
}

for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
}


快速幂

更基本的工具之一

#include <cstdio>
#include <iostream>
using namespace std;
int f_pow(int x,int y)
{
int ans=1;
while(y)
{
if(y&1) ans*=x;
x*=x;
y/=2;
}
return ans;
}
int main()
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d",f_pow(x,y));
}


归并排序

可以N log N排好序,主要是可以求出逆序对的个数。

相较于树状数组的优势在于,可以把数字的问题转化成字典序排序的字符串问题,在比较的时候加上二分和哈希

而树状数组无法解决这类问题(可能行,但是我不会)

#include <cstdio>
#include <iostream>
using namespace std;
int a[999999];
int tmp[999999];
int ans=0;//逆序对个数
void sort(int l,int r)
{
if(l>=r) return;
int mid=(l+r)/2;
sort(l,mid);
sort(mid+1,r);
int i=l,j=mid+1;
int now=l;
while(i<=mid||j<=r)
{
bool flag;
if(i>mid) flag=0;
if(j>r) flag=1;
if(i<=mid&&j<=r)
if(a[i]<=a[j]) flag=1;//千万要注意,一定加上=号
else flag=0;
if(flag)
tmp[now++]=a[i++];
else
{
ans+=(mid-i+1);
tmp[now++]=a[j++];
}
}
for(int i=l;i<=r;i++)
a[i]=tmp[i];
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(1,n);
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
printf("\n%d",ans);
return 0;
}


树状数组

树状数组是一种简单版的线段树,核心思想的一个lowbit的函数,不再概述。

作用主要是求逆序对的个数问题

也是 N logN

相较于归并排序 的优势在于,好打不易错

但是劣势也很明显,需要进行离散化,无法处理字典序问题

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxm=500000;
int sum[510000];//极限数据
int a[510000],w[510000];
int lowbit(int x)
{
return x&-x;
}
void ins(int x)
{
for(int i=x;i<=maxm;i+=lowbit(i))
sum[i]++;
}
int ask(int x)
{
int ans=0;
for(int i=x;i>=1;i-=lowbit(i))
ans+=sum[i];
return ans;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),w[i]=a[i];
sort(w+1,w+n+1);
int t=unique(w+1,w+n+1)-w-1;//去重函数,为了搞离散化
for(int i=1;i<=n;i++)
a[i]=lower_bound(w+1,w+t+1,a[i])-w;
int ans=0;
for(int i=n;i>=1;i--)
{
ans+=ask(a[i]);
ins(a[i]+1);
}
printf("%d",ans);
return 0;
}


离散化

一些题目中的数据会有大量间隙,导致内存爆炸。

离散化用于的是对于数据的大小关系决定答案的一类问题。

比如逆序对,以及一些纸牌的问题。

具体离散化方法如下

先把所有的元素扔进一个数组中

排序,去重。

然后对于原数组,用二分找排序数组中这个数值的位置,用这个位置来做具体值。

复杂度为N log N

Tarjan

关于Tarjan的用途有很多,比如求强连通分量,求割点或者割边,以及树上最近公共祖先

对于强连通分量,我们可以有很多的用途,比方说缩点。

这里给出求强连通分量的代码

#include <cstdio>
#include <iostream>
using namespace std;
const int maxm=1e6+1;
int dfn[maxm],low[maxm],c[maxm],stk[maxm];
bool vis[maxm];
int col,num,top;
int head[maxm],net[maxm],to[maxm];
int cnt,maxi,maxc;
void add(int x,int y)
{
cnt++;
to[cnt]=y;
net[cnt]=head[x];
head[x]=cnt;
}
void tarjan(int x)
{
dfn[x]=low[x]=++num;
stk[++top]=x;
vis[x]=1;
for(int i=head[x];i;i=net[i])
{
int p=to[i];
if(!dfn[p])
{
tarjan(p);
low[x]=min(low[x],low[p]);
}
else
if(vis[p])
low[x]=min(low[x],dfn[p]);
}
if(low[x]==dfn[x])
{
col++;
//int tot=1;
while(stk[top]!=x)
{
c[stk[top]]=col;
vis[stk[top--]]=0;
}
//if(tot>maxi) maxi=tot,maxc=col;
top--;
c[x]=col;
vis[x]=0;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,t;
scanf("%d%d",&u,&v);
add(u,v);
//if(t==2) add(v,u);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
//printf("%d\n",maxi);
for(int i=1;i<=n;i++)
//if(c[i]==maxc)
printf("%d ",c[i]);

return 0;
}


Tarjan缩点

在很多题中,可以把强连通里的点当一个点处理。

这时就需要用到Tarjan的缩点法则

首先记录下原图的信息,求出原图的强连通分量,然后用原图信息重新构图即可
一道练手题

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int maxm=2*1e6+1;
int head[maxm],net[maxm],to[maxm];
int val[maxm],cval[maxm];
int dfn[maxm],low[maxm],c[maxm],stk[maxm];
int col,num,cnt,top;
int dp[maxm];
bool vis[maxm];
struct node{
int x,y;
}a[maxm];
void add(int x,int y)
{
cnt++;
to[cnt]=y;
net[cnt]=head[x];
head[x]=cnt;
}
void tarjan(int x)
{
vis[x]=1;
dfn[x]=low[x]=++num;
stk[++top]=x;
for(int i=head[x];i;i=net[i])
{
int p=to[i];
if(!dfn[p])
{
tarjan(p);
low[x]=min(low[x],low[p]);
}
else
if(vis[p])
low[x]=min(low[x],dfn[p]);
}
if(low[x]==dfn[x])
{
c[x]=++col;
vis[x]=0;
cval[col]+=val[x];
while(stk[top]!=x)
{
vis[stk[top]]=0;
c[stk[top]]=col;
cval[col]+=val[stk[top]];
vis[stk[top--]]=0;
}
top--;
}
}
void dfs(int x)
{
//if(dp[x]) return dp[x];
dp[x]=cval[x];
//printf("%d ",x);
int maxi=0;
for(int i=head[x];i;i=net[i])
{
int p=to[i];
//if(!p) continue;
if(!dp[p]) dfs(p);
maxi=max(maxi,dp[p]);
}
dp[x]+=maxi;
//return dp[x]=cval[x]+maxi;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",val+i);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
add(a[i].x,a[i].y);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);

memset(head,0,sizeof(head));
memset(net,0,sizeof(net));
memset(to,0,sizeof(to));
cnt=0;

for(int i=1;i<=m;i++)
if(c[a[i].x]!=c[a[i].y])
add(c[a[i].x],c[a[i].y]);
//else printf("--%d %d--\n",a[i].x,a[i].y);
//printf("---%d---\n",col);
/*for(int i=1;i<=n;i++)
printf("%d\n",c[i]);*/
int ans=0;

for(int i=1;i<=col;i++)
{
if(!dp[i]) dfs(i);
ans=max(ans,dp[i]);
}

printf("%d",ans);

return 0;
}


LCA

LCA有非常多的用途,比方说求树上两点的最短路径长度

这条路径一定是 u->lca(u,v)->v

那么最短路径即为 dis[u]+dis[v]-2*dis[lca(u,v)]

dis为距离根节点的距离

对于LCA的求法也有很多

1:Tarjan求

2:倍增求

两种方法都很快,但是第二种方法易被卡,但对于求具体的路径却没有第二种来的方便

这里给出两种方法的代码

Tarjan求法

#include <cstdio>
#include <iostream>
using namespace std;
const int maxm=1010000;
int head[2][maxm],net[2][maxm],to[2][maxm],lca[2][maxm];
int cnt[2],fat[maxm];
bool vis[maxm];
int find(int x)
{
if(fat[x]==x) return x;
return fat[x]=find(fat[x]);
}
void add(int id,int x,int y)
{
cnt[id]++;
to[id][cnt[id]]=y;
net[id][cnt[id]]=head[id][x];
head[id][x]=cnt[id];
}
void tarjan(int x)
{
fat[x]=x;
vis[x]=1;
for(int i=head[0][x];i;i=net[0][i])
{
int p=to[0][i];
if(vis[p]) continue;
tarjan(p);
fat[p]=x;
}
for(int i=head[1][x];i;i=net[1][i])
{
int p=to[1][i];
if(!vis[p]) continue;
int fa=find(p);
lca[1][i]=fa;
if(i%2) lca[1][i+1]=fa;
else lca[1][i-1]=fa;
}
}
int main()
{
int n,q,root;
scanf("%d%d%d",&n,&q,&root);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(0,u,v);
add(0,v,u);
}
for(int i=1;i<=q;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(1,u,v);
add(1,v,u);
}
/*for(int i=1;i<=n;i++)
if(!vis[i]) tarjan(i); */
tarjan(root);
for(int i=1;i<=q;i++)
printf("%d\n",lca[1][i*2]);
}


倍增法

#include <cstdio>
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
const int max1=1000001;
int p[max1][23],deep[max1];
int head[max1],net[2*max1],to[2*max1];
bool vis[max1];
int cnt;
int n,q,root;
void add(int x,int y)
{
cnt++;
to[cnt]=y;
net[cnt]=head[x];
head[x]=cnt;
}
void get_deep(int x)
{
vis[x]=1;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(vis[tmp]) continue;
p[tmp][0]=x;
deep[tmp]=deep[x]+1;
get_deep(tmp);
}
}
void get_fat()
{
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(p[i][j-1]!=-1)
{
//printf("--%d %d--\n",i,j);
p[i][j]=p[p[i][j-1]][j-1];
//if(i==5&&j==1) printf("--%d %d--\n",p[i][j-1],p[p[i][j-1]][j-1]);
}

}
int lca(int u,int v)
{
if(deep[u]<deep[v]) swap(u,v);

int i;
for(i=0;(1<<i)<=deep[u];i++);
i--;
for(int j=i;j>=0;j--)
if(deep[u]-(1<<j)>=deep[v])
u=p[u][j];

if(u==v) return u;

for(int j=i;j>=0;j--)
if(p[u][j]!=-1&&p[u][j]!=p[v][j])
u=p[u][j],v=p[v][j];
return p[u][0];
}
int main()
{
scanf("%d%d%d",&n,&q,&root);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
p[root][0]=-1;
get_deep(root);
get_fat();
//p[5][1]=p[p[5][0]][0];
for(int i=1;i<=q;i++)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
//p[5][1]=p[p[5][0]][0];
//printf("%d %d",p[4][0],p[5][1]);
}


最小(最大)生成树算法

最小生成树一般有两种方法

1:Prim算法

2:Kruskal算法

第一种不加堆优化可以到N^2的复杂度

加了以后为NlogN

遗憾的是,我不会。

于是使用第二种算法

复杂度为 M logN

一般不会卡的。

最小生成树一般用于最短连接问题,即用最小的花费把所有的点搞成联通图。

最大生成树用于限重性问题,如NOIP2013 货车运输。

这里给出最小生成树算法,最大的反过来排序就行了

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
struct node{
int x,y;
int cost;
};
node a[210000];
int fat[5001];
int find(int x)
{
if(fat[x]==x) return x;
return fat[x]=find(fat[x]);
}
bool comp(node xx,node yy)
{
return xx.cost<yy.cost;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].cost);
for(int i=1;i<=n;i++) fat[i]=i;
sort(a+1,a+m+1,comp);
int tot=0,ans=0;
for(int i=1;i<=m;i++)
if(find(a[i].x)!=find(a[i].y))
{
fat[find(a[i].x)]=find(a[i].y);
tot++;
ans+=a[i].cost;
if(tot==n-1)
{
printf("%d\n",ans);
return 0;
}
}
printf("orz\n");
return 0;
}


最短路径算法

我知道的有Floyd算法,Dijkstra算法,Bellman-Ford算法,以及SPFA

其中 SPFA是Bellman-Ford的优化。

Floyd能够求任意两点之间的最短距离,而其他三种只能够求单源最短路

Floyd非常好写,复杂度N^3,如果不是太小的数据范围,基本不予考虑

Dijkstra算法无堆优化N^2,有了 N log (可是我TM不会堆优化)

SPFA是Bellman-Ford的优化,所以我这里只给出了SPFA的模板

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
const int max1=600001;
int head[max1],net[2*max1],to[2*max1],cost[2*max1];
bool vis[max1];
int cnt,dis[max1];
queue <int> dl;
void add(int x,int y,int z)
{
cnt++;
cost[cnt]=z;
to[cnt]=y;
net[cnt]=head[x];
head[x]=cnt;
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++)
{
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
add(u,v,c);
}
memset(dis,127/3,sizeof(dis));
dis[s]=0;
dl.push(s);
vis[s]=1;
while(!dl.empty())
{
int d=dl.front();
dl.pop();
vis[d]=0;
for(int i=head[d];i;i=net[i])
{
int p=to[i];
if(dis[p]>dis[d]+cost[i])
{
dis[p]=dis[d]+cost[i];
if(vis[p]) continue;
dl.push(p),vis[p]=1;
}
}
}
for(int i=1;i<=n;i++)
if(dis[i]!=dis[0]) printf("%d ",dis[i]);
else printf("2147483647 ");
return 0;
}


并查集

普通的并查集,带反集的并查集,带权并查集,按秩合并并查集,单向并查集

前四种会,先给出第一种的基本板子,先打完最简单的板子再来填坑

#include <cstdio>
#include <iostream>
using namespace std;
const int maxm=11000;
int fat[maxm];
int find(int x)
{
if(fat[x]==x) return x;
return fat[x]=find(fat[x]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
fat[i]=i;
for(int i=1;i<=m;i++)
{
int opt,x,y;
scanf("%d%d%d",&opt,&x,&y);
if(opt==1) fat[find(x)]=find(y);
else
if(find(x)==find(y)) printf("Y\n");
else printf("N\n");
}
return 0;
}


ST表

ST表其实就是倍增的思想 可以维护最大值,最小值,以及位运算的和

查询做到了O(1)

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int st[110000][19];
int ask(int l,int r)
{
int len=(r-l+1);
int t=log2(len);
return max(st[l][t],st[r-(1<<t)+1][t]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++)
scanf("%d",&x),st[i][0]=x;
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(i+(1<<j-1)<=n)
st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
for(int i=1;i<=m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",ask(l,r));
}
return 0;
}


线段树

我会写的只是最基本的线段树,只会维护最大值,最小值,和,单点修改,区间加与区间乘.
妈的线段树真好吃

区间 +

#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
struct node{
ll adi,sum;
ll len;
};
node st[4*110000];
ll a[110000];
void build(int o,int l,int r)
{
if(l==r)
{
st[o].sum=a[l];
st[o].len=1;
return;
}
int mid=(l+r)/2;
build((o<<1),l,mid);
build((o<<1)|1,mid+1,r);
st[o].sum=st[(o<<1)].sum+st[(o<<1)|1].sum;
st[o].len=r-l+1;
}
void push_down(int o)
{
if(st[o].adi)
{
st[(o<<1)].adi+=st[o].adi;
st[(o<<1)|1].adi+=st[o].adi;
st[(o<<1)].sum+=st[o].adi*st[(o<<1)].len;
st[(o<<1)|1].sum+=st[o].adi*st[(o<<1)|1].len;
st[o].adi=0;
}
}
void updata(int o,int l,int r,int ql,int qr,ll add)
{
if(l>qr||r<ql) return;
if(ql<=l&&r<=qr)
{
st[o].adi+=add;
st[o].sum+=st[o].len*add;
return;
}
push_down(o);
int mid=(l+r)/2;

updata((o<<1),l,mid,ql,qr,add);
updata((o<<1)|1,mid+1,r,ql,qr,add);

st[o].sum=st[(o<<1)].sum+st[(o<<1)|1].sum;
}
ll ask(int o,int l,int r,int ql,int qr)
{
if(l>qr||r<ql) return 0;
if(ql<=l&&r<=qr) return st[o].sum;
push_down(o);
int mid=(l+r)/2;
return ask((o<<1),l,mid,ql,qr)+ask((o<<1)|1,mid+1,r,ql,qr);
}
int main()
{
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
build(1,1,n);
for(int i=1;i<=q;i++)
{
int opt;
scanf("%d",&opt);
if(opt==2)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",ask(1,1,n,l,r));
}
else
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
updata(1,1,n,l,r,k);
}
}

return 0;
}


区间 X

#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
struct node{
ll adi,sum,adx;
ll len;
};
node st[4*110000];
ll a[110000];
int mod;
void build(int o,int l,int r)
{
st[o].adx=1;
if(l==r)
{
st[o].sum=a[l];
st[o].len=1;
return;
}
int mid=(l+r)/2;
build((o<<1),l,mid);
build((o<<1)|1,mid+1,r);
st[o].sum=(st[(o<<1)].sum+st[(o<<1)|1].sum)%mod;
st[o].len=r-l+1;
}
inline void push_down(ll o)
{
if(st[o].adi==0&&st[o].adx==1) return;
st[(o<<1)].adi=(st[(o<<1)].adi*st[o].adx+st[o].adi)%mod;
st[(o<<1)|1].adi=(st[(o<<1)|1].adi*st[o].adx+st[o].adi)%mod;
st[(o<<1)].adx=(st[(o<<1)].adx*st[o].adx)%mod;
st[(o<<1)|1].adx=(st[(o<<1)|1].adx*st[o].adx)%mod;
st[(o<<1)].sum=((st[o].adi*(st[(o<<1)].len)%mod)%mod+(st[(o<<1)].sum*st[o].adx)%mod)%mod;
st[(o<<1)|1].sum=((st[o].adi*(st[(o<<1)|1].len)%mod)%mod+(st[(o<<1)|1].sum*st[o].adx)%mod)%mod;
st[o].adi=0;st[o].adx=1;
}
void updata_j(int o,int l,int r,int ql,int qr,ll add)
{
if(l>qr||r<ql) return;
if(ql<=l&&r<=qr)
{
st[o].adi=(st[o].adi+add)%mod;
st[o].sum=(st[o].sum+(st[o].len*add)%mod);
return;
}
push_down(o);
int mid=(l+r)/2;

updata_j((o<<1),l,mid,ql,qr,add);
updata_j((o<<1)|1,mid+1,r,ql,qr,add);

st[o].sum=(st[(o<<1)].sum+st[(o<<1)|1].sum)%mod;
}
void updata_x(int o,int l,int r,int ql,int qr,ll add)
{
if(l>qr||r<ql) return;
if(ql<=l&&r<=qr)
{
st[o].adi=(st[o].adi*add)%mod;
st[o].sum=(st[o].sum*add)%mod;
st[o].adx=(st[o].adx*add)%mod;
return;
}
push_down(o);
int mid=(l+r)/2;

updata_x((o<<1),l,mid,ql,qr,add);
updata_x((o<<1)|1,mid+1,r,ql,qr,add);

st[o].sum=(st[(o<<1)].sum+st[(o<<1)|1].sum)%mod;
}
ll ask(int o,int l,int r,int ql,int qr)
{
if(l>qr||r<ql) return 0;
if(ql<=l&&r<=qr) return st[o].sum%mod;
push_down(o);
int mid=(l+r)/2;
return (ask((o<<1),l,mid,ql,qr)+ask((o<<1)|1,mid+1,r,ql,qr))%mod;
}
int main()
{
int n,q;
scanf("%d%d%d",&n,&q,&mod);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
build(1,1,n);
for(int i=1;i<=q;i++)
{
int opt;
scanf("%d",&opt);
if(opt==1)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
updata_x(1,1,n,l,r,k);
}
if(opt==2)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
updata_j(1,1,n,l,r,k);
}
if(opt==3)
{
int l,r,k;
scanf("%d%d",&l,&r);
printf("%lld\n",ask(1,1,n,l,r)%mod);
}
}

return 0;
}


读入优化

C++中有一个函数:getchar() ,用于读入字符,那么这跟读入整数有什么关系呢?

其实,经过类似高精度的处理,就可以实现类型转换啦!

读字符肯定要比您直接读数快!

long long read()
{
long long x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
x=(x<<3)+(x<<1)+ch-'0',ch=getchar();//x*8+x*2,用位运算还要快!
return x*w;
}


乘法逆元

(a+b)%p=a%p+b%p

(a*b)%p=a%p * b%p

但是

(a/b)%p !=a%p/b%p

但是若有

b*x ≡ 1 (mod p)

那么(a/b)%p=a % p * x%p

方法有三

1:费马小定理

若p为质数

那么a关于p的逆元即为 ap−2ap−2

2 : 拓展欧几里得定理

p不用为质数

#include <cstdio>
using namespace std;
void e_gcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;
y=0;
return;
}
e_gcd(b,a%b,x,y);
int tmp=x;
x=y;
y=tmp-(a/b)*y;

}
int main()
{
int a,b,x,y;
scanf("%d%d",&a,&b);
e_gcd(a,b,x,y);
printf("%d",(x+b)%b);
}


3 : 线性逆元筛法

#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const int maxm=1e6+1;
int inv[3*maxm];
int main()
{
int n,p;
scanf("%d%d",&n,&p);

inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i];
while(inv[i]<0) inv[i]+=p;
printf("%d\n",inv[i-1]%p);
}

printf("%d\n",inv
%p);

return 0;
}


差分

差分有3种 (我所知道的)

一维差分 二维差分 树上差分

1 一维差分:

我用的是用两个数组维护的差分

令 f 数组为差分数组

给 l-r区间上加数 k

只需在 f[l]+=k f[r+1]-=k
∑i1f[i]∑1if[i] 即为 i 位置上操作加上的数字

2: 二维差分

令s[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]。

则a[i][j]=s[1][1]…+s[1][j]+s[2][1]+…+s[2][j]+…+s[i][1]+…+s[i][j]。

对于每次以(x,y)为左上角,(x2,y2)为右下角的矩阵操作,相当于是令s[x][y]+=k,s[x][y2+1]-=k,s[x2+1][y]-=k,s[x2+1][y2+1]+=k。

s[x][y]+=k; s[x][y2+1]-=k;
s[x2+1][y]-=k; s[x2+1][y2+1]+=k;

for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+s[i][j];


3:树上差分

对于 ui−>lca(ui,vi)−>viui−>lca(ui,vi)−>vi若干路径

我们想求出每条边被经过了几次

我们只需对s[ui]++,s[vi]++,s[lca(ui,vi)]−=2s[ui]++,s[vi]++,s[lca(ui,vi)]−=2

然后DFS遍历 S[i]+=∑S[子树]S[i]+=∑S[子树]

这样s[i]即为边 i->fat[i]的边被经过的次数

这玩意啥用?
详见 NOIP原题运输计划

二分图匹配

匈牙利算法,可以找出最优匹配

若只判断这张图是不是二分图,可以使用黑白染色法

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
bool f[999999];
int match[999999];
int n,m,e;
int head[999999],to[999999],net[999999],cnt;
void add(int x,int y)
{
cnt++;
to[cnt]=y;
net[cnt]=head[x];
head[x]=cnt;
}
int dfs(int u)
{
for(int i=head[u];i;i=net[i])
{
int tmp=to[i];
if(f[tmp]) continue;
f[tmp]=1;
if(!match[tmp]||dfs(match[tmp]))
{
match[u]=tmp;
match[tmp]=u;
return 1;
}

}
return 0;
}
int main()
{
freopen("c.in","r",stdin);
scanf("%d%d%d",&n,&m,&e);

for(int i=1;i<=e;i++)
{
int x,y;

scanf("%d%d",&x,&y);
if(y>m) continue;
add(x,y+n);
add(y+n,x);
}

int sum=0;

for(int i=1;i<=n;i++)
{
for(int j=1;j<=n+m;j++)
f[j]=0;
sum+=dfs(i);
}

printf("%d",sum);

return 0;
}


Hash

哈希可以暴力求出两段字符串是否相同

如何得到一段区间的hash值?

hash[l->r]=hash[r]-hash[l-1]*base^len

单模容易被卡,双模大法好

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
struct node{
ll hash1,hash2;
}a[99999];
const int mod1=1e9+7;
const int mod2=1e9+9;
const int base1=31;
const int base2=31;
char s[9999999];
ll make_hash1()
{
ll hash=0;
int len=strlen(s+1);
for(int i=1;i<=len;i++)
hash=(hash*base1+(ll)s[i])%mod1;
return hash;
}
ll make_hash2()
{
ll hash=0;
int len=strlen(s+1);
for(int i=1;i<=len;i++)
hash=(hash*base2+(ll)s[i])%mod2;
return hash;
}
bool comp(node x,node y)
{
return x.hash1<y.hash1;
}
int main()
{
int n;
scanf("%d\n",&n);
for(int i=1;i<=n;i++)
scanf("%s",(s+1)),a[i].hash1=make_hash1(),a[i].hash2=make_hash2();
sort(a+1,a+n+1,comp);
int ans=1;

for(int i=2;i<=n;i++)
if(a[i].hash1!=a[i-1].hash1||a[i].hash2!=a[i-1].hash2) ans++;

return printf("%d",ans)*0;
}


状态压缩

状态压缩好像只与DP联系在一起

但其实并不是的,它同样运用于BFS或者是DFS中(逃)

状压的题非常好看出来

很明显的特征就是数据范围贼小。

八成就是状压或者是爆搜了

状压的基本思路就是枚举基础状态,然后转移。

这里给出一道练手题

标签是记忆化搜索,我居然用状压水过去了 (雾)

基本的状压思路是差不多的,就看你怎么操作了233

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
bool dp[1<<17][17];
char w[101],t[101],len[101];
char s[200];
int main()
{
int n;
scanf("%d\n",&n);
for(int i=1;i<=n;i++)
scanf("%s",(s+1)),w[i]=s[1],t[i]=s[strlen(s+1)],len[i]=strlen(s+1);
for(int i=1;i<=n;i++)
dp[1<<i-1][i]=1;
dp[0][0]=1;
for(int i=0;i<1<<n;i++)
for(int j=1;j<=n;j++)
if(dp[i][j])
{
for(int k=1;k<=n;k++)
if(((1<<k-1)&i)==0&&j!=k)
if(t[j]==w[k]) dp[i|(1<<k-1)][k]=1;
}
int ans=0;
for(int i=0;i<1<<n;i++)
for(int j=1;j<=n;j++)
if(dp[i][j])
{
int num=0;
for(int k=1;k<=n;k++)
if(i&(1<<k-1)) num+=len[k];
//puts("");
ans=max(ans,num);
}
printf("%d",ans);
}


高精度

压位比较快233

高精加和高精除(其他两个改就行)



#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int a[99999];
int b[99999];
int c[99999];
char s[99999];
int main()
{
scanf("%s",(s+1));
int len=strlen(s+1);
int lena=1,k=1;
for(int i=len;i>=1;i--)
{
if(k==1000) k=1,lena++;
a[lena]+=k*(s[i]-'0');
k*=10;
}
scanf("%s",(s+1));
k=1;
len=strlen(s+1);
int lenb=1;
for(int i=len;i>=1;i--)
{
if(k==1000) k=1,lenb++;
b[lenb]+=k*(s[i]-'0');
k*=10;
}

len=max(lena,lenb);
for(int i=1;i<=len;i++)
{
c[i]+=a[i]+b[i];
if(c[i]>=1000) c[i+1]+=c[i]/1000,c[i]%=1000;
}
while(c[len+1]) len++;

printf("%d",c[len]);
for(int i=len-1;i>=1;i--)
printf("%03d",c[i]);
}




#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#define ll long long
using namespace std;
const ll K=1e9;
char s[1000000];
ll a[100000],b;
int main()
{
scanf("%s",(s+1));
int len=strlen(s+1);
ll k=1;
int lena=1;

for(int i=len;i>=1;i--)
{
a[lena]+=(s[i]-'0')*k;
k*=10;
if(k==K) k=1,lena++;
}
ll x;
scanf("%lld",&x);
for(int i=lena;i>=1;i--)
{
a[i-1]+=a[i]%x*K;
a[i]/=x;
}
while(lena>=1&&!a[lena]) lena--;

printf("%lld",a[lena--]);
for(int i=lena;i>=1;i--)
printf("%09d",a[i]);
return 0;
}


矩阵快速幂

用于递推问题,先推出矩阵,然后模拟即可

模板题

#include <cstdio>
#include <iostream>
#include <cstring>
#define ll long long
using namespace std;
const int mod=1e9+7;
ll ans[4][4],x[4][4],c[4][4];
void ans_x()
{
for(int i=1;i<=3;i++)
c[1][i]=ans[1][i],ans[1][i]=0;
for(int i=1;i<=1;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
ans[i][j]=(ans[i][j]+(c[i][k]*x[k][j])%mod)%mod;
}
void x_x()
{
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
c[i][j]=x[i][j],x[i][j]=0;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
x[i][j]=(x[i][j]+(c[i][k]*c[k][j])%mod)%mod;
}
void init()
{
memset(ans,0,sizeof(ans));
memset(x,0,sizeof(x));
ans[1][1]=1,ans[1][2]=1,ans[1][3]=1;
x[1][1]=1,x[1][3]=1,x[2][1]=1,x[3][2]=1;
}
ll fast_pow(ll k)
{
if(k<=3) return 1;
init();
k-=3;
while(k)
{
if(k%2) ans_x();
k/=2;
x_x();
}
/*for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
printf("-%d ",x[i][j]);*/
return ans[1][1]%mod;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
ll x;
scanf("%lld",&x);
printf("%lld\n",fast_pow(x));
}

return 0;
}


单调队列

一个队列递增或者递减成为单调队列

单调队列对于动态求最小最大值有奇效。

详见 NOIP2016 蚯蚓

险恶的出题人,卡double ,但是没卡住Float

注意啦,维护三队列时绝对不能开FIFO队列,会RE,别问我咋知道的
蚯蚓的链接,可以当做练手题

#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
ll dl1[7*1000000+100000+100];
ll dl2[7*1000000+100000+100];
ll dl3[7*1000000+100000+100];
ll a[999999];
int main()
{
memset(dl1,128,sizeof(dl1));
memset(dl2,128,sizeof(dl2));
memset(dl3,128,sizeof(dl3));
int n,m,q,u,v,t;
scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
float p=(float)u/(float)v;
//printf("%lf",p);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
sort(a+1,a+n+1);
for(int i=n;i>=1;i--)
dl1[n-i+1]=a[i];
int l1=1,l2=1,l3=1;
for(int i=1;i<=m;i++)
{
ll x1=-1e7,x2=-1e7,x3=-1e7;
x1=dl1[l1];
x2=dl2[l2];
x3=dl3[l3];
ll x=max(x1,max(x2,x3));
if(x==x1) l1++;
else
{
if(x==x2) l2++;
else l3++;
}
ll p1=(x+=(i-1)*q)*u/v;
if(i%t==0)
printf("%lld ",x);
//ll p1=x*p;
ll p2=x-p1;
p1-=(i*q);
p2-=(i*q);
dl2[i]=p1,dl3[i]=p2;
}
puts("");
for(int i=1;i<=(n+m);i++)
{
ll x1=-1e7,x2=-1e7,x3=-1e7;
x1=dl1[l1];
x2=dl2[l2];
x3=dl3[l3];
ll x=max(x1,max(x2,x3));
if(x==x1) l1++;
else
{
if(x==x2) l2++;
else l3++;
}
x+=m*q;
if(i%t==0)
printf("%lld ",x);
}

return 0;
}


Dinic模板(重置版

原来的模板写的太丑了,新写的放在这里存一下

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
const int inf=0x7fffffff;
const int maxm=1e6+100;
int head[maxm],to[maxm<<1],cap[maxm<<1],net[maxm<<1];
int cnt=1;
inline void add(int u,int v,int c){cnt++;to[cnt]=v,cap[cnt]=c,net[cnt]=head[u],head[u]=cnt;}
inline void addedge(int u,int v,int c){add(u,v,c),add(v,u,0);}
int deep[maxm];
std::queue<int> dl;
namespace Maxflow{
inline bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(deep,-1,sizeof(deep));
dl.push(s),deep[s]=0;
while(!dl.empty())
{
int now=dl.front();
dl.pop();
for(int i=head[now];i;i=net[i])
if(cap[i]>0&&deep[to[i]]==-1)
dl.push(to[i]),deep[to[i]]=deep[now]+1;
}
return deep[t]!=-1;
}
int dfs(int now,int flow,int t)
{
if(now==t) return flow;
int w,used=0;
for(int i=head[now];i;i=net[i])
{
int v=to[i];
if(deep[v]==deep[now]+1&&cap[i])
{
w=dfs(v,std::min(cap[i],flow-used),t);
cap[i]-=w,cap[i^1]+=w;
used+=w;
if(used==flow) return flow;
}
}
if(!used) deep[now]=-1;
return used;
}
int dinic(int s,int t)
{
int maxflow=0;
while(BFS(s,t)) maxflow+=dfs(s,inf,t);
return maxflow;
}
}


MCMF模板(重制版

原来的模板写的太丑了,新写的放在这里存一下

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
const int inf=0x7fffffff;
const int maxm=1e5+100;
int head[maxm],to[maxm<<1],net[maxm<<1],cost[maxm<<1],cap[maxm<<1];
int cnt=1;
inline void add(int u,int v,int c1,int c2){cnt++;to[cnt]=v,cap[cnt]=c1,cost[cnt]=c2,net[cnt]=head[u],head[u]=cnt;}
inline void addedge(int u,int v,int c1,int c2){add(u,v,c1,c2),add(v,u,0,-c2);}
namespace MCMF{
int flow[maxm],pre[maxm],id[maxm],dis[maxm],maxflow,mincost;
bool vis[maxm];
std::queue<int> dl;
inline bool SPFA(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(dis,127/3,sizeof(dis)),memset(pre,-1,sizeof(pre));
dl.push(s),dis[s]=0,pre[s]=0,vis[s]=1,flow[s]=inf;
while(!dl.empty())
{
int now=dl.front();
dl.pop();
vis[now]=0;
for(int i=head[now];i;i=net[i])
if(cap[i]&&dis[to[i]]>dis[now]+cost[i])
{
dis[to[i]]=dis[now]+cost[i];
pre[to[i]]=now;
id[to[i]]=i;
flow[to[i]]=std::min(cap[i],flow[now]);
if(!vis[to[i]]) vis[to[i]]=1,dl.push(to[i]);
}
}
return pre[t]!=-1;
}
inline void change_cap(int s,int t,int x)
{
int now=t;
while(now!=s)
{
cap[id[now]]-=x,cap[id[now]^1]+=x;
now=pre[now];
}
}
inline int mcmf(int s,int t)
{
maxflow=0,mincost=0;
while(SPFA(s,t))
{
maxflow+=flow[t],mincost+=flow[t]*dis[t];
change_cap(s,t,flow[t]);
}
return mincost;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐