图论知识补全

黑科技向

1.分层图最短路

这种题呢,一般是有一个这样的模型:

有一个分层图,在层中间连的边走需要$x$的花费,跨层需要$y$的花费,求$1$到$n$的最短路

我们一般会用一个$dis[i][j]$来表示现在是在第$i$个点,第$j$层的最短路

例题:bzoj2662

有一个$n$个点$m$条边的无向图,现在你有$k$张符卡可以使一条边边权减半,求$1$到$n$的最短路

这道题的“跨层转移”就是用的符卡的张数,每次多枚举一下当前用了几张符卡转移一下就可以了

#include<iostream>
 #include<cstdio>
 #include<cstring>
 #include<algorithm>
 using namespace std;
 int n,m,k,cnt,ans;
 struct data{
     int next,to,dis;
 }edge[2010];
 int head[60],w[60][60];
 bool check[60][60];
 void add(int strat,int end,int dd){
     edge[++cnt].next=head[strat];
     edge[cnt].to=end;
     edge[cnt].dis=dd;
     head[strat]=cnt; 
 }
 void dijkstra(){
     memset(w,0x3f3f3f3f,sizeof(w));
     w[1][0]=0;
     int mn,tmp1,tmp2;
     while(1){
         mn=0x3f3f3f3f;
         for(int i=1;i<=n;i++)
             for(int j=0;j<=k;j++)
                 if(!check[i][j]&&w[i][j]<mn){
                     mn=w[i][j];
                     tmp1=i;
                     tmp2=j;
                 }
         if(mn==0x3f3f3f3f)  break;
         check[tmp1][tmp2]=1;
         for(int i=head[tmp1];i;i=edge[i].next){
             w[edge[i].to][tmp2]=min(w[edge[i].to][tmp2],w[tmp1][tmp2]+edge[i].dis);//!!!tmp2
             w[edge[i].to][tmp2+1]=min(w[edge[i].to][tmp2+1],w[tmp1][tmp2]+edge[i].dis/2);   
         }
     } 
 }
 int main(){
     scanf("%d%d%d",&n,&m,&k);
     int u,v,d;
     for(int i=1;i<=m;i++){
         scanf("%d%d%d",&u,&v,&d);
         add(u,v,d);
         add(v,u,d);
     }
     dijkstra();
     ans=0x3f3f3f3f;
     for(int i=0;i<=k;i++) ans=min(ans,w[n][i]);
     printf("%d",ans);
     return 0;
 }
View Code

当然 还有一些要用到小trick的情况

比如说 每层的图都一样 但是k很大卡你空间的时候

就可以类似dp用滚动数组的方法

每次跑当前层和下一层的最短路

2.二分图的Hall定理

wzj52501学长在BJWC上讲过...可是当时我在打炉石

定理内容:

设二分图中G=<V1,V2,E>中 |V1|=m<=|V2|=n,G中存在从V1到V2的完全匹配当且仅当V1中任意k(k=1,2,...,m)个顶点至少与V2中k个顶点是相邻的。

这个可以把计算二分图存不存在完美匹配的复杂度降低

举个例子

bzoj1135

初始时滑冰俱乐部有1到n号的溜冰鞋各k双。已知x号脚的人可以穿x到x+d的溜冰鞋。 有m次操作,每次包含两个数ri,xi代表来了xi个ri号脚的人。xi为负,则代表走了这么多人。 对于每次操作,输出溜冰鞋是否足够。

朴素的想法是每来一个人做一次二分图匹配

。。。但显然是超时的

我们可以这样想

根据Hall定理,左边需求任意k种鞋的人数肯定小于等于能满足他们需求的鞋的总数

因为“任意”这个东西不好搞我们可以把它转换成“极值”

就是当一些人需求的鞋的范围极小时人数依然小于等于需求的鞋数

容易知道当人的集合是一段连续的区间时需求鞋的范围取到极小

也就是

$sum_{i = l}^{r}a_i leq (r - l + d + 1) imes k$

$sum_{i = l}^{r}(a_i - k) leq d imes k$

其中$a_i$表示需求鞋$i$的人数

于是我们用线段树动态维护$(a_i - k)$的最大子段和,每次看是否小于$d imes k$就可以了

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<string>
#define inf 1000000000
#define maxn 250000
#define maxm 500+100
#define eps 1e-10
#define ll long long
#define pa pair<int,int>
#define for0(i,n) for(int i=0;i<=(n);i++)
#define for1(i,n) for(int i=1;i<=(n);i++)
#define for2(i,x,y) for(int i=(x);i<=(y);i++)
#define for3(i,x,y) for(int i=(x);i>=(y);i--)
#define mod 1000000007
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();}
    return x*f;
}
struct seg{ll l,r,lx,rx,mx,sum;}t[4*maxn];
ll n,m,kk,d;
inline void build(int k,int l,int r)
{
    t[k].l=l;t[k].r=r;int mid=(l+r)>>1;
    t[k].lx=t[k].rx=t[k].mx=-kk;t[k].sum=-(r-l+1)*kk;
    if(l==r)return;
    build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
inline void pushup(int k)
{
    int l=k<<1,r=k<<1|1;
    t[k].lx=max(t[l].lx,t[l].sum+t[r].lx);
    t[k].rx=max(t[r].rx,t[r].sum+t[l].rx);
    t[k].mx=max(t[l].rx+t[r].lx,max(t[l].mx,t[r].mx));
    t[k].sum=t[l].sum+t[r].sum;
}
inline void add(int k,int x,ll y)
{
    int l=t[k].l,r=t[k].r,mid=(l+r)>>1;
    if(l==r){t[k].sum+=y;t[k].mx=t[k].lx=t[k].rx=t[k].sum;return;}
    if(x<=mid)add(k<<1,x,y);else add(k<<1|1,x,y);
    pushup(k);
}
int main()
{
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    n=read();m=read();kk=read();d=read();
    build(1,1,n);
    while(m--)
    {
        int x=read();ll y=read();
        add(1,x,y);
        printf("%s
",t[1].mx<=d*kk?"TAK":"NIE");
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/Kong-Ruo/p/9410491.html