zoukankan      html  css  js  c++  java
  • 一个大Za

    图论

    最短路

    对比

    Floyd Bellman-Ford Dijkstra
    每对结点之间的最短路 单源最短路 单源最短路
    无负环的图 任意图 非负权图
    (O(N^3)) (O ( NM )) (O((N+M)log M))

    dijkstra

    priority_queue<pii>q; 
    void dijkstra(int S){
    	memset(dis,inf,sizeof(dis)),memset(vis,0,sizeof(vis));
    	q.push(make_pair(dis[S]=0,S));
    	while(!q.empty()){
    		int u=q.top().second;q.pop();
    		if(vis[u]) continue;vis[u]=1;
    		for(int i=head[u],v;i;i=e[i].nxt)
    			if(dis[v=e[i].v]>dis[u]+e[i].w) q.push(make_pair(-(dis[v]=dis[u]+e[i].w),v));
    	}
    }
    

    struct重载:

    struct node{
        int dis,pos;
        node():dis(0),pos(0){}//无参数初始化
        node(int a,int b):dis(a),pos(b){}//带参初始化
        bool operator <(const node &x)const{return x.dis<dis;}//从小到大
    }
    

    spfa判负环

    queue<int>q;
    int dis[N],len[N];bool vis[N];
    bool spfa(int s){
    	for(int i=1;i<=n;++i) dis[i]=inf,vis[i]=0,len[i]=-1;
    	while(!q.empty()) q.pop();
    	dis[s]=len[s]=0,q.push(s),vis[s]=1;
    	while(!q.empty()){
    		int u=q.front();q.pop(),vis[u]=0;
    		if(len[u]>=n) return 1;
    		for(int i=head[u],v;i;i=e[i].nxt)
    			if(dis[v=e[i].v]>dis[u]+e[i].w)
    				dis[v]=dis[u]+e[i].w,len[v]=len[u]+1,(!vis[v])?(vis[v]=1,q.push(v),1):1;
    	}
    	return 0;
    }
    

    floyd

    (f[i][j]):从(i)号顶点到(j)号顶点只经过前(k)号点的最短路程

    (k)是阶段 所以必须位于最外层 而(i和j)为附加状态

    for (k=1;k<=n;k++)
      for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
          f[i][j] = min(f[i][j],f[i][k]+f[k][j]);
    

    给一个正权无向图,找一个最小权值和的环

    这一定是一个简单环 考虑环上编号最大的结点(u)

    (f[u-1][x][y])((u,x), (u,y))共同构成了环。

    在 Floyd 的过程中枚举(u),计算这个和的最小值即可

    有向图的最小环问题 可枚举起点(s=1sim n) 执行对优化的(Dijsktra)求解单源最短路径(s)一定为第一个被从堆中取出节点 扫描(s)所有出边 扩展、更新完成后 令(d[s]=+infty) 然后继续求解 当s第二次被从堆中取出时 (d[s])就是经过点(s)的最小环长度

    POJ1734 sightseeing trip

    找最小环 并输出一个最小环方案

    int n,m,mp[N][N],dis[N][N],pos[N][N];
    vector<int>path;
    void get_path(int x,int y){
       	if(!pos[x][y]) return;
       	get_path(x,pos[x][y]);
       	path.push_back(pos[x][y]);
       	get_path(pos[x][y],y);
    }
       
    int main(){
    	scanf("%d%d",&n,&m);
       	int ans=inf;
       	memset(mp,inf,sizeof(mp));
       	for(int i=1;i<=n;++i) mp[i][i]=0;
       	for(int i=1,x,y,w;i<=m;++i)
       		scanf("%d%d%d",&x,&y,&w),mp[x][y]=mp[y][x]=w;
       	memcpy(dis,mp,sizeof(mp));
       	for(int k=1;k<=n;++k){
       		for(int i=1;i<k;++i)
       			for(int j=i+1;j<k;++j)
       				if((long long)dis[i][j]+mp[i][k]+mp[k][j]<ans){
       					ans=dis[i][j]+mp[i][k]+mp[k][j];
       					path.clear(),path.push_back(i);
       					get_path(i,j);path.push_back(j),path.push_back(k);
       				}
       		for(int i=1;i<=n;++i)
       			for(int j=1;j<=n;++j)
       			if(dis[i][j]>dis[i][k]+dis[k][j]) dis[i][j]=dis[i][k]+dis[k][j],pos[i][j]=k;
       	}
       	if(ans==inf) return puts("No solution."),0;
       	for(int i=0;i<path.size();++i) printf("%d ",path[i]);
       	return 0;
    }
    
    传递闭包

    已知一个有向图中任意两点之间是否有连边,要求判断任意两点是否连通

    按照 Floyd 的过程,逐个加入点判断一下。

    只是此时的边的边权变为 (1/0),而取 (min)变成了运算。

    再进一步用 bitset 优化,复杂度可以到 (Oleft(dfrac{n^3}w ight))

    for (k=1;k<=n;k++)
    	for (i=1;i<=n;i++)
        	for (j=1;j<=n;j++)
            	 f[i][j]|=f[i][k]&f[k][j];
    
       // std::bitset<SIZE> f[SIZE];
    for (k = 1; k <= n; k++)
    	for (i = 1; i <= n; i++)
            if (f[i][k]) f[i] = f[i] & f[k];
    

    输出方案

    开一个pre数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。

    比如 Floyd 就要记录pre[i][j] = k;,Bellman-Ford 和 Dijkstra 一般记录 pre[v] = u

    树上问题

    LCA

    倍增

    void dfs(int u,int ff){
        for(int i=head[u],v;i;i=e[i].nxt)
            if((v=e[i].v)!=ff) f[v][0]=u,dep[v]=dep[u]+1,dfs(v,u);
    }
    void doubling(){
        for(int j=1;j<=20;++j)
            for(int i=1;i<=n;++i)
                	if(dep[i]>=1<<j) f[i][j]=f[f[i][j-1]][j-1];
    }
    int LCA(int x,int y){
        if(dep[x]>dep[y]) swap(x,y);
        for(int i=20;i>=0;--i)
            if(dep[f[y][i]]>=dep[x]) y=f[y][i];
        if(x==y) return x;
        for(int i=20;i>=0;--i)
            if(f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    

    仓鼠找sugar:如果两条路径相交 那么一定有一条路径的LCA在另一条路径上 判断一个点(x)是否在路径(s->t)上:(dep[x]>=dep[LCA(s,t)],LCA(s,x)=x或LCA(t,x)=x)

    树的直径

    两遍dfs

    int s,mxl,len[N];
    void dfs(int u,int ff){
        if(len[u]>mxl) s=u,mxl=f[u];
        for(int i=head[u],v;i;i=e[i].nxt)
            if((v=e[i].v)!=ff) len[v]=len[u]+e[i].w,dfs(v,u);
    }
    int main(){
        ......
        memset(len,0,sizeof(len)),mxl=-1,dfs(1,0);
        memser(len,0,sizeof(len)),mxl=-1,dfs(s,0);
        return 0;
    }
    

    树形dp

    int mxl,f[N][2];
    void dfs(int u,int ff){
        f[u][0]=f[u][1]=0;
        for(int i=head[u],v,dis;i;i=e[i].nxt)
         	if((v=e[i].v)!=ff){
                dfs(v,u);
                dis=f[v][0]+e[i].w;
                if(dis>f[u][0]) f[u][1]=f[u][0],f[u][0]=dis;
                else if(dis>f[u][1]) f[u][1]=dis;
                mxl=max(mxl,f[u][0]+f[u][1]);
            }
    }
    

    树的重心

    以树的重心为根时,所有的子树的大小都不超过整个树大小的一半

    找到一个点,其所有的子树节点数最少,那么这个点就是这棵树的重心

    可通过两次dfs求出,第一遍求出每个点的子树(sz_x) 第二遍找出使(max_{vin son_u}{n-sz_u,sz_v})最小的节点

    性质:树中所有点到某个点的距离和中,到中心的距离和是最小的;若有两个重心,那么他们的距离和都一样

    int ans,siz=inf;
    void dfs(int u,int ff){
        sz[u]=1;int res=0;
        for(int i=head[u],v;i;i=e[i].nxt)
            if((v=e[i].v)!=ff) dfs(v,u),sz[u]+=sz[v],res=max(res,son[v]);
        res=max(res,n-sz[u]);
        if(res<siz) ans=u,siz=res;
    }
    

    树上差分

    最小生成树

    kruskal

    int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
    void kruskal(){
    	for(int i=1;i<=n;++i) f[i]=i;
    	for(int i=1,u,v,cnt=0;i<=m;++i)
    	if(find(u=e[i].u)!=find(v=e[i].v)){
    		f[f[u]]=f[v],sum+=e[i].w;
    		if(++cnt==n-1) return;
    	}
    }
    

    prim

    priority_queue<pii,vector<pii>,greater<pii> >q;
    void prim(){
    	memset(vis,0,sizeof(vis)),memset(dis,inf,sizeof(dis));
    	q.push(make_pair(dis[1]=0,1));
    	while(!q.empty()){
    		int u=q.top().second;q.pop();
    		if(vis[u]) continue;
    		ans+=dis[u],vis[u]=1;
    		for(int i=head[u],v;i;i=e[i].nxt)
    			if(!vis[v=e[i].v]&&dis[v]>e[i].w) q.push(make_pair(dis[v]=e[i].w,v));
    	}
    }
    

    tarjan

    有向图

    int idx=0,Bcnt=0,St[N],dfn[N],low[N],bl[N],sz[N];bool inst[N];
    void tarjan(int u){
    	dfn[u]=low[u]=++idx,St[++St[0]]=u,inst[u]=1;
    	for(int i=head[u],v;i;i=e[i].nxt)
    	if(!dfn[v=e[i].v]) tarjan(v),low[u]=min(low[u],low[v]);
    	else if(inst[v]&&dfn[v]<low[u]) low[u]=dfn[v];
    	if(dfn[u]==low[u]){
    		int v;++Bcnt;
    		do{
    			inst[v=St[St[0]--]]=0,bl[v]=Bcnt,++sz[Bcnt];
    		}while(u!=v);
    	}
    }
    

    欧拉图

    欧拉图中所有顶点的度数都是偶数 若(G)是欧拉图,则它是若干个边不重的圈的并

    匈牙利算法

    int k,n,m,ans,match[N];
    double link[N][N],vis[N];
    bool dfs(int x){
        for(int i=1;i<=n;++i){//扫描每个男生 
            if(link[x][i]&&!vis[i]){//如果能连接 并且i不在当前匈牙利树中 
                vis[i]=1;
                if(!match[i]||dfs(match[i])){match[i]=x;return 1;}
                //名花无主或者能腾出空位置来 
            } 
        }
        return 0;
    }
    
    int main(){
        while(scanf("%d",&k)!=EOF&&k){
            memset(link,0,sizeof(link)),memset(match,0,sizeof(match));
            rd(m),rd(n),ans=0;
            for(int i=1,x,y;i<=k;++i) rd(x),rd(y),link[x][y]=1;
            for(int i=1;i<=m;++i){
                memset(vis,0,sizeof(vis));
                if(dfs(i)) ++ans;
            }
            printf("%d
    ",ans);
        } 
        return 0;
    }
    

    网络流

    最大流

    queue<int> q;bool vis[N];
    bool bfs(){
        while(!q.empty()) q.pop();
        memset(vis,0,sizeof(vis));
        q.push(s),vis[s]=1,incf[s]=inf;
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=head[u],v,w;i;i=e[i].nxt)
            if(w=e[i].w&&!vis[v=e[i].v]){
                incf[v]=Min(incf[u],w),pre[v]=i;
                q.push(v),vis[v]=1;
                if(v==t) return 1;
            }
        }
        return 0;
    }
    
    void upd(){
        int x=t;
        while(x!=s){
            int i=pre[x];
            e[i].w-=incf[t],e[i^1].w+=incf[t],x=e[i^1].v;
        }
        maxflow+=incf[t];
    }
    
    int main(){
        rd(n),rd(m),rd(s),rd(t);
        for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,0);
        while(bfs()) upd();
        printf("%d",maxflow);
        return 0;
    }
    

    最小费用最大流

    int head[N],tot=1;
    struct edge{int v,flo,cos,nxt;}e[M<<1];
    void add(int u,int v,int flo,int cos){
        e[++tot]=(edge){v,flo,cos,head[u]},head[u]=tot;
        e[++tot]=(edge){u,0,-cos,head[v]},head[v]=tot;
    }
    
    int dis[N];
    queue<int>q;bool vis[N];
    bool spfa(){
        memset(vis,0,sizeof(vis)),memset(dis,inf,sizeof(dis));
        q.push(s),vis[s]=1,dis[s]=0,incf[s]=inf;
        while(!q.empty()){
            int u=q.front();q.pop(),vis[u]=0;
            for(int i=head[u],v,flo,cos;i;i=e[i].nxt)
            if((flo=e[i].flo)&&dis[v=e[i].v]>dis[u]+(cos=e[i].cos)){
                dis[v]=dis[u]+cos,pre[v]=i,incf[v]=Min(incf[u],flo);
                if(!vis[v]) q.push(v),vis[v]=1;
            }
        }
        return dis[t]!=inf;
    }
    void upd(){
        int x=t;
        while(x!=s){
            int i=pre[x];
            e[i].flo-=incf[t],e[i^1].flo+=incf[t],x=e[i^1].v;
        }
        mxflo+=incf[t],mncos+=incf[t]*dis[t];
    }
    
    int main(){
        rd(n),rd(m),rd(s),rd(t);
        for(int i=1,u,v,w,z;i<=m;++i) rd(u),rd(v),rd(w),rd(z),add(u,v,w,z);
        while(spfa()) upd();
        printf("%d %d",mxflo,mncos);
        return 0;
    }
    

    数论

    扩欧

    void exgcd(ll a,ll b,ll &x,ll &y){
        if(!b){x=1,y=0;return;}
        exgcd(b,a%b,x,y);
        ll t=x;x=y,y=t-(a/b)*y;
    }
    
    void exgcd(int a,int b,int &d,int &x,int &y){
        if(b) exgcd(b,a%b,d,y,x),y-=x*(a/b);
        else d=a,x=1,y=0;
    }
    

    中国剩余定理

    欧拉筛

    void prime(int N){
        memset(prime,0,sizeof(prime));
        memset(v,0,sizeof(v));
        for(int i=2;i<=N;++i){
            if(!v[i]) v[i]=i,prime[++num_prime]=i;
            for(int j=1;j<=num_prime&&i*prime[j]<=n;++j)
                v[i*prime[j]]=prime[j];
                if(!(i%prime[j])) break;
        }
    }
    

    BSGS

    SDOI2011 计算器

    1、给定y、z、p,计算y^z mod p 的值;

    2、给定y、z、p,计算满足xy ≡z(mod p)的最小非负整数x;

    3、给定y、z、p,计算满足y^x ≡z(mod p)的最小非负整数x。

    第一个要求直接快速幂

    第二个要求因为保证P为质数 直接费马小定理求逆元然后*z

    第三个就是BSGS模板

    const int N=10000+5,M=20000+5,INF=1e9+7,inf=0x3f3f3f3f;
    int y,z,p;
    int qpow(int a,int b){
    	int res=1;
    	while(b){
    		if(b&1) res=(ll)a*res%p;
    		a=(ll)a*a%p,b>>=1;
    	}
    	return res;
    }
    
    map<int,int>hash;
    int BSGS(){
    	hash.clear();y%=p,z%=p;
    	if(!y) return -1;
    	int t=(int)sqrt(p)+1;
    	for(int j=0,val;j<t;++j)
    		val=(ll)z*qpow(y,j)%p,hash[val]=j;
    	y=qpow(y,t);
    	if(!y) return !z?1:-1;
    	for(int i=0,val,j;i<=t;++i){
    		val=qpow(y,i);
    		j=hash.find(val)==hash.end()?-1:hash[val];
    		if(j>=0&&i*t-j>=0) return i*t-j;
    	}
    	return -1;
    }
    
    void work2(){
    	if(!(y%p)&&z%p) puts("Orz, I cannot find x!");
    	else printf("%lld
    ",(ll)qpow(y,p-2)*z%p);
    }
    void work3(){
    	int ans=BSGS();
    	if(ans==-1) puts("Orz, I cannot find x!");
    	else printf("%d
    ",ans);
    }
    
    int main(){
    	int T,K;rd(T),rd(K); 
    	while(T--){
    		rd(y),rd(z),rd(p);
    		if(K==1) printf("%d
    ",(qpow(y,z))%p);
    		else if(K==2) work2();
    		else work3();
    	}
    	return 0;
    }
    

    贪心

    数据结构

    并查集

    银河英雄传说

    如果是询问指令,输出一行,仅包含一个整数,表示在同一列上第(i)号战舰与第(j)号战舰之间布置的战舰数目。如果第(i)号战舰与第(j)号战舰当前不在同一列上,则输出(-1) 带权并查集

    int d[N],sz[N];
    int find(int x){
        if(x==f[x]) return x;
        int rt=find(f[x]);
        d[x]+=d[f[x]];
        return f[x]=rt;
    }
    void Merge(int x,int y){f[x=find(x)]=y=find(y),d[x]=sz[y],sz[y]+=sz[x];}
    
    int main(){
        int T,x,y;char opt[5];
        rd(T);
        for(int i=1;i<=30000;++i) f[i]=i,sz[i]=1;
        while(T--){
            scanf("%s",opt);rd(x),rd(y);
            if(opt[0]=='M') Merge(x,y);
            else{
                if(find(x)!=find(y)) puts("-1");
                else printf("%d
    ",Abs(d[x]-d[y])-1);
            }
        }
        return 0;
    }
    

    按轶合并

    int size[N];  //记录子树的大小
    void Union(int x, int y) {
      int xx = find(x),yy = find(y);
      if (xx == yy) return;
      if (size[xx] > size[yy]) swap(xx, yy);
      fa[xx] = yy,size[yy] += size[xx];
    }
    

    食物链

    开三倍x 自身 x+2n 猎物 x+3n 天敌

    int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
    void Union(int x,int y) {f[find(y)]=find(x);}
    int main(){
        rd(n),rd(k);
        for(int i=1;i<=n*3;++i) f[i]=i;
        for(int i=1,op,x,y;i<=k;++i){
            rd(op),rd(x),rd(y);
            if(x>n||y>n) {++ans;continue;}
            if(op==1){
                if(find(x+n)==find(y)||find(x+2*n)==find(y)) ++ans;//为猎物 为天敌 
                else Union(x,y),Union(x+n,y+n),Union(x+2*n,y+2*n);
            }
            else{
                if(x==y) {++ans;continue;}
                if(find(x)==find(y)||find(x)==find(y+n)) ++ans;//为同类  为猎物
                else Union(x,y+2*n),Union(x+n,y),Union(x+2*n,y+n);
            }
        }
        printf("%d",ans);
        return 0;
    }
    

    树状数组

    (lowbit(x))表示非负整数(x)在二进制表示下“最低位的(1)及其后面所有的(0)”所构成的数值

    逆序对

    struct node{int w,id;}a[N],b[N];
    bool cmp(node x,node y){return x.w<y.w;}
    
    int query(int x){int ret=0;while(x>0)ret=(ret+t[x])%P,x-=(x&(-x));return ret;}
    void upd(int x){while(x<=n)++t[x],x+=(x&(-x));}
    
    int main(){
        rd(n);
        for(int i=1;i<=n;++i) rd(a[i].w),a[i].id=i;
        for(int i=1;i<=n;++i) rd(b[i].w),b[i].id=i;
        sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);
        for(int i=1;i<=n;++i) c[a[i].id]=b[i].id;
        for(int i=n;i;--i)
            upd(c[i]),ans=(ans+query(c[i]-1))%P;
        printf("%d",ans);
        return 0;
    }
    

    noip2013火柴排队 mergesort

    c[a[i].id]=b[i].id得理解 (c[i])(i)对应(a)中第(i)小的数的位置,(c[i])对应(b)中第(i)小的数的位置 排完序后(c[i]=i)(a)中第(i)小的数的位置与中第(i)小的数的位置相同

    struct node{int w,id;}a[N],b[N];
    bool cmp(node x,node y){return x.w<y.w;}
    void mergesort(int l,int r){
        if(l>=r) return;
        int mid=l+r>>1,pl=l,pr=mid+1,k=l;
        mergesort(l,mid),mergesort(mid+1,r);
        while(pl<=mid&&pr<=r)
            if(c[pl]<=c[pr]) rk[k++]=c[pl++];
            else rk[k++]=c[pr++],ans=(ans+mid-pl+1)%P;
        while(pl<=mid) rk[k++]=c[pl++];
        while(pr<=r) rk[k++]=c[pr++];
        for(int i=l;i<=r;++i) c[i]=rk[i];
    }
    
    int main(){
        rd(n);
        for(int i=1;i<=n;++i) rd(a[i].w),a[i].id=i;
        for(int i=1;i<=n;++i) rd(b[i].w),b[i].id=i;
        sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);
        for(int i=1;i<=n;++i) c[a[i].id]=b[i].id;
        mergesort(1,n);
        printf("%d",ans);
        return 0;
    }
    

    ST表

    询问区间最值

    int query(int x,int y){
        int s=lg[y-x+1];
        return max(f[x][s],f[y-cm[s]+1][s]);
    }
    int main(){
        for(int i=1;i<=n;++i) rd(a[i]);lg[0]=-1,cm[0]=1;
        for(int i=1;i<=20;++i) cm[i]=cm[i-1]<<1;
    	for(int i=1;i<=n;++i) f[i][0]=a[i],lg[i]=lg[i>>1]+1;
    	for(int j=1;j<=20;++j)
    		for(int i=1;i+cm[j]-1<=n;++i) f[i][j]=Max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    }
    

    线段树

    [SP1716 GSS3 - Can you answer these queries III]

    动态查询最大子段和+单点修改

    struct SegmentTree{int sum,lmx,rmx,mxs;}tree[N];
    void pushup(int o){
        tree[o].sum=tree[lson].sum+tree[rson].sum;
        tree[o].lmx=Max(tree[lson].lmx,tree[lson].sum+tree[rson].lmx);
        tree[o].rmx=Max(tree[rson].rmx,tree[rson].sum+tree[lson].rmx);
        tree[o].mxs=Max(Max(tree[lson].mxs,tree[rson].mxs),tree[lson].rmx+tree[rson].lmx);
    }
    void buildtree(int o,int l,int r){
        if(l==r){tree[o].sum=tree[o].lmx=tree[o].rmx=tree[o].mxs=a[l];return;}
        int mid=l+r>>1;
        buildtree(lson,l,mid),buildtree(rson,mid+1,r);
        pushup(o);
    }
    
    void modify(int o,int l,int r,int x,int k){
        if(l==r){tree[o].sum=tree[o].lmx=tree[o].rmx=tree[o].mxs=k;return;}
        int mid=l+r>>1;
        if(x<=mid) modify(lson,l,mid,x,k);
        else modify(rson,mid+1,r,x,k);
        pushup(o);
    }
    
    SegmentTree query(int o,int l,int r,int x,int y){
        if(x<=l&&r<=y) return tree[o];
        int mid=l+r>>1;
        if(y<=mid) return query(lson,l,mid,x,y);
        else if(x>mid) return query(rson,mid+1,r,x,y);
        else{
            SegmentTree ls,rs,ans;
            ls=query(lson,l,mid,x,y),rs=query(rson,mid+1,r,x,y);
            ans.sum=ls.sum+rs.sum;
            ans.lmx=Max(ls.lmx,ls.sum+rs.lmx);
            ans.rmx=Max(rs.rmx,rs.sum+ls.rmx);
            ans.mxs=Max(Max(ls.mxs,rs.mxs),ls.rmx+rs.lmx);
            return ans;
        }
    }
    
    int main(){
        rd(n);
        for(int i=1;i<=n;++i) rd(a[i]);
        buildtree(1,1,n);rd(q);
        for(int i=1,x,y,op;i<=q;++i){
            rd(op),rd(x),rd(y);
            if(op==1) printf("%d
    ",query(1,1,n,x,y).mxs);
            else modify(1,1,n,x,y);
        }
        return 0;
    }
    

    主席树

    又理解了一遍== 发现以前只是在背代码 压根不太理解

    主席树的主要思想就是:保存每次插入操作时的历史版本,以便查询区间第k小

    看图好理解嘿嘿嘿嘿 其实脑抽理解了好久....

    只更改了 (O(log n))个结点,形成一条链,也就是说每次更改的结点数 = 树的高度。

    我们把问题简化一下:每次求 ([1,r])区间内的k小值。
    怎么做呢?只需要找到插入 r 时的根节点版本,然后用普通权值线段树(有的叫键值线段树/值域线段树)做就行了。

    那么这个相信大家很简单都能理解,把问题扩展到原问题——求([l,r])区间k小值。
    这里我们再联系另外一个知识理解: 前缀和
    这个小东西巧妙运用了区间减法的性质,通过预处理从而达到 回答每个询问。

    那么我们阔以发现,主席树统计的信息也满足这个性质。
    所以……如果需要得到([l,r])的统计信息,只需要用([1,r])的信息减去([1,l-1])的信息就行了。

    那么至此,该问题解决!

    const int N=2e5+5,M=100+5,inf=0x3f3f3f3f;
    int n,m,a[N],w[N],tot=0,tl,rt[N];
    struct SegmentTree{int lc,rc,sum;}t[N*50];
    void pup(int o){t[o].sum=t[t[o].lc].sum+t[t[o].rc].sum;}
    void upd(int &o,int l,int r,int pre,int k){
    	o=++tot;
    	if(l==r){t[o].sum=t[pre].sum+1;return;}
    	int mid=l+r>>1;
    	if(k<=mid) upd(t[o].lc,l,mid,t[pre].lc,k),t[o].rc=t[pre].rc;
    	else upd(t[o].rc,mid+1,r,t[pre].rc,k),t[o].lc=t[pre].lc;
    	pup(o);
    }
    int query(int l,int r,int x,int y,int k){
    	if(l==r) return l;
    	int mid=l+r>>1,ss=t[t[y].lc].sum-t[t[x].lc].sum;
    	if(ss>=k) return query(l,mid,t[x].lc,t[y].lc,k);
    	else return query(mid+1,r,t[x].rc,t[y].rc,k-ss);
    }
    
    int main(){
    	rd(n),rd(m);
    	for(int i=1;i<=n;++i) rd(a[i]),w[i]=a[i];
    	sort(a+1,a+n+1);
    	tl=unique(a+1,a+n+1)-a-1;
    	for(int i=1;i<=n;++i){
    		w[i]=lower_bound(a+1,a+tl+1,w[i])-a;
    		upd(rt[i],1,tl,rt[i-1],w[i]);
    	}
    	for(int i=1,l,r,k;i<=m;++i)
    		rd(l),rd(r),rd(k),printf("%d
    ",a[query(1,tl,rt[l-1],rt[r],k)]);
    	return 0;
    }
    

    平衡树

    手写版

    void pushup(int x){size[x]=size[ch[x][0]]+size[ch[x][1]]+cnt[x];}
    int chk(int x){return ch[par[x]][1]==x;}
    
    void rotate(int x){
        int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
        ch[y][k]=w,par[w]=y;
        ch[z][chk(y)]=x,par[x]=z;
        ch[x][k^1]=y,par[y]=x;
        pushup(y),pushup(x);
    }
    void splay(int x,int goal){
        while(par[x]!=goal){
            int y=par[x],z=par[y];
            if(z!=goal) (chk(x)==chk(y))?rotate(y):rotate(x);
            rotate(x);
        }
        if(!goal) root=x;
    }
    void find(int x){//将最大的小于等于x的数所在的节点splay到根
        int cur=root;
        while(ch[cur][x>val[cur]]&&val[cur]!=x) cur=ch[cur][x>val[cur]];
        splay(cur,0);
    }
    void insert(int x){
        int cur=root,f=0;
        while(cur&&val[cur]!=x) f=cur,cur=ch[cur][x>val[cur]];//当u存在并且没有移动到当前的值
        if(cur) ++cnt[cur];
        else{
            cur=++tot;
            if(f) ch[f][x>val[f]]=cur;
            par[cur]=f,val[cur]=x;
            size[cur]=cnt[cur]=1;
            ch[cur][0]=ch[cur][1]=0;
        }
        splay(cur,0);
    }
    int kth(int k){
        int cur=root;
        while(1){
            if(ch[cur][0]&&k<=size[ch[cur][0]]) cur=ch[cur][0];
            else if(k>size[ch[cur][0]]+cnt[cur]) k-=size[ch[cur][0]]+cnt[cur],cur=ch[cur][1];
            else return cur;
        }
    }
    int Nxt(int x,int fla){
        find(x);
        int cur=root;
        if(val[cur]>x&&fla) return cur;
        if(val[cur]<x&&!fla) return cur;
        cur=ch[cur][fla];
        while(ch[cur][fla^1]) cur=ch[cur][fla^1];
        return cur;
    }
    void remove(int x){
        int pre=Nxt(x,0),nxt=Nxt(x,1);
        splay(pre,0),splay(nxt,pre);
        int del=ch[nxt][0];
        if(cnt[del]>1) --cnt[del],splay(del,0);
        else ch[nxt][0]=0;
    }
    
    int getrank(int x){
        find(x);
        return size[ch[root][0]];
    }
    
    int main(){
        rd(n);
        insert(INF),insert(-INF);
        while(n--){
            rd(op),rd(x);
            if(op==1) insert(x);
            else if(op==2) remove(x);
            else if(op==3) printf("%d
    ",getrank(x));
            else if(op==4) printf("%d
    ",val[kth(x+1)]);
            else if(op==5) printf("%d
    ",val[Nxt(x,0)]);
            else if(op==6) printf("%d
    ",val[Nxt(x,1)]);
        }
        return 0;
    }
    

    vector实现

    vector<int>vec;
    int main(){
        rd(n);
        while(n--){
            int op,x;
            rd(op),rd(x);
            if(op==1) vec.insert(upper_bound(vec.begin(),vec.end(),x),x);
            else if(op==2) vec.erase(lower_bound(vec.begin(),vec.end(),x));
            else if(op==3) printf("%d
    ",lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1);
            else if(op==4) printf("%d
    ",vec[x-1]);
            else if(op==5) printf("%d
    ",*(--lower_bound(vec.begin(),vec.end(),x)));
            else if(op==6) printf("%d
    ",*upper_bound(vec.begin(),vec.end(),x));
        }
        return 0;
    }
    

    multiset实现

    distance(pos1,pos2)返回一个int值为pos1与pos2之间的距离,pos1,pos2为指向同一容器的迭代器。 时间复杂度:O(N)

    advance(pos,k)一个void的函数,让pos这个迭代器前进k步 时间复杂度:O(N)

    set.equal_range(x),它返回的一队迭代器,因此是pair类型,定义时需注意。

    multiset<int>mul;
    int main(){
        rd(n);
        multiset<int>::iterator it;
        while(n--){
            rd(op),rd(x);
            if(op==1) mul.insert(x);
            else if(op==2) mul.erase(mul.lower_bound(x));
            else if(op==3) printf("%d
    ",distance(mul.begin(),mul.lower_bound(x))+1);
            else if(op==4) it=mul.begin(),advance(it,x-1),printf("%d
    ",*it);
            else if(op==5) printf("%d
    ",*(--mul.lower_bound(x)));
            else if(op==6) printf("%d
    ",*(mul.upper_bound(x)));
        }
        return 0;
    }
    

    莫队

    小Z数袜子

    struct node{int l,r,id,bl;}q[N];
    bool cmp(node A,node B){return (A.bl^B.bl)?A.bl<B.bl:((A.bl&1)?A.r<B.r:A.r>B.r);}
    //bool cmp(node A,node B){return A.bl==B.bl?A.r<B.r:A.bl<B.bl;}
    void count(int x,int add){
        Ans-=(cnt[a[x]]*cnt[a[x]]),cnt[a[x]]+=add;
        Ans+=(cnt[a[x]]*cnt[a[x]]);
    }
    ll gcd(ll a,ll b){
        if(a>b) swap(a,b);
        return !a?b:gcd(b%a,a);
    }
    int main(){
        rd(n),rd(m),block=sqrt(n);
        for(int i=1;i<=n;++i) rd(a[i]);
        for(int i=1;i<=m;++i) rd(q[i].l),rd(q[i].r),q[i].id=i,q[i].bl=(q[i].l-1)/block+1;
        sort(q+1,q+m+1,cmp);
        int l=1,r=0;Ans=0ll;
        for(int i=1;i<=m;++i){
            while(l<q[i].l) count(l++,-1);
            while(l>q[i].l) count(--l,1);
            while(r<q[i].r) count(++r,1);
            while(r>q[i].r) count(r--,-1);
            if(q[i].l==q[i].r) {aa[q[i].id]=0,ab[q[i].id]=1;continue;}
            ll x=q[i].r-q[i].l+1;
            aa[q[i].id]=Ans-x,ab[q[i].id]=x*(x-1);
        }
        for(int i=1;i<=m;++i){
            ll x=gcd(aa[i],ab[i]);
            if(x>0) printf("%lld/%lld
    ",aa[i]/x,ab[i]/x);
            else printf("%lld/%lld
    ",aa[i],ab[i]);
        }
        return 0;
    }
    

    树剖

    void dfs1(int u,int ff){
    	dep[u]=dep[fa]+1,f[u]=ff,sz[u]=1;
    	for(int i=head[u],v,mxs=-1;i;i=e[i].nxt)
    	if((v=e[i].v)!=ff)
    		dfs1(v,u),sz[u]+=sz[v],(sz[v]>mxs)?(son[u]=v,mxs=sz[v],1):1;
    }
    void dfs2(int u,int topf){
    	id[dfn[u]=++idx]=u,top[u]=topf;
    	if(!son[u]) return;
    	dfs2(son[u],topf);
    	for(int i=head[u],v;i;i=e[i].nxt)
    		if((v=e[i].v)!=ff&&v!=son[u]) dfs2(v,v);
    }
    int LCA(int x,int y){
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            x=f[top[x]];
        }
        return dep[x]<dep[y]?x:y;
    }
    int qrange(int x,int y){
    	int ret=0;
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]]) swap(x,y);
    		ret=(ret+query(1,1,n,dfn[top[x]],dfn[x]))%P,x=f[top[x]];
    	}
    	if(dep[x]>dep[y]) swap(x,y);
    	ret=(ret+query(1,1,n,dfn[x],dfn[y]));
    	return ret;
    }
    int qson(int x){return query(1,1,n,dfn[x],dfn[x]+sz[x]-1);}
    
    

    换根

    int findc(int x,int y){
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]]) swap(x,y);
    		if(f[top[x]]==y) return top[x];
    		x=f[top[x]];
    	}
    	return dep[x]<dep[y]?son[x]:son[y];
    }
    int qson(int x){
    	if(x==rt) return query(1,1,idx,1,idx);
    	int lca=LCA(x,rt);
    	if(x!=lca) return query(1,1,n,dfn[x],dfn[x]+sz[x]-1);
    	int chi=findc(x,rt);
    	return query(1,1,b,1,n)-query(1,1,n,dfn[chi],dfn[chi]+sz[chi]-1);
    }
    

    ddp

    给定一棵(n)个点的树,点带点权。

    (m)操作,每次操作给定(x,y),表示修改点(x)的权值为(y) 在每次操作之后求出这棵树的最大权独立集的权值大小。

    ll w[N],f[N][2];
    int head[N],tot=0;
    struct edge{int v,nxt;}e[N<<1];
    void add(int u,int v){e[++tot]=(edge){v,head[u]},head[u]=tot;}
    
    int idx=0,dfn[N],id[N],fa[N],son[N],top[N],bot[N],sz[N];
    void dfs1(int u,int ff){
        fa[u]=ff,sz[u]=1;
        for(int i=head[u],v,mxs=-1;i;i=e[i].nxt){
            if((v=e[i].v)==ff) continue;
            dfs1(v,u),sz[u]+=sz[v];
            if(mxs<sz[v]) mxs=sz[v],son[u]=v;
        }
    }
    void dfs2(int u,int topf){
        dfn[u]=++idx,id[idx]=u,top[u]=topf;
        if(!son[u]) {bot[u]=u;return;}
        dfs2(son[u],topf),bot[u]=bot[son[u]];
        for(int i=head[u],v;i;i=e[i].nxt)
            if((v=e[i].v)!=fa[u]&&v!=son[u]) dfs2(v,v);
    }
    void dfs(int u){
        f[u][0]=0,f[u][1]=w[u];
        for(int i=head[u],v;i;i=e[i].nxt) 
            if((v=e[i].v)!=fa[u]){
                dfs(v);
                f[u][0]+=Max(f[v][1],f[v][0]),f[u][1]+=f[v][0];
            }
    }
    
    struct Matri{
        ll a[2][2];
        Matri operator*(const Matri &X)const{
            Matri c;
            memset(c.a,0,sizeof(c.a));
            for(int i=0;i<=1;++i)
                for(int j=0;j<=1;++j)
                    for(int k=0;k<=1;++k)
                        c.a[i][j]=Max(c.a[i][j],a[i][k]+X.a[k][j]);
            return c;
        }
    }val[N],t[N<<2],ans;
    void pup(int o){t[o]=t[ls]*t[rs];}
    void mdf(int o,int l,int r,int x){
        if(l==r){t[o]=val[l];return;}
        int mid=l+r>>1;
        if(x<=mid) mdf(ls,l,mid,x);
        else mdf(rs,mid+1,r,x);
        pup(o);
    }
    Matri query(int o,int l,int r,int x,int y){
        if(x<=l&&r<=y) return t[o];
        int mid=l+r>>1;
        if(y<=mid) return query(ls,l,mid,x,y);
        if(x>mid) return query(rs,mid+1,r,x,y);
        return query(ls,l,mid,x,y)*query(rs,mid+1,r,x,y);
    }
    
    void build(int o,int l,int r){
        if(l==r){
            int u=id[l];ll g0=0,g1=w[u];
            for(int i=head[u],v;i;i=e[i].nxt)
                if((v=e[i].v)!=fa[u]&&v!=son[u])
                    g0+=Max(f[v][0],f[v][1]),g1+=f[v][0];
            val[l]=t[o]=(Matri){g0,g0,g1,-inf};
            return;
        }
        int mid=l+r>>1;
        build(ls,l,mid),build(rs,mid+1,r);
        pup(o);
    }
    
    
    void Mdf(int x,int k){
        val[dfn[x]].a[1][0]+=k-w[x],w[x]=k;
        while(x){
            Matri a=query(1,1,n,dfn[top[x]],dfn[bot[x]]),b;
            mdf(1,1,n,dfn[x]);
            b=query(1,1,n,dfn[top[x]],dfn[bot[x]]);
            x=fa[top[x]];if(!x) return;
            int nw=dfn[x];
            ll g0=a.a[0][0],g1=a.a[1][0],f0=b.a[0][0],f1=b.a[1][0];
            val[nw].a[0][0]=val[nw].a[0][1]=val[nw].a[0][0]+Max(f0,f1)-Max(g0,g1),
            val[nw].a[1][0]=val[nw].a[1][0]+f0-g0;
        }
    }
    
    int main(){
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif
        rd(n),rd(m);
        for(int i=1;i<=n;++i) rd(w[i]);
        for(int i=1,u,v;i<n;++i) rd(u),rd(v),add(u,v),add(v,u);
        dfs1(1,0),dfs2(1,1),dfs(1),
        build(1,1,idx);
        for(int i=1,x,y;i<=m;++i){
            rd(x),rd(y);
            Mdf(x,y);
            ans=query(1,1,n,dfn[1],dfn[bot[1]]);
            printf("%d
    ",Max(ans.a[0][0],ans.a[1][0]));
        }
        return 0;
    }
    

    分数规划

    模板:

    (n)个物品中选(k)个使其(frac {sum a[i]}{sum b[i]}) 最大

    double check(double l){
    	double sum=0.0;
    	for(int i=1;i<=n;++i) d[i]=(double)a[i]-l*b[i];
    	sort(d+1,d+n+1);
    	for(int i=n-k+1;i<=n;++i) sum+=d[i];
    	return sum;
    }
    
    int main(){
    	rd(n),rd(k);
    	double l=0.0,r=0.0,mid;
    	for(int i=1;i<=n;++i) rd(a[i]);
    	for(int i=1;i<=n;++i) rd(b[i]),r=Max(r,1.0*a[i]/b[i]);
    	while(r-l>=eps){
    		mid=(l+r)/2;
    		if(check(mid)>0) l=mid;
    		else r=mid;
    	}
    	printf("%.4lf
    ",l);
    	return 0;
    }
    

    desert king 最优比率生成树

    struct node{int x,y,z;}c[N];
    double qdis(int x,int y){return sqrt((double)(1.0*x*x)+(1.0*y*y));}
    
    bool vis[N];
    double sum,dis[N],d[N][N];
    bool check(double mid){
    	for(int i=0;i<=n;++i) dis[i]=1e20,d[i][i]=0.0,vis[i]=0;
    	dis[1]=sum=0.0;
    	for(int i=1;i<=n;++i)
    		for(int j=i+1;j<=n;++j) d[i][j]=d[j][i]=b[i][j]-mid*a[i][j];
    	for(int i=1,u=0;i<=n;++i,u=0){
    		for(int j=1;j<=n;++j)
    			if(!vis[j]&&dis[j]<dis[u]) u=j;
    		vis[u]=1,sum+=dis[u];
    		for(int v=1;v<=n;++v)
    			if(!vis[v]) dis[v]=Min(dis[v],d[u][v]);
    	}
    	return sum>0;
    }
    
    int main(){
    	while(scanf("%d",&n)!=EOF&&n){
    		for(int i=1;i<=n;++i) rd(c[i].x),rd(c[i].y),rd(c[i].z);
    		double l=0.0,r=10.0,mid;
    		for(int i=1;i<=n;++i)
    			for(int j=i+1;j<=n;++j)
    			a[i][j]=a[j][i]=qdis(c[i].x-c[j].x,c[i].y-c[j].y),b[i][j]=b[j][i]=(double)Abs(c[i].z-c[j].z);//,r=Max(r,b[i][j]/a[i][j])
    		while(r-l>=eps){
    			mid=(l+r)/2;
    			if(check(mid)) l=mid;
    			else r=mid;
    		}
    		printf("%.3f
    ",l);
    	}
    	return 0;
    }
    

    sightseeing cows 最优比率环

    找一个环使其点权/边权最大

    每次check找一个正环 正环不好找就将其边权改为负值去找负环

    int cnt[N];bool vis[N];
    queue<int>q;bool vis[N];
    bool spfa(){
        while(!q.empty()) q.pop();
        for(int i=1;i<=n;++i) dis[i]=0.0,cnt[i]=vis[i]=1,q.push(i);//图有可能不连通
        while(!q.empty()){
            int u=q.front();q.pop(),vis[u]=0;
            for(int i=head[u],v;i;i=e[i].nxt)
            if(dis[v=e[i].v]>dis[u]+e[i].w){
                    dis[v]=dis[u]+e[i].w;
                    if(!vis[v]) q.push(v),vis[v]=1,++cnt[v];
                    if(cnt[v]>=n) return 1;
                }
        }
        return 0;
    }
    bool check(double x){
        memset(head,0,sizeof(head)),tot=0;
        for(int i=1;i<=m;++i) add(fr[i],to[i],-((double)a[to[i]]-x*co[i]));
        if(spfa()) return 1;//找到负环
        else return 0;
    }
    
    int main(){
        rd(n),rd(m);
        for(int i=1;i<=n;++i) rd(a[i]);
        for(int i=1;i<=m;++i) rd(fr[i]),rd(to[i]),rd(co[i]);
        double l=0.0,r=3000.0,mid;
        while(r-l>=eps){
            mid=(l+r)/2;
            if(check(mid)) l=mid;
            else r=mid;
        }
        printf("%.2f",l);
        return 0;
    }
    

    Talent Show

    记得初始化值为极小

    double d[N],f[N][M];
    bool check(double mid){
        for(int i=0;i<=n;++i){
            if(i) d[i]=(double)a[i]-mid*b[i];
            for(int j=0;j<=W;++j) f[i][j]=-INF;
        }
        f[0][0]=0.0;
        for(int i=1;i<=n;++i)
        	for(int j=0;j<=W;++j) f[i][j]=Max(f[i][j],f[i-1][j]),
        f[i][Min(W,j+b[i])]=Max(f[i][Min(W,j+b[i])],f[i-1][j]+d[i]);
        return f[n][W]>0;
    }
    
    int main(){
        rd(n),rd(W);
        double l=0.0,r=0.0,mid;
        for(int i=1;i<=n;++i) rd(b[i]),rd(a[i]),r=Max(r,(double)a[i]/b[i]);
        while(r-l>=eps){
            mid=(l+r)/2;
            if(check(mid)) l=mid;
            else r=mid;
        }
        printf("%d",(int)(l*1000));
        return 0;
    }
    

    动态规划

    树形dp

    保安站岗

    每个点有三种状态 自己覆盖自己 被父亲覆盖 被儿子覆盖

    然后要注意被儿子覆盖时的转移 最后如果都是儿子被孙子覆盖的花费更少的话 得选一个儿子自己覆盖自己花费最少的来覆盖 最后输出根节点中自己覆盖自己和被儿子覆盖中较小的一个

    int head[N],tot=0;
    struct edge{int v,nxt,w;}e[N<<1];
    void add(int u,int v){e[++tot].v=v,e[tot].nxt=head[u],head[u]=tot;}
    
    void dp(int u,int ff){
    	f[u][0]=a[u],f[u][1]=f[u][2]=0;
    	bool yes=0;int minc=inf;
    	for(int i=head[u];i;i=e[i].nxt)
    	if((v=e[i].v)!=ff){
    		dp(v,u);
    		f[u][0]+=min(f[v][1],min(f[v][0],f[v][2]));//自己覆盖自己 
    		f[u][1]+=min(f[v][0],f[v][2]);//被父亲覆盖
    		f[u][2]+=min(f[v][0],f[v][2]) ;//被儿子覆盖
    		if(f[v][0]<=f[v][2])  yes=1;
    		else minc=min(minc,f[v][0]-f[v][2]);
    	}
    	if(!yes) f[u][2]+=minc;
    }
    
    int main(){
        rd(n);
        for(rg int i=1;i<=n;++i){
        	rd(i),rd(a[i]),rd(ns);
        	for(rg int j=1;j<=ns;++j) rd(s),add(i,s),add(s,i);
    	}dp(1,0);
    	printf("%d",min(f[1][0],f[1][2]));
        return 0;
    }
    

    区间dp

    染色

    关路灯

    一条路线上安装了n盏路灯,每盏灯的功率(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯

    他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯,可以中间调头

    现在已知老张走的速度为1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。 求耗电最少

    int main(){
        rd(n),rd(s);
        for(int i=1;i<=n;++i) rd(pos[i]),rd(w[i]),sum+=w[i];
        for(int i=1;i<=n;++i)
        	for(int j=i;j<=n;++j) t[i][j]=t[i][j-1]+w[j];
        for(int i=1;i<=n;++i)
        	for(int j=i;j<=n;++j) t[i][j]=sum-t[i][j];
        memset(f,inf,sizeof(f));
        f[s][s][0]=f[s][s][1]=0;
        for(int l=2;l<=n;++l)//枚举长度 
        	for(int i=1,j;i<=n-l+1;++i){//枚举左端点
            	j=l+i-1;
            	f[i][j][0]=Min(f[i+1][j][0]+t[i+1][j]*(pos[i+1]-pos[i]),f[i+1][j][1]+t[i+1][j]*(pos[j]-pos[i]));
            	f[i][j][1]=Min(f[i][j-1][1]+t[i][j-1]*(pos[j]-pos[j-1]),f[i][j-1][0]+t[i][j-1]*(pos[j]-pos[i]));
        }
        printf("%d",Min(f[1][n][0],f[1][n][1]));
        return 0;
    }
    

    数位dp

    不要62

    void pre(){
        for(int i=0;i<=9;++i) f[1][i]=(!(i==4));
        for(int i=2;i<=8;++i)
      	  for(int j=0;j<=9;++j){
        	    if(j==4) {f[i][j]=0;continue;}
            	for(int k=0;k<=9;++k)
            	if(!(k==4||j==6&&k==2)) f[i][j]+=f[i-1][k];
        }
    }
    
    ll solve(ll xx){
        ll p=0,num[20],sum=0;
        while(xx) num[++p]=xx%10,xx/=10;
        num[p+1]=0;
        for(int i=p;i>0;--i){
            for(int j=0;j<num[i];++j)
               if(!(j==2&&num[i+1]==6)) sum+=f[i][j];
            if(num[i]==4||(num[i]==2&&num[i+1]==6)) break;
        }
        return sum;
    }
    
    int main(){
        pre();
        while(scanf("%lld%lld",&a,&b)==2&&a&&b) printf("%lld
    ",solve(b+1)-solve(a));
        return 0;
    }
    

    状压dp

    背包

    01背包

    题中已知条件有物品的重量(w_i),价值(v_i),背包总容量(W)

    f[i][j]为在只能放前(i)个物品的情况下,容量为(j)的背包所能达到最大总价值

    f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);

    滚动数组优化为一维:f[i]表示处理到当前物品时背包容量为(i)的最大价值 f[i]=max(f[i],f[i-w[i]]+v[i])

    for(int i=1;i<=n;++i)
        for(int j=W;j>=w[i];--j)
            f[j]=max(f[j],f[j-w[i]]+v[i]);
    

    完全背包

    即01背包中物品的个数变为无数个

    for(int i=1;i<=n;++i)
        for(int j=w[i];j<=W;++j)
            f[j]=max(f[j],f[j-c[i]]+w[i]);
    

    多重背包

    即01背包中每种物品可选(k_i)

    二进制拆分法

    单调队列

    混合背包

    混合背包就是将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取 次。

    for (循环物品种类) {
      if (是 0 - 1 背包) 套用 0 - 1 背包代码;
      else if (是完全背包) 套用完全背包代码;
      else if (是多重背包) 套用多重背包代码;
    }
    

    二维费用背包

    0-1 背包问题,可是不同的是选一个物品会消耗两种价值(经费、时间)方程基本不用变,只需再开一维数组,同时转移两个价值就行了!(完全、多重背包同理)
    这时候就要注意,再开一维存放物品编号就不合适了,因为容易 MLE。

    for (int k = 1; k <= n; k++) {
      for (int i = m; i >= mi; i--)    //对经费进行一层枚举
        for (int j = t; j >= ti; j--)  //对时间进行一层枚举
          dp[i][j] = max(dp[i][j], dp[i - mi][j - ti] + 1);
    }
    

    分组背包即:物品分组,每组的物品相互冲突,最多只能选一个物品放进去。
    其实就是从“在所有物品中选择一件”变成了“从当前组中选择一件”,于是就对每一组进行一次 0-1 背包就可以了。

    可以将(t[k][i])表示第(k)组的第(i)件物品的编号是多少,再用(cnt_k)表示第(k)组物品有多少个。

    for (int k = 1; k <= ts; k++)          //循环每一组
      for (int i = m; i >= 0; i--)         //循环背包容量
        for (int j = 1; j <= cnt[k]; j++)  //循环该组的每一个物品
          if (i >= w[t[k][j]])
            dp[i] = max(dp[i],
                        dp[i - w[t[k][j]]] + c[t[k][j]]);  //像0-1背包一样状态转移                 
    

    这里要注意: 一定不能搞错循环顺序 ,这样才能保证正确性。

    消失之物

    求失去第(i)个物品装满背包有几种方法

    在转移的时候是(f[v]+=f[v-a[i]])这样统计的体积为(a[i])的贡献值

    int main(){
        rd(n),rd(m);f[0][0]=1;
        for(int i=1;i<=n;++i){
            rd(a[i]),f[0][i]=1;
            for(int v=m;v>=a[i];--v) f[v][0]=(f[v][0]+f[v-a[i]][0])%10;
        }
        for(int i=1;i<=n;++i){
            for(int v=1;v<=m;++v)
           		if(v>=a[i]) f[v][i]=(f[v][0]-f[v-a[i]][i]+10)%10;
            	else f[v][i]=f[v][0];
            for(int v=1;v<=m;++v) printf("%d",f[v][i]);puts("");
        }
        return 0;
    }
    

    POJ3093

    问有多少种方案使得无法装入剩下的任意一个物品

    不能再装即=最小的也装不进去 枚举不在背包中的最小值 然后比它小的肯定都装进去了 比它大的装不进去

    int main(){
        rd(T);
        for(int t=1;t<=T;++t){
            memset(f,0,sizeof(f));
            rd(n),rd(m),mn=inf,ans=sum[0]=0,f[0]=1;
            for(int i=1;i<=n;++i) rd(a[i]);
            sort(a+1,a+n+1);
            for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];
            for(int i=n;i>=1;--i)
            	for(int v=m;v;--v){
          	      if(v-sum[i-1]>=0&&v>m-a[i]) ans+=f[v-sum[i-1]];//加上sum[i-1]的贡献 
            //比它小的都放进去了  比它大的放不进去  
          	      if(v>=a[i]) f[v]+=f[v-a[i]];//统计 
                }
            printf("%d %d
    ",t,ans);
        }
        return 0;
    }
    

    存题

    素数密度

    给定区([L,R](L≤R≤2147483647,R-L≤1000000)),计算区间中素数的个数。

    int cnt=0,ans=0,prime[50000];bool v[50010];
    void primes(){
        for(int i=2;i<=50000;++i){
            if(!v[i]) v[i]=1,prime[++cnt]=i;
            for(int j=1;j<=cnt&&i*prime[j]<=50000;++j){
                v[i*prime[j]]=1;
                if(!(i%prime[j])) break;
            }
        } 
    }
    
    bool a[N];
    int main(){
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif
        primes();
        rd(l),rd(r);
        for(int i=1;(ll)prime[i]*prime[i]<=r&&i<=cnt;++i)
            for(ll j=max(2ll,(l-1)/prime[i]+1)*prime[i];j<=r;j+=prime[i])
            a[j-l]=1;
        for(ll i=l;i<=r;++i) if(!a[i-l]) ++ans;
        printf("%d",ans);
        return 0;
    }
    

    [HAOI2008]圆上整点

    (egin{align*}x^2+y^2&=r^2\y^2&=r^2-x^2\y&=sqrt{(r+x)(r-x)}end{align*}) 令:(d=gcd(r+x,r-x)),则:设(A=frac{r-x}d,B=frac{r+x}d) 因为(d)(r+x,r-x)的最大公约数 所以一定存在(gcd(A,B)=1,A,B)互质

    (A,B)代回柿子 得:(y^2=d^2*A*B) 因为(d^2,y^2)为完全平方数 则(A*B)一定为完全平方数 又(gcd(A,B)=1 herefore A ot=B)(A,B)本身一定为完全平方数

    (A)的算术平方根为(a)(B)的算术平方根为(b) 即:(A=a*a,B=b*b)

    (ecause A ot=B herefore a ot=b)(a<b) 所以(a*a=frac{r-x}d,b*b=frac{r+x}d o a^2+b^2=frac{2r}d)

    通解:(x=dfrac{v^2-u^2}2,y=duv,r=frac{2(v^2+u^2)}2)

    枚举(2r)的因子(d),对于每个(d)(O(sqrt{frac rd}))枚举(u),带入(r)计算出(v^2) 计算(v^2)是否为完全平方数及(i,v)是否互质

    ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}
    bool check(ll a,ll b){
    	ll x=(ll)sqrt(b);
    	if(x*x==b) return gcd(a,b)==1;
    	return 0;
    }
    ll calc(ll d){
    	ll ret=0;
    	for(ll a=1;(a*a<<1)<d;++a)
    	ret+=check(a,d-a*a);
    	return ret;
    }
    int main(){
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif
    	rd(r),r<<=1;
    	for(ll d=1;d*d<=r;++d)
    	if(!(r%d)) ans+=calc(d)+(((d*d)==r)?0:calc(r/d));
    	printf("%lld",ans);
        return 0;
    }
    

    [SCOI2009]粉刷匠

    N条木板需要被粉刷。 每条木板被分为M个格子。 每个格子要被刷成红色或蓝色。

    每次粉刷,只能选择一条木板上一段连续的格子涂上一种颜色。 每个格子最多只能被粉刷一次。

    如果只能粉刷 T 次,最多能正确粉刷多少格子?一个格子如果未被粉刷或粉刷错颜色,就算错误粉刷。

    所以就先想只有一条木板怎么做 即(f[i][j])表示前(i)个格子刷(j)次最多能刷正确多少个格子

    然后很容易就能想到n条木板就可以将其进行01背包来算最多能刷正确有多少个格子

    int main(){
        rd(n),rd(m),rd(t);
        memset(f,0,sizeof(f));
        for(int x=1;x<=n;++x){
            scanf("%s",S+1);
            for(int i=1;i<=m;++i) sum[i]=sum[i-1]+(S[i]=='1');
            for(int i=1;i<=m;++i)//前i个格子 
            for(int j=1;j<=i;++j){//涂j次
                nw[i][j]=0;
                for(int k=0;k<i;++k)//由前k个格子转移过来 
                    nw[i][j]=Max(nw[i][j],nw[k][j-1]+Max(sum[i]-sum[k],i-k-(sum[i]-sum[k])));
            }
            	for(int i=1;i<=t;++i)
            		for(int j=1;j<=Min(i,m);++j) f[x][i]=Max(f[x][i],f[x-1][i-j]+nw[m][j]);
        }
        for(int i=1;i<=t;++i) ans=Max(ans,f[n][i]);
        printf("%d",ans);
        return 0;
    }
    
  • 相关阅读:
    [转载] IE8+兼容小结
    vue添加,删除内容
    vue跳转的两种方法
    checked 完整版全选,单选,反选
    网页特殊符号HTML代码大全
    利用css 实现 视觉差效果
    css 自定义滚动条样式
    js 模拟键盘
    css 动画 transition和animation
    浅谈浏览器垃圾回收机制
  • 原文地址:https://www.cnblogs.com/lxyyyy/p/11853296.html
Copyright © 2011-2022 走看看