图论1:最短路相关

文章目录

  • 最短路
    • bfs求0/1最短路:电路维修
    • 分治最短路:[Zjoi2016]旅行者
    • 分块floyd:Problem M. Walking Plan
    • floyd的本质:最优路线
    • test2018-2-28旅途
    • noip2017d1t3逛公园
  • floyd传递闭包+bitset优化
  • 墨墨的等式
    • 小凯的疑惑
  • 差分约束
    • bzoj2788: [Poi2012]Festival

最短路

bfs求0/1最短路:电路维修

luoguP2243 电路维修
权值仅0,1,即可O(1)维护单调队列做dijkstra。用双端队列实现。

// luogu-judger-enable-o2
//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=507;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m,d[N][N],tx[5]={-1,-1,1,1},ty[5]={-1,1,-1,1};
char s[N][N];

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

#define pr pair
#define fi first
#define se second
#define MP make_pair
int ck(int x,int y) {
    return x>=1&&x<=n&&y>=1&&y<=m;
}

deque<pr>que;
void solve() {
    n++; m++;
    memset(d,127/3,sizeof(d));
    int inf=d[1][1];
    d[1][1]=0;
    que.push_back(MP(1,1));
    while(!que.empty()) {
        pr t=que.front();
        que.pop_front();
        int x=t.fi,y=t.se;
        For(i,0,3) if(ck(x+tx[i],y+ty[i])) {
            int xx=x+tx[i],yy=y+ty[i],c=0;
            if((i==0&&s[xx][yy]=='/')||(i==3&&s[x][y]=='/')||(i==1&&s[x-1][y]=='\\')||(i==2&&s[x][y-1]=='\\')) c=1;
            if(d[xx][yy]>d[x][y]+c) {
                d[xx][yy]=d[x][y]+c;
                if(!c) que.push_front(MP(xx,yy));
                else que.push_back(MP(xx,yy));
            }
        }
    }
    printf("%d\n",d[n][m]);
}

//#define ANS
int main() {
#ifdef ANS
    freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
#endif
    read(T);
    while(T--) {
        read(n); read(m);
        For(i,1,n) scanf("%s",s[i]+1);
        if((n%2==0&&m%2==1)||(n%2==1&&m%2==0)) puts("NO SOLUTION");
        else solve();
    }
    Formylove;
}

分治最短路:[Zjoi2016]旅行者

传送门
网格图多次询问最短路,每次考虑同一个矩形内的询问的答案,从矩形长边的中点切一刀,从中间这条线上的点跑最短路,更新所有询问的答案再递归下去。
很久前的丑陋代码

//Achen
#include
#include
#include
#include
#include
#include
#include
#include
const int N=2e5+7;
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
typedef long long LL;
using namespace std;
int n,m,q,ans[N];

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[N<<1],to[N<<1],val[N];
void add(int u,int v,int w) {
    nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
    nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; val[ecnt]=w;
}

int get(int x,int y) { return (x-1)*m+y; }
void get(int xx,int &x,int &y) { y=xx%m; if(!y) y=m; x=xx/m; if(xx%m) x++; }
int in(int x,int y,int l,int r,int L,int R) { return x>=l&&x<=r&&y>=L&&y<=R;  }

struct node {
    int x,y,xx,yy,id;
}qs[N],lq[N],rq[N];

int dis[N],vis[N];
struct dj {
    int x,dis;
    dj(int x,int dis):x(x),dis(dis){}
    friend bool operator <(const dj&A,const dj&B) {
        return A.dis>B.dis;
    }
};
priority_queue<dj>que;

void dijkstra(int s,int l,int r,int L,int R) {
    For(i,l,r) For(j,L,R) {
        int x=get(i,j);
        dis[x]=ans[0]; vis[x]=0;
    }
    dis[s]=0;
    while(!que.empty()) que.pop();
    que.push(dj(s,0));
    while(!que.empty()) {
        dj x=que.top();
        que.pop();
        if(vis[x.x]||x.dis!=dis[x.x]) continue;
        vis[x.x]=1;
        for(int i=fir[x.x];i;i=nxt[i]) if(!vis[to[i]]) {
            int xx,yy; get(to[i],xx,yy);
            if(in(xx,yy,l,r,L,R)) {
                if(dis[to[i]]>dis[x.x]+val[i]) {
                    dis[to[i]]=dis[x.x]+val[i];
                    que.push(dj(to[i],dis[to[i]])); 
                }
            }
        }
    } 
}

void solve(int l,int r,int L,int R,int ql,int qr) {
    if(l>r||L>R||ql>qr) return;
    if(r-l<=R-L) {
        int mid=((L+R)>>1),ll=0,rr=0;
        For(i,l,r) {
            dijkstra(get(i,mid),l,r,L,R);
            For(j,ql,qr) {
                ans[qs[j].id]=min(ans[qs[j].id],dis[get(qs[j].x,qs[j].y)]+dis[get(qs[j].xx,qs[j].yy)]);
                if(i==l) {
                    if(in(qs[j].x,qs[j].y,l,r,L,mid)&&in(qs[j].xx,qs[j].yy,l,r,L,mid)) lq[++ll]=qs[j];
                    else if(in(qs[j].x,qs[j].y,l,r,mid+1,R)&&in(qs[j].xx,qs[j].yy,l,r,mid+1,R)) rq[++rr]=qs[j];
                }
            }
        }
        For(i,1,ll) qs[ql+i-1]=lq[i]; For(i,1,rr) qs[ql+ll+i-1]=rq[i];
        if(l==r||L==R) return;
        solve(l,r,L,mid,ql,ql+ll-1); solve(l,r,mid+1,R,ql+ll,ql+ll+rr-1);
    }
    else {
        int mid=((l+r)>>1),ll=0,rr=0;
        For(i,L,R) {
            dijkstra(get(mid,i),l,r,L,R);
            For(j,ql,qr) {
                ans[qs[j].id]=min(ans[qs[j].id],dis[get(qs[j].x,qs[j].y)]+dis[get(qs[j].xx,qs[j].yy)]);
                if(i==L) {
                    if(in(qs[j].x,qs[j].y,l,mid,L,R)&&in(qs[j].xx,qs[j].yy,l,mid,L,R)) lq[++ll]=qs[j];
                    else if(in(qs[j].x,qs[j].y,mid+1,r,L,R)&&in(qs[j].xx,qs[j].yy,mid+1,r,L,R)) rq[++rr]=qs[j];
                }
            }
        }
        For(i,1,ll) qs[ql+i-1]=lq[i]; For(i,1,rr) qs[ql+ll+i-1]=rq[i];
        if(l==r||L==R) return;
        solve(l,mid,L,R,ql,ql+ll-1); solve(mid+1,r,L,R,ql+ll,ql+ll+rr-1);
    }
}

//#define DEBUG 
int main() {
#ifdef DEBUG
    freopen("4456.in","r",stdin);
    freopen("4456.out","w",stdout);
#endif
    read(n); read(m);
    For(i,1,n) 
        For(j,1,m-1) {
            int w; read(w);
            add(get(i,j),get(i,j+1),w);
        }    
    For(i,1,n-1)
        For(j,1,m) {
            int w; read(w);
            add(get(i,j),get(i+1,j),w);
        }
    read(q);
    For(i,1,q) {
        read(qs[i].x); read(qs[i].y);
        read(qs[i].xx); read(qs[i].yy);
        qs[i].id=i;
    }
    memset(ans,127/3,sizeof(ans));
    solve(1,n,1,m,1,q);
    For(i,1,q) printf("%d\n",ans[i]);
    return 0;
}
/*
2
3
4
1 1 2 2
2 2 1
*/

分块floyd:Problem M. Walking Plan

传送门
求s到t至少经过k条边的最短路,多组询问,n<=50,m<=1e4,q<=1e5.

f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示从i走k步到j的最短路

g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k]表示从i走k*100步到j的最短路

h [ i ] [ j ] [ k ] h[i][j][k] h[i][j][k]表示从i至少走k步到j的最短路

询问时,因为至多会多走n步,用上面预处理的f,g,h可以得出答案。

//Achen
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define Formylove return 0
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=55;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m,f[N][N][205],g[N][N][205],h[N][N][205];

template<typename T>void read(T &x)  {
	char ch=getchar(); x=0; T f=1;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int main() {
#ifdef ANS
	freopen(".in","r",stdin);
	freopen(".out","w",stdout);
#endif
	read(T);
	while(T--) {
		read(n); read(m);
		
		memset(f,127/3,sizeof(f));
		memset(g,127/3,sizeof(g));
		memset(h,127/3,sizeof(h));
		
		int inf=f[0][0][0];
		
		For(i,1,n) f[i][i][0]=g[i][i][0]=h[i][i][0]=0;
		
		For(i,1,m) {
			int u,v,w;
			read(u); read(v); read(w);
			f[u][v][1]=min(f[u][v][1],w);
		}
		
		For(k,2,200) {
			For(l,1,n) For(i,1,n) For(j,1,n) 
				f[i][j][k]=min(f[i][j][k],f[i][l][k-1]+f[l][j][1]);	
		}
		
		For(i,1,n) For(j,1,n) g[i][j][1]=f[i][j][100];
		For(k,2,100) 
			For(l,1,n) For(i,1,n) For(j,1,n)
				g[i][j][k]=min(g[i][j][k],g[i][l][k-1]+g[l][j][1]);
		For(i,1,n) For(j,1,n) For(k,1,100) 
			For(l,k,200) h[i][j][k]=min(h[i][j][k],f[i][j][l]);
		
		int q;
		read(q);
		while(q--) {
			int i,j,z;
			read(i); read(j); read(z);
			int ans=inf;
			For(k,1,n) {
				int a=z/100,b=z%100;
				if(b==0) a--,b=100;
				ans=min(ans,g[i][k][a]+h[k][j][b]);
			}
			if(ans>=inf) ans=-1;
			printf("%d\n",ans);
		}
	}
	Formylove;
}

floyd的本质:最优路线

路径权值定义为经过点权的最大值乘边权最大值,求两两点对间最短路。

考虑 floyd 的本质: f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j]表示只经过前 k 个点的情况下 i 到 j 的最短路,一般按节点标号顺序枚举 k 即可求得两两最短路。这里我 们可以改变枚举顺序,按点权从小到大枚举 k,这样 f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j]就是点权 不超过第 k 个点,i 到 j 的最短路,在每个阶段都更新一遍两两点对 答案,时间复杂度 O ( n 3 ) O(n^3 ) O(n3)

test2018-2-28旅途

s到t关于k的路径权值定义为路径上前k大的边的权值和。求关于K=1~m的最短路。
容易想到枚举第k大的边v,把权值小于它的设为0。如果直接这样跑最短路,肯定会尽量跑那些0边导致根本就没选到k条大于等于v的边,也就是最短路上第k大小于v,一些需要计算代价的边被看成了0边,权值不真实。
我们想知道最短路上到底选了多少条大于等于v的边,此时所有非0边都大于等于v,最短路的关系等价于把所有非0边边权-v个后的图,且这样最后我们可以暴力枚举假设它选了k条大于等于v的边+k*v来更新k的答案。
显然当枚举的边数不对时k的答案不会更优。
且k的合法最优解的路径经过上诉对第k大边权值的处理时得到的一定是最短路(否则最短路可以构造出更优解或最优解直接是全部边的最短路),所以所有最优解会被枚举到会被枚举到。

noip2017d1t3逛公园

问1到n的最短路长度+K(k<=50)以内的长度的路径条数,边权非负。

正反最短路+拓扑排序处理0环。
其实没什么可说的,主要是最后统计方案数dp时,排序不能傻乎乎地n*k的排序,dis为第一关键字,拓扑序为第二关键字排序n个点,k从小到大枚举来更新即可。

//Achen
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int N=100005;
const int M=200005;
typedef long long LL;
using namespace std;
 
int T,n,m,k,p;
LL dp[N][51];
 
template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
 
int fir[N],nxt[M],to[M],val[M],ecnt;
int fif[N],nxf[M],tf[M],vaf[M],ecnf;
void add(int u,int v,int w) {
    nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
    nxf[++ecnf]=fif[v]; fif[v]=ecnf; tf[ecnf]=u; vaf[ecnf]=w;
}
  
int fi[N],nx[M],tt[M],in[N],ec;
void Add(int u,int v) {
    nx[++ec]=fi[u]; fi[u]=ec; tt[ec]=v; in[v]++;
}
 
queue<int>que;
int ds[N],dt[N],vis[N],tps[N];
void spfa(int s,int d[],int fir[],int nxt[],int to[],int val[]) {
    d[s]=0;
    vis[s]=1;
    que.push(s);
    while(!que.empty()) {
        int x=que.front();
        que.pop();
        vis[x]=0;
        for(int i=fir[x];i;i=nxt[i])
            if(d[to[i]]>d[x]+val[i]) {
                d[to[i]]=d[x]+val[i];
                if(!vis[to[i]]) {
                    vis[to[i]]=1;
                    que.push(to[i]);
                }
            }
    }
}
 
int tpsort() {
    for(int i=1;i<=n;i++) if(!in[i]) que.push(i);
    int idd=0;
    while(!que.empty()) {
        int x=que.front();
        que.pop();
        tps[x]=++idd;
        for(int i=fi[x];i;i=nx[i]) {
            if(!(--in[tt[i]]))
                que.push(tt[i]);
        }
    }
    for(int i=1;i<=n;i++)
        if(in[i]&&ds[i]+dt[i]<=ds[n]+k)
            return 1;
    return 0;
}
 
struct node{
    int x;
    friend bool operator <(const node &A,const node &B) {
        return ds[A.x]==ds[B.x]?tps[A.x]<tps[B.x]:ds[A.x]<ds[B.x];
    }
}po[N*50];
 
void clear() {
    ecnt=ec=ecnf=0;
    memset(dt,127,sizeof(dt));
    memset(ds,127,sizeof(ds));
    memset(fi,0,sizeof(fir));
    memset(fir,0,sizeof(fir));
    memset(fif,0,sizeof(fif));
    memset(in,0,sizeof(in));
    memset(tps,0,sizeof(tps));
    memset(dp,0,sizeof(dp));
}
 
void work() {
    spfa(1,ds,fir,nxt,to,val);
    spfa(n,dt,fif,nxf,tf,vaf);
    if(tpsort()) printf("-1\n");
    else {
        int tot=0;
        for(int i=1;i<=n;i++)
            po[++tot].x=i;
        dp[1][0]=1;
        sort(po+1,po+tot+1);
        for(int y=0;y<=k;y++) {
        for(int o=1;o<=tot;o++) {
            int x=po[o].x;
            for(int i=fir[x];i;i=nxt[i])
                if((LL)ds[x]+y+val[i]+dt[to[i]]<=ds[n]+k) {
                    (dp[to[i]][ds[x]+y+val[i]-ds[to[i]]]+=dp[x][y])%=p;
                }
        }
        }
        LL ans=0;
        for(int i=0;i<=k;i++) (ans+=dp[n][i])%=p;
        printf("%lld\n",ans);
    }
}
 
void init() {
    read(T);
    while(T--) {
        clear();
        read(n);
        read(m);
        read(k);
        read(p);
        for(int i=1;i<=m;i++) {
            int u,v,w;
            read(u); read(v); read(w);
            if(!w) Add(u,v);
            add(u,v,w);
        }
        work();
    }
}
 
#define DEBUG
int main() {
#ifdef DEBUG
    freopen("park.in","r",stdin);
    freopen("park.out","w",stdout);
#endif
    init();
    return 0;
}

floyd传递闭包+bitset优化

hdu5036Explosion

无关:此题题解

虽然和图论没有关系还是说一下做法,有n个门每个门里有一些钥匙没把钥匙可以打开一些门,没有钥匙的时候就用炸弹随机炸一扇没开过的门,问期望用炸弹的个数。根据期望的可加性,答案就是每扇门被炸的次数的期望之和。如果有k扇门打开后就可以打开x这扇门(不一定是直接打开),那么x被炸的次数的期望就是1/(k+1)。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=1007;
typedef long long LL;
typedef double db;
using namespace std;
int T,n;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

bitset<N>b[N];

//#define ANS
int main() {
#ifdef ANS
    freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
#endif
    read(T);
    For(cs,1,T) {
        read(n);
        For(i,1,n) {
            b[i].reset();
            b[i].set(i);
            int k,x;
            read(k);
            For(j,1,k) {
                read(x);
                b[i].set(x);
            }
        }
        For(k,1,n) For(i,1,n) if(b[i][k]) 
            b[i]|=b[k];
        db ans=0;
        For(i,1,n) {
            int cnt=0;
            For(j,1,n) if(b[j][i]) cnt++;
            ans+=1.0/(1.0*cnt);
        }
         printf("Case #%d: %.5lf\n",cs,ans);
    }
    Formylove;
}

墨墨的等式

传送门
这道当年sxy给我讲的题帮我过了Noip2017小凯的疑惑,Noip2018货币系统,感谢sxy让我苟延残喘到今天。

用最小的a把所有数按mod a分类,建出代表0~a-1的a个点,对每个ai,点x向点(x+ai)%a连边ai,跑最短路求得能表达出的mod a=x的最小值即可。

小凯的疑惑

a,b互质,问ax+by不能组成的最大的数。
传送门
答案为ab-a-b.
设a 0 b , 1 b , 2 b , 3 b , . . . , ( a − 1 ) b ( m o d   a ) 0b,1b,2b,3b,...,(a-1)b(mod \ a) 0b,1b,2b,3b,...,(a1)bmod a)的结果两两不同。
反证,若存在 x b = y b ( m o d   a ) , ( 0 ≤ x < y < a ) xb=yb(mod \ a),(0 \leq x<y< a) xb=yb(mod a),(0x<y<a)
( y − x ) b = k a (y-x)b=ka (yx)b=ka
∵ ( a , b ) = 1 , ∴ a ∣ ( y − x ) ∵(a,b)=1,∴a|(y-x) (a,b)=1,a(yx)
y − x < a y-x<a yx<a矛盾。
故mod a=kb%a(k=0~(a-1))的最小数为kb且大于它的数都能构造出.最大不能构成的数即(a-1)*b-a=ab-a-b.

差分约束

对于限制y<=x+a,从x向y连权值为a的边,通过最短路来满足这些限制。
即使已经过去两年了,Achen仍然忘不了当初啥都不知道的时候llj神仙第一次用spfa解一道约束问题时给Achen带来的震撼。

bzoj2788: [Poi2012]Festival

传送门

//Achen
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define Formylove return 0
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=607,M=100007;
typedef long long LL;
typedef double db;
using namespace std;
int n,m1,m2;
int f[N][N];

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[M<<1],to[M<<1];
void add(int u,int v) {
    nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
}

vector<int>vc[N];
int dfn[N],low[N],dfk,sta[N],top,bl[N],tot;
void tarjan(int x) {
    dfn[x]=low[x]=++dfk;
    sta[++top]=x;
    for(int i=fir[x];i;i=nxt[i]) {
        if(!dfn[to[i]]) {
            tarjan(to[i]);
            low[x]=min(low[x],low[to[i]]);
        }
        else if(!bl[to[i]]) low[x]=min(low[x],dfn[to[i]]);
    }
    if(dfn[x]==low[x]) {
        bl[x]=++tot;
        for(;;) {
            int u=sta[top--];
            vc[tot].push_back(u);
            bl[u]=tot;
            if(u==x) break;
        }
    }
}

//#define ANS
int main() {
#ifdef ANS
    freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
#endif
    read(n); read(m1); read(m2);
    memset(f,127/3,sizeof(f));
    For(i,1,n) f[i][i]=0;
    For(i,1,m1) {
        int x,y;
        read(x); read(y);
        add(x,y);
        add(y,x);
        f[x][y]=min(f[x][y],1);
        f[y][x]=min(f[y][x],-1);    
    }
    For(i,1,m2) {
        int x,y;
        read(x); read(y);
        add(y,x);
        f[y][x]=min(f[y][x],0);
    }
    For(k,1,n) For(i,1,n) For(j,1,n) 
        f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
    For(i,1,n) if(f[i][i]<0) {
        puts("NIE");
        return 0;
    }
    For(i,1,n) if(!dfn[i]) tarjan(i);
    int ans=0;
    For(i,1,tot) {
        int up=vc[i].size();
        int mx=0;
        For(j,0,up-1) For(k,0,up-1) 
            mx=max(mx,f[vc[i][j]][vc[i][k]]);
        ans+=mx+1;
    }
    printf("%d\n",ans);
    Formylove;
}
/*
4 2 2
1 2
3 4
1 4
3 1
*/

你可能感兴趣的:(图论)