[置顶] 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; } }
相关文章推荐
- 内部类可以引用它的包含类的成员吗?有没有什么限制?
- 有没有什么办法可以快速找到上一刻的操作状态?
- 【Java面试题】12 内部类可以引用它的包含类的成员吗?有没有什么限制?
- 有没有什么方法可以让IFRAME的高度随着里面的内容增加而自动增高
- 我们对入口函数之前可以做什么
- bzoj5144 [Ynoi2018]末日时在做什么?有没有空?可以来拯救吗?
- 有没有什么命令行或工具可以查看so文件的方法名不?
- 苹果6丢了有没有什么办法可以找回来
- 有没有什么方法可以让IFRAME的高度随着里面的内容增加而自动增高
- 【工具】今天有人问我可以直接离线一个完整的网站吗?有没有什么工具之类的?我推荐一款:Httrack (网站复制机)案例:离线你的博客园
- 我们对入口函数之前可以做什么
- 用jquery可以用使用serialize()序列化表单值,那有没有什么方法可以将值填充到表单中呢? (一段不错的代码)
- 我们公司的用SANGFOR将内部员工的网封掉后,有员工破解掉,还可以上网,麻烦问下有没有什么办法给阻止掉》
- 写在NOIP之前-蒟蒻我到底会什么
- [置顶] tslog是什么文件夹?可以删除吗?
- 有没有代码可以判断一个List<View>集合里的view是什么view
- [置顶] 各类模板(更新中...)(若有什么特别的需要,可以留下评论)
- Java - 内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
- 在linux下有没有什么软件可以连接windows上的MSSQL SERVER
- 有没有代码可以判断一个List<View>集合里的view是什么view