图论
(一)弦图:当且仅当存在完美消除序列。
bzoj1006 神奇的国度
题目大意:给定一张弦图,求它的最小染色数。
思路:之所以看出是弦图,因为题目中说只有三元环,而没有更多的。求最小染色数就是求出完美消除序列后,从后往前尽量染能染的最小的颜色。其他分析的复杂度是O(n+m)的,但是自己写出程序没有优化,是O(n^2+m)的,速度也不是很慢。(同时cdq证明了团数=颜色数)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 10005 #define maxedge 2000005 using namespace std; int point[maxnode]={0},next[maxedge]={0},en[maxedge]={0},tot=0,du[maxnode]={0},ai[maxnode]={0}; bool visit[maxnode]={false}; void add(int u,int v) { ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v; ++tot;next[tot]=point[v];point[v]=tot;en[tot]=u; } int main() { int n,m,u,v,i,j,maxdu,maxi,ans=0; scanf("%d%d",&n,&m); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);} for (i=n;i>=1;--i) { maxdu=-1;maxi=0; for (j=1;j<=n;++j) { if (visit[j]) continue; if (du[j]>maxdu){maxdu=du[j];maxi=j;} } ai[i]=maxi;visit[maxi]=true; for (j=point[maxi];j;j=next[j]) ++du[en[j]]; }memset(du,0,sizeof(du)); for (i=n;i>=1;--i) { memset(visit,false,sizeof(visit)); for (j=point[ai[i]];j;j=next[j]) visit[du[en[j]]]=true; for (j=1;j<=n;++j) if (!visit[j]) break; ans=max(ans,j);du[ai[i]]=j; } printf("%d\n",ans); }
zoj1015 fishing net
题目大意:给定一张图,判断是否是弦图。
思路:重新对图编号(相当于求一遍完美消除序列),然后判断这是否是个完美消除序列(对于序列中的第i个点,判断它后面所有与他相邻的点中的第一个是否与其他所有相连)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1005 using namespace std; int map[maxnode][maxnode]={0},ai[maxnode]={0},ci[maxnode]={0}; bool visit[maxnode]={false}; bool judge(int n) { int i,j,u; for (i=1;i<=n;++i) { for (u=0,j=i+1;j<=n;++j) if (map[ai[i]][ai[j]]) ci[++u]=ai[j]; for (j=2;j<=u;++j) if (!map[ci[1]][ci[j]]) return false; } return true; } int main() { int n,m,i,j,u,v,maxdu,maxi; while(scanf("%d%d",&n,&m)==2) { if (n==0&&m==0) break; memset(map,0,sizeof(map)); memset(ci,0,sizeof(ci)); memset(visit,false,sizeof(visit)); for (i=1;i<=m;++i) { scanf("%d%d",&u,&v);map[u][v]=map[v][u]=1; } for (i=n;i>=1;--i) { maxdu=-1; for (j=1;j<=n;++j) { if (visit[j]) continue; if (ci[j]>maxdu){maxdu=ci[j];maxi=j;} } ai[i]=maxi;visit[maxi]=true; for (j=1;j<=n;++j) if (map[maxi][j]) ++ci[j]; } if (judge(n)) printf("Perfect\n\n"); else printf("Imperfect\n\n"); } }
求最大独立集:求出完美消除序列后,从前往后能选就选。
求最小团覆盖:最小团覆盖数=最大独立集数
(二)区间图:当它是若干区间的相交图。(一定是弦图)
(三)最短路
CODEVS2645 Spore
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int next[20001]={0},point[10001]={0},en[20001]={0},va[20001]={0},que[2010]={0},num[10001]={0}; long long dis[1001]={0}; bool visit[10001]={false}; int main() { int n,m,i,j,tot,head,tail,g1,g2,c1,c2,x,y; long long maxn; bool ff; maxn=2100000000; while(true) { tot=0; memset(que,0,sizeof(que)); memset(next,0,sizeof(next)); memset(point,0,sizeof(point)); memset(en,0,sizeof(en)); memset(va,0,sizeof(va)); memset(num,0,sizeof(num)); memset(visit,false,sizeof(visit)); ff=false; scanf("%d%d",&n,&m); if (n==0&&m==0) break; if (m==0&&n==1) { printf("0\n"); continue; } if (m==0) { printf("No such path\n"); continue; } for (i=1;i<=m;++i) { scanf("%d%d%d%d",&g1,&g2,&c1,&c2); ++tot; next[tot]=point[g1]; point[g1]=tot; en[tot]=g2; va[tot]=c1; ++tot; next[tot]=point[g2]; point[g2]=tot; en[tot]=g1; va[tot]=c2; } head=0;tail=1;que[tail]=1; for (i=0;i<=n;++i) dis[i]=maxn; dis[1]=0; visit[1]=true; num[1]++; do { ++head; head=(head-1)%2000+1; x=que[head]; visit[x]=false; y=point[x]; while(y!=0) { if (dis[en[y]]>dis[x]+va[y]) { dis[en[y]]=dis[x]+va[y]; if (!visit[en[y]]) { ++tail; tail=(tail-1)%2000+1; que[tail]=en[y]; ++num[en[y]]; visit[en[y]]=true; if (num[en[y]]>n) { ff=true; break; } } } y=next[y]; } if (ff) break; }while(head!=tail); if (ff) printf("No such path\n"); else { if (dis[n]!=maxn) printf("%lld\n",dis[n]); else printf("No such path\n"); } } }
bzoj2007 海拔
题目大意:给定一个n*n个区域的网格,已知左上角高度为0,右下角为1,每两个相邻路口间有一条路,已知路上人数,人的疲惫值定为从一点到另一点的高度差(如果<0则为0),给网格分配高度,使总的疲惫值最小。
思路:首先我们有两个结论:1)只分配0、1高度最优;2)0、1分别聚成一团最优。这个可以通过调整感受到。那我们只需要找到这一条0、1的分割线就行了,也就是最小割,因数据范围,进而转化成平面图。这里要注意,如果我们以从起点到终点的方向来看,如果向下走则左1右0,向上走则左0右1,向左走则上1下0,向右走则上0下1(做题的时候仅凭猜测,后来也不知道怎么证明)。因为这道题目的数据,所以写了堆优化的dijkstra算法。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define maxnode 250005 #define maxedge 2000000 #define len 10000000 using namespace std; int point[maxnode]={0},next[maxedge]={0},en[maxedge]={0},map[505][505][4]={0},n,st,enn,tot=0; long long dis[maxnode],va[maxedge]={0}; bool visit[maxnode]={false}; struct use{ int dis,u; bool operator<(const use x)const {return dis>x.dis;} }; priority_queue <use> que; void add(int u,int v,int vaa) { ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=(long long)vaa; } void makeedge() { int i,j; for (i=1;i<=n+1;++i) for (j=1;j<=n;++j) { if (i==1) add(j,enn,map[i][j][0]); if (i==n+1) add(st,(i-2)*n+j,map[i][j][0]); if (i>1&&i<=n) {add((i-1)*n+j,(i-2)*n+j,map[i][j][0]);add((i-2)*n+j,(i-1)*n+j,map[i][j][2]);} } for (i=1;i<=n;++i) for (j=1;j<=n+1;++j) { if (j==1) add(st,(i-1)*n+j,map[i][j][1]); if (j==n+1) add((i-1)*n+j-1,enn,map[i][j][1]); if (j>1&&j<=n) {add((i-1)*n+j-1,(i-1)*n+j,map[i][j][1]);add((i-1)*n+j,(i-1)*n+j-1,map[i][j][3]);} } } long long dij() { memset(dis,127,sizeof(dis));dis[st]=0;que.push((use){0,st}); while(!que.empty()) { use x=que.top(); que.pop(); if (!visit[x.u]) { visit[x.u]=true; for (int i=point[x.u];i;i=next[i]) { if (dis[en[i]]>dis[x.u]+va[i]) { dis[en[i]]=dis[x.u]+va[i]; que.push((use){dis[en[i]],en[i]}); } } } } return dis[enn]; } int main() { int i,j; scanf("%d",&n);st=0;enn=n*n+1; for (i=1;i<=n+1;++i) for (j=1;j<=n;++j) scanf("%d",&map[i][j][0]); for (i=1;i<=n;++i) for (j=1;j<=n+1;++j) scanf("%d",&map[i][j][1]); for (i=1;i<=n+1;++i) for (j=1;j<=n;++j) scanf("%d",&map[i][j][2]); for (i=1;i<=n;++i) for (j=1;j<=n+1;++j) scanf("%d",&map[i][j][3]); makeedge();printf("%lld\n",dij()); }
bzoj4152 The Captain
题目大意:给定一些点,两点间距离定义为两点x、y坐标差的较小值,求从1~n的最短距离。
思路:按横纵坐标分别排序之后,相邻点连边,然后跑最短路就可以了。堆优化的dijkstra算法。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define maxm 1000005 #define LL long long using namespace std; struct use{ LL x,y,po; }ai[maxm]={0}; struct uu{ LL dis;int u; bool operator<(const uu x)const{return dis>x.dis;} }; int point[maxm]={0},next[maxm]={0},en[maxm]={0},tot=0; LL va[maxm]={0},dis[maxm]={0}; bool visit[maxm]={false}; priority_queue<uu> que; int cmp1(const use&x,const use&y){return (x.x==y.x?x.y<y.y:x.x<y.x);} int cmp2(const use&x,const use&y){return (x.y==y.y?x.x<y.x:x.y<y.y);} LL ab(LL x){return x<0 ? -x : x;} void add(int u,int v,LL vv){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vv; } LL work(int s,int t){ int i,j;memset(dis,127,sizeof(dis)); dis[s]=0;que.push((uu){0,s}); while(!que.empty()){ uu x=que.top();que.pop(); if (!visit[x.u]){ visit[x.u]=true; for (i=point[x.u];i;i=next[i]) if (dis[en[i]]>dis[x.u]+va[i]) que.push((uu){dis[en[i]]=dis[x.u]+va[i],en[i]}); } }return dis[t]; } int main(){ int n,i,j;scanf("%d",&n); for (i=1;i<=n;++i){scanf("%I64d%I64d",&ai[i].x,&ai[i].y);ai[i].po=i;} sort(ai+1,ai+n+1,cmp1); for (i=2;i<=n;++i) add(ai[i-1].po,ai[i].po,min(ab(ai[i].x-ai[i-1].x),ab(ai[i].y-ai[i-1].y))); sort(ai+1,ai+n+1,cmp2); for (i=2;i<=n;++i) add(ai[i-1].po,ai[i].po,min(ab(ai[i].x-ai[i-1].x),ab(ai[i].y-ai[i-1].y))); printf("%I64d\n",work(1,n)); }
bzoj1922 大陆争霸
题目大意:给定一张图,访问一些点的时候要先访问其他的点,求从1~n的最短路。
思路:用dijkstra算法+拓扑思想就可以了。开两个距离数组,一个是表示最短路的,一个是它前继最长的时间,然后最短路的时候更新一下就行了。注意一点就是每轮取最小值的时候,要从一个点的这两个数组里取一个较大值(!!!)去更新后面的点。
#include<iostream> #include<cstdio> #include<cstring> #define maxn 3005 #define maxedge 70005 #define LL long long using namespace std; int map[maxn][maxn]={0},point[maxn]={0},next[maxedge]={0},en[maxedge]={0},tot=0,du[maxn]={0}; LL dis[maxn],ea[maxn]={0},va[maxedge]={0}; bool visit[maxn]={false}; void add(int u,int v,int wi) { ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=wi; } int main() { int n,m,i,j,u,v,wi,mini;LL minn; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&u,&v,&wi); if (u!=v) add(u,v,wi); } for (i=1;i<=n;++i) { scanf("%d",&du[i]); for (j=1;j<=du[i];++j){scanf("%d",&u);map[u][i]=1;} } memset(dis,127/3,sizeof(dis));dis[1]=0; for (i=1;i<=n;++i) { minn=dis[0];mini=0; for (j=1;j<=n;++j) if (!visit[j]&&!du[j]&&max(ea[j],dis[j])<minn){minn=max(ea[j],dis[j]);mini=j;} if (!mini) break; visit[mini]=true; for (j=1;j<=n;++j) if (map[mini][j]){--du[j];ea[j]=max(ea[j],minn);} for (j=point[mini];j;j=next[j]) { v=en[j];dis[v]=min(dis[v],minn+va[j]); } } printf("%I64d\n",max(dis[n],ea[n])); }
bzoj1880 Elaxia的路线
题目大意:给定一张图,求两对点最短路上的最长公共路径。
思路:以四个点为顶点跑出最短路之后,答案就是(len(x1,y1)+len(x2,y2)-min(len(s1,s2)+len(t1,t2),len(s1,t2)+len(t1,s2)))/2(这个其实并不正确,hack的数据也很好出,只是能a掉)。当然还有很多其他做法:因为这段最长公共路径一定是连续不断的一段,所以我们可以枚举左右端点,然后判断是否同时在两对点间的最短路上,最大值就是了;或者把同时在两对点间最短路上的路保存下来,拓扑一下求出最大值就是答案了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 600000 #define len 5000000 using namespace std; int point[maxm]={0},en[maxm]={0},next[maxm]={0},va[maxm]={0},tot=0, que[len+1]={0},dis[4][maxm]; bool visit[maxm]={false},flag[2][maxm]={false}; int ab(int x){return x<0?-x:x;} void add(int u,int v,int vaa){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vaa; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vaa; } void spfa(int st,int kk){ int head,tail,i,j,u,v;head=tail=0; memset(visit,false,sizeof(visit)); memset(dis[kk],127,sizeof(dis[kk])); que[++tail]=st;visit[st]=true;dis[kk][st]=0; while(head!=tail){ head=head%len+1;u=que[head];visit[u]=false; for (i=point[u];i;i=next[i]){ v=en[i]; if (dis[kk][v]>dis[kk][u]+va[i]){ dis[kk][v]=dis[kk][u]+va[i]; if (!visit[v]){ visit[v]=true;tail=tail%len+1; que[tail]=v; } } } } } int main(){ int n,i,j,m,u,v,l,x1,y1,x2,y2,len1,len2,ans=0;tot=1; scanf("%d%d%d%d%d%d",&n,&m,&x1,&y1,&x2,&y2); for (i=1;i<=m;++i){scanf("%d%d%d",&u,&v,&l);add(u,v,l);} spfa(x1,0);spfa(y1,1);spfa(x2,2);spfa(y2,3); len1=dis[0][y1];len2=dis[2][y2]; ans=max(0,(len1+len2-min(dis[0][x2]+dis[1][y2],dis[0][y2]+dis[1][x2]))/2); printf("%d\n",ans); }
bzoj1295 最长距离
题目大意:给定一张图,一些点是障碍物,求最多移走t个障碍后的图中的最远两点间的欧几里德距离。
思路:以每个点为开始跑最短路(根据障碍在相邻点之间建的图),如果两点间的距离小于t就根据这两点间的距离更新答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxm 50 #define maxe 100000 #define len 1000000 using namespace std; int bi[maxm][maxm]={0},point[maxe]={0},en[maxe]={0},next[maxe]={0},tot=0,map[maxm][maxm]={0}, dx[2]={0,1},dy[2]={1,0},va[maxe]={0},dis[maxe]={0},que[len+1]={0}; bool visit[maxe]; void add(int u,int v,int v1,int v2){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=v1; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=v2; } int fang(int x){return x*x;} int sc(){ char ch;while(scanf("%c",&ch)==1) if (ch=='0'||ch=='1') break; return ch-'0'; } void spfa(int x,int y){ int head=0,tail=0,i,j,u,v; memset(dis,127/3,sizeof(dis));que[++tail]=bi[x][y]; memset(visit,false,sizeof(visit));visit[bi[x][y]]=true; dis[bi[x][y]]=map[x][y]; while(head!=tail){ head=head%len+1;u=que[head];visit[u]=false; for (i=point[u];i;i=next[i]){ v=en[i]; if (dis[v]>dis[u]+va[i]){ dis[v]=dis[u]+va[i]; if (!visit[v]){ visit[v]=true;tail=tail%len+1;que[tail]=v; } } } } } int main(){ int n,m,t,i,j,k,x,y;double ans=0; scanf("%d%d%d",&n,&m,&t); for (tot=0,i=1;i<=n;++i) for (j=1;j<=m;++j){map[i][j]=sc();bi[i][j]=++tot;} for (tot=0,i=1;i<=n;++i) for (j=1;j<=m;++j) for (k=0;k<2;++k){ x=i+dx[k];y=j+dy[k]; if (x<1||x>n||y<1||y>m) continue; add(bi[i][j],bi[x][y],map[x][y],map[i][j]); } for (i=1;i<=n;++i) for (j=1;j<=m;++j){ spfa(i,j); for (x=1;x<=n;++x) for (y=1;y<=m;++y) if (dis[bi[x][y]]<=t) ans=max(ans,sqrt(fang(i-x)+fang(j-y))); }printf("%.6f\n",ans); }
bzoj3875 骑士游戏
题目大意:有n个怪物,每个怪物可以直接消灭或者打死之后产生一些新的怪物(代价不同),求杀死1号怪物的最小代价。
思路:连反向边,然后spfa的时候更新一下(一个是某个点的全局最优和打死后产生新怪物的最优),最开始全局最优就是直接消灭、打死后产生新怪物的最优就是直接消灭那些怪物的和。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 4000005 #define len 4000000 #define LL long long using namespace std; int point[maxm]={0},next[maxm]={0},en[maxm]={0},que[len+1],tot=0; LL si[maxm],ki[maxm],gi[maxm]={0},fi[maxm]={0}; bool visit[maxm]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} LL spfa(int n){ int i,j,u,v,head=0,tail=0; for (i=1;i<=n;++i){que[++tail]=i;visit[i]=true;} while(head!=tail){ head=head%len+1;u=que[head];visit[u]=false; if (si[u]+gi[u]<fi[u]){ for (i=point[u];i;i=next[i]){ v=en[i];gi[v]=gi[v]-fi[u]+gi[u]+si[u]; if (!visit[v]){ visit[v]=true;tail=tail%len+1;que[tail]=v; } }fi[u]=gi[u]+si[u]; } }return fi[1]; } int main(){ int n,i,j,ri,u;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%I64d%I64d%d",&si[i],&ki[i],&ri);fi[i]=ki[i]; for (j=1;j<=ri;++j){scanf("%d",&u);add(u,i);} }for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) gi[en[j]]+=ki[i]; printf("%I64d\n",spfa(n)); }
bzoj2750 道路
题目大意:给定一张有向图,求每一条边作为最短路径出现的次数。
思路:据说有n^3的floyed的写法,但这题的范围比较大,所以要另想方法。可以对每个点开始spfa,然后做出最短路径图(dag图),所以可以用拓扑来更新两个东西(一个是倒着拓扑、更新每一个点的儿子的个数;一个是正着拓扑、更新每一个点到根有多少种路径),然后对一条边起点终点这两个的乘积就是答案了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 5005 #define len 1000000 #define LL long long #define p 1000000007 using namespace std; struct use{ int st,en,va;LL cnt; }edge[maxm]={0},edge1[maxm]={0}; int tot=0,point[maxm]={0},next[maxm]={0},point1[maxm]={0},next1[maxm]={0}, dis[maxm],que[len+1]={0},n,m,du[maxm]={0}; LL siz[maxm],ji[maxm]; bool visit[maxm]; void add(int u,int v,int vv){ next[++tot]=point[u];point[u]=tot;edge[tot]=(use){u,v,vv,0}; next1[tot]=point1[v];point1[v]=tot;edge1[tot]=(use){v,u,vv,0}; } void spfa(int uu){ int u,v,head=0,tail=0,i,j; memset(dis,127,sizeof(dis));que[++tail]=uu;dis[uu]=0; memset(visit,false,sizeof(visit));visit[uu]=true; while(head!=tail){ u=que[head=head%len+1];visit[u]=false; for (i=point[u];i;i=next[i]){ v=edge[i].en; if (dis[v]>dis[u]+edge[i].va){ dis[v]=dis[u]+edge[i].va; if (!visit[v]){visit[v]=true;que[tail=tail%len+1]=v;} } } } } void work(){ int head,tail,i,u,v; memset(ji,0,sizeof(ji));memset(du,0,sizeof(du)); for (i=1;i<=m;++i) if (dis[edge[i].en]==dis[edge[i].st]+edge[i].va) ++du[edge[i].en]; for (head=tail=0,i=1;i<=n;++i) if (!du[i]&&dis[i]<dis[0]) {que[++tail]=i;ji[i]=1LL;} while(head!=tail){ u=que[++head]; for (i=point[u];i;i=next[i]){ v=edge[i].en; if (dis[v]==dis[u]+edge[i].va){ ji[v]+=ji[u];--du[v]; if (!du[v]) que[++tail]=v; } } }memset(du,0,sizeof(du)); for (i=1;i<=n;++i) siz[i]=1LL; for (i=1;i<=m;++i) if (dis[edge[i].en]==dis[edge[i].st]+edge[i].va) ++du[edge[i].st]; for (head=tail=0,i=1;i<=n;++i) if (!du[i]&&dis[i]<dis[0]){que[++tail]=i;siz[i]=1LL;} while(head!=tail){ u=que[++head]; for (i=point1[u];i;i=next1[i]){ v=edge1[i].en; if (dis[u]==dis[v]+edge[i].va){ edge[i].cnt+=ji[v]*siz[u]; siz[v]+=siz[u];--du[v]; if (!du[v]) que[++tail]=v; } } } } int main(){ int i,j,u,v,vv;scanf("%d%d",&n,&m); for (i=1;i<=m;++i){scanf("%d%d%d",&u,&v,&vv);add(u,v,vv);} for (i=1;i<=n;++i){spfa(i);work();} for (i=1;i<=m;++i) printf("%lld\n",edge[i].cnt%p); }
code(!!!)
题目大意:给定一个字符串,求最短的不是这个字符串子序列的字符串(都仅由a~z组成)及种类数。
思路:考虑判断一个串是否是另一个串的子序列,我们都是尽量选最靠前的相应字符,那么我们可以对每一位向后面每一种字母最靠前的位置连边(如果不存在就向终点连),然后从起点也要连一下各种字母,然后跑最短路的时候更新方案数,就是答案了。
(这竟然是最短路!!!)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 5000000 #define siz 26 #define len 5000000 #define LL long long #define p 1000000007 using namespace std; int point[maxm]={0},tot=0,next[maxm]={0},en[maxm],dis[maxm],que[len+1],la[maxm],kk[maxm]={0}; char ss[maxm]; bool visit[maxm]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void spfa(int st){ int head=0,tail=0,i,j,u,v; memset(dis,127,sizeof(dis));dis[st]=0; que[++tail]=st;visit[st]=true;kk[st]=1; while(head!=tail){ head=head%len+1;u=que[head];visit[u]=false; for (i=point[u];i;i=next[i]){ v=en[i]; if (dis[v]>=dis[u]+1){ if (dis[v]>dis[u]+1){ dis[v]=dis[u]+1;kk[v]=kk[u]; }else kk[v]=(kk[v]+kk[u])%p; if (!visit[v]){ visit[v]=true;tail=tail%len+1;que[tail]=v; } } } } } int main(){ int l,i,j;scanf("%s",&ss);l=strlen(ss); for (i=0;i<siz;++i) la[i]=l+1; for (i=l-1;i>=0;--i){ for (j=0;j<siz;++j) add(i+1,la[j]); la[ss[i]-'a']=i+1; }for (i=0;i<siz;++i) add(0,la[i]); spfa(0);printf("%d %d\n",dis[l+1],kk[l+1]); }
noip模拟题
题目大意:给定一个三维空间,长宽两边相接,每秒最表面的一层木块会消去,问多少秒后所有木块消去。
思路:一个位置上的木块会消去,要么是最上面一个消去,要么是因为周围四个的木块消去而消去(所以连边不能管高度,而是把它和四周的点都连上),那么就是所有点的初始距离是高度,然后相邻点之间连边距离为1,跑出最短路之后取最远的就是答案了。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxm 1005 #define maxe 4000005 #define len 5000000 using namespace std; int map[maxm][maxm],dis[maxe],point[maxe]={0},next[maxe]={0},en[maxe],que[len+1], bi[maxm][maxm],tot=0,dx[4]={1,0,-1,0},dy[4]={0,1,0,-1},n,m,tt=0; bool visit[maxe]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} int spfa(){ int u,v,head=0,tail=0,i,j,ans=0; for (i=1;i<=tt;++i) visit[que[++tail]=i]=true; while(head!=tail){ visit[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]){ if (dis[v=en[i]]>dis[u]+1){ dis[v]=dis[u]+1; if (!visit[v]) visit[que[tail=tail%len+1]=v]=true; } } }for (i=1;i<=tt;++i) ans=max(ans,dis[i]); return ans; } int main(){ freopen("relic.in","r",stdin); freopen("relic.out","w",stdout); int i,j,x,y,k;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ scanf("%d",&map[i][j]); dis[bi[i][j]=++tt]=map[i][j]; }for (i=1;i<=n;++i) for (j=1;j<=m;++j) for (k=0;k<4;++k){ x=i+dx[k];y=j+dy[k]; if (x<1) x=n;if (x>n) x=1; if (y<1) y=m;if (y>m) y=1; add(bi[x][y],bi[i][j]); }printf("%d\n",spfa()); }
bzoj3669 魔法森林
题目大意:n个点m条边的无向图,求1~n的一条路径使得max ai + max bi最小。
思路:跑spfa的时候一条边一条边的加,加边的时候就把左右端点入队列,不用清数组,随时更新答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 50005 #define M 200005 #define inf 1000000000 #define len 1000000 using namespace std; struct use{ int s,t,a,b; bool operator<(const use&x)const{return b<x.b;} }ai[M],ed[M]; int point[N]={0},next[M],n,m,tot=0,dis[N]={0},que[len+1],head=0,tail=0; bool vi[N]={false}; void add(int u,int v,int a,int b){ next[++tot]=point[u];point[u]=tot;ed[tot]=(use){u,v,a,b}; next[++tot]=point[v];point[v]=tot;ed[tot]=(use){v,u,a,b};} void spfa(){ int i,j,u,v; while(head!=tail){ vi[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]) if (max(dis[u],ed[i].a)<dis[v=ed[i].t]){ dis[v]=max(dis[u],ed[i].a); if (!vi[v]) vi[que[tail=tail%len+1]=v]=true; } } } int main(){ int n,m,i,j,u,v,a,b,ans;ans=inf; scanf("%d%d",&n,&m); for (i=2;i<=n;++i) dis[i]=inf; for (i=1;i<=m;++i){ scanf("%d%d%d%d",&u,&v,&a,&b); ai[i]=(use){u,v,a,b}; }sort(ai+1,ai+m+1); for (i=1;i<=m;++i){ add(ai[i].s,ai[i].t,ai[i].a,ai[i].b); vi[que[tail=tail%len+1]=ai[i].s]=true; vi[que[tail=tail%len+1]=ai[i].t]=true; if (ai[i].b!=ai[i+1].b) spfa(); ans=min(ans,dis[n]+ai[i].b); }printf("%d\n",(ans==inf ? -1 : ans)); }
bzoj4394 Bessie
题目大意:给定一个nm的网格,不同的格子有不同的种类,求左上到右下的最短路长。
思路:不用连边,spfa的时候更新就可以了。
注意:1)没有路径的输出-1;2)紫色的格子到不能走的也要停。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define len 2000000 #define N 1005 #define M 4000000 using namespace std; struct use{int ki,x,y;}que[len+1]; int n,m,map[N][N]={0},dis[2][N][N],dx[4]={1,0,-1,0},dy[4]={0,1,0,-1}; bool visit[2][N][N]={false}; void spfa(){ int i,j,k,tail,xx,yy,head=0,va;use u; memset(dis,127/3,sizeof(dis)); que[tail=1]=(use){0,1,1}; visit[0][1][1]=true;dis[0][1][1]=0; while(head!=tail){ u=que[head=head%len+1]; visit[u.ki][u.x][u.y]=false; for (i=0;i<4;++i){ xx=u.x+dx[i];yy=u.y+dy[i];va=1; if (!map[xx][yy]||(map[xx][yy]==3&&!u.ki)) continue; if (map[xx][yy]==1) k=u.ki; if (map[xx][yy]==2||map[xx][yy]==3) k=1; if (map[xx][yy]==4){ while(map[xx][yy]==4){xx+=dx[i];yy+=dy[i];++va;} if (!map[xx][yy]||map[xx][yy]==3){xx-=dx[i];yy-=dy[i];--va;} if (map[xx][yy]==2) k=1; else k=0; }if (dis[k][xx][yy]>dis[u.ki][u.x][u.y]+va){ dis[k][xx][yy]=dis[u.ki][u.x][u.y]+va; if (!visit[k][xx][yy]){ visit[k][xx][yy]=true; que[tail=tail%len+1]=(use){k,xx,yy}; } } } } } int main(){ int i,j,ans;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&map[i][j]); spfa();ans=min(dis[0][n][m],dis[1][n][m]); printf("%d\n",(ans==dis[0][0][0] ? -1 : ans)); }
bzoj2118 墨墨的等式
题目大意:给定ai,问sigma ai*xi=b有非负整数解的b在bmin~bmax中有多少个。
思路:令p=min ai,对ai%p,从每个0~p-1的数x向(x+ai)%p连ai的边,最短路的dis就是%p后第一次的数y,(b-y)/p+1就是b的可能取值(每次+p都可以是一个新的b),答案就是calc(bmax)-calc(bmin-1)。(!!!)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define up 15 #define N 500005 #define M 6000005 #define LL long long using namespace std; struct use{ int u;LL d; bool operator<(const use&x)const{return d>x.d;} }; priority_queue <use> que; int point[N]={0},next[M],en[M],tot=0,ai[up],p; bool visit[N]={false};LL dis[N]={0LL},va[M]; void add(int u,int v,LL vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;} void spfa(){ int i,v;use u; memset(dis,127,sizeof(dis));dis[0]=0LL; que.push((use){0,0LL}); while(!que.empty()){ u=que.top();que.pop(); if (!visit[u.u]){ visit[u.u]=true; for (i=point[u.u];i;i=next[i]) if (dis[v=en[i]]>u.d+va[i]){ dis[v]=u.d+va[i]; que.push((use){v,dis[v]}); } } }dis[0]=p; } LL calc(LL x){ int i;LL ci=0LL; for (i=0;i<p;++i){ if (dis[i]==dis[N-1]||x<dis[i]) continue; ci+=(x-dis[i])/(LL)p+1; }return ci;} int main(){ int n,i,j;LL mx,mn; scanf("%d%I64d%I64d",&n,&mn,&mx);p=N; for (i=1;i<=n;++i){ scanf("%d",&ai[i]); if (ai[i]) p=min(p,ai[i]); }for (i=1;i<=n;++i){ if (!ai[i]) continue; for (j=0;j<p;++j) add(j,(j+ai[i])%p,(LL)ai[i]); }spfa(); printf("%I64d\n",calc(mx)-calc(mn-1LL)); }
bzoj4289 Tax
题目大意:给定一张n个点m条边无向图,经过每个点的代价是进入和出去的边权的较大值,求1到n的最小代价。
思路:将边建点,最简单的是m^2的建图,对一个点出去的边两两连边权值为较大值,然后求最短路。如果对一个点连出去的边按边权排序,并且把每条边拆成入边和出边,就可以按差分的权值建图了(!!!),具体的是对一条边的反向i'->正向i连边权为i的权值,i->排序后相邻的边j连边权为ij权值差,j->i连边权为0。然后跑最短路就可以了,这样是不包含起点和终点的代价的,加上就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define N 400005 #define M 2000005 #define inf 0x7fffffffffffffffLL #define LL long long using namespace std; struct use{ int u,v,p,va; bool operator<(const use&x)const{return va<x.va;} }ed[N],zh[N]; int point[N]={0},next[M],en[M],tot,t1,po1[N]={0},ne1[N],n,m,va[M]; LL dis[N]; bool visit[N]={false}; struct uu{ int u;LL d; bool operator<(const uu&x)const{return d>x.d;} }; priority_queue<uu> que; void add1(int u){ne1[t1]=po1[u];po1[u]=t1;} void add(int u,int v,int vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;} LL dij(){ int i,v;LL mn=inf;uu u; memset(dis,127,sizeof(dis)); for (i=po1[1];i;i=ne1[i]) que.push((uu){ed[i].p,dis[ed[i].p]=(LL)ed[i].va}); while(!que.empty()){ u=que.top();que.pop(); if (!visit[u.u]){ visit[u.u]=true; for (i=point[u.u];i;i=next[i]) if (dis[v=en[i]]>dis[u.u]+(LL)va[i]){ dis[v]=dis[u.u]+(LL)va[i]; que.push((uu){v,dis[v]}); } } }for (i=po1[n];i;i=ne1[i]) mn=min(mn,dis[ed[i].p^1]+ed[i].va); return mn; } int main(){ int i,j,u,v,top,vv;scanf("%d%d",&n,&m); for (t1=1,i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&vv); ed[++t1]=(use){u,v,t1,vv};add1(u); ed[++t1]=(use){v,u,t1,vv};add1(v); }for (tot=0,i=1;i<=n;++i){ for (top=0,j=po1[i];j;j=ne1[j]) zh[++top]=ed[j]; sort(zh+1,zh+top+1); for (j=1;j<=top;++j){ add(zh[j].p^1,zh[j].p,zh[j].va); if (j<top){ add(zh[j].p,zh[j+1].p,zh[j+1].va-zh[j].va); add(zh[j+1].p,zh[j].p,0); } } }printf("%I64d\n",dij()); }
bzoj3575 道路堵塞(!!!)
题目大意:给定一张有向图和一条最短路径,求最短路径上每一条边不能走的时候的最短路,如果没有输出-1。
思路:从前往后枚举删去每一条边,从这条边的起点跑最短路,对于从后面又回到最短路径上的用堆维护一下路径长度和回来的点,查询的时候就是先弹出所有回来的点在这条边之前的路径(回来的点靠前的路径的长度也是较小的,所以可以直接弹栈顶),然后把最小的输出,如果没有输出-1。同时dis数组不需要清,因为从前往后走的话,dis数组是单调不增的,其他数组用多少清多少就可以了。因为边很少,所以这种做法可以过。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define N 100005 #define M 200005 #define len 1000000 using namespace std; struct use{int u,v,va;}ed[M]; struct uu1{ int u,va; bool operator<(const uu1&x)const{return va>x.va;} }; priority_queue<uu1> heap; int point[N]={0},next[M],tot=0,zh[N]={0},sum[N]={0},dis[N],que[len+1],st[N],li[N],id[N]={0}; bool vi[N]={false},ctg[M]={false},od[N]={false},us[N]={false}; void add(int u,int v,int vv){next[++tot]=point[u];point[u]=tot;ed[tot]=(use){u,v,vv};} void spfa(int uu,int nd){ int i,u,v,head=0,tail;zh[0]=0; dis[uu]=nd;vi[que[tail=1]=uu]=true; while(head!=tail){ vi[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]) if (!ctg[i]&&dis[v=ed[i].v]>dis[u]+ed[i].va){ dis[v]=dis[u]+ed[i].va; if (od[v]){ if (!us[v]){zh[++zh[0]]=v;us[v]=true;} }else if (!vi[v]) vi[que[tail=tail%len+1]=v]=true; } }for (i=1;i<=zh[0];++i){ heap.push((uu1){id[zh[i]],dis[zh[i]]+sum[zh[i]]});us[zh[i]]=false; } } int main(){ int n,m,l,i,j,u,v,c,nd=0; scanf("%d%d%d",&n,&m,&l); for (i=1;i<=m;++i){scanf("%d%d%d",&u,&v,&c);add(u,v,c);} for (i=1;i<=l;++i){ scanf("%d",&li[i]); od[ed[li[i]].v]=true;id[ed[li[i]].v]=i; }od[1]=true; for (i=l;i;--i) sum[ed[li[i]].u]=sum[ed[li[i]].v]+ed[li[i]].va; memset(dis,127,sizeof(dis)); for (i=1;i<=l;++i){ ctg[li[i]]=true;spfa(ed[li[i]].u,nd);ctg[li[i]]=false; while(!heap.empty()&&heap.top().u<i) heap.pop(); if (heap.empty()) printf("-1\n"); else printf("%d\n",heap.top().va); nd+=ed[li[i]].va; } }
bzoj2304 寻路
题目大意:给出n个不相交的矩形,每次可以在矩形边界上改变方向,求从S->T的最短路。
思路:离散化之后求出矩形端点四个方向最近的边,在那个边上建一个新点。求出所有关键点之后,对于同一条水平线或者竖直线上相邻点且不经过矩形的可以连边,经过矩形可以看作是前一个点在某个矩形的前边或者后一个点在某个矩形的后边上。spfa求最短路。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<ctime> #define N 1005 #define M 200005 #define nn 2000005 #define LL long long using namespace std; int in(){ char ch=getchar();int x=0,f=1; while((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if (ch=='-'){f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x*f;} struct point{ int x,y,id,po; bool operator<(const point &xx)const{return (x==xx.x ? y<xx.y : x<xx.x);} }ai[M]; struct rec{int x1,y1,x2,y2;}bi[N]; struct edge{ int p,l,r,id; bool operator<(const edge&xx)const{return p<xx.p;} }rx[N<<1],ry[N<<1]; int xz,yz,poi[M],next[nn],en[nn],tot,que[M+1],si,ti,xi[N<<1],yi[N<<1],n,tt,rxz,ryz, xs,ys,xt,yt; LL dis[M],va[nn],cg[N<<1][N<<1]; bool vi[M]={false}; void add(int u,int v,LL vv){ next[++tot]=poi[u];poi[u]=tot;en[tot]=v;va[tot]=vv; next[++tot]=poi[v];poi[v]=tot;en[tot]=u;va[tot]=vv;} int ab(int x){return (x<0 ? -x : x);} LL getd(point a,point b){return (LL)ab(xi[a.x]-xi[b.x])+(LL)ab(yi[a.y]-yi[b.y]);} int getx(int x){return upper_bound(xi+1,xi+xz+1,x)-xi-1;} int gety(int y){return upper_bound(yi+1,yi+yz+1,y)-yi-1;} void getn(int x,int y,int ii){ if (cg[x][y]) return; ai[++tt]=(point){x,y,ii,tt}; cg[x][y]=true; if (x==xs&&y==ys) si=tt; if (x==xt&&y==yt) ti=tt; } int cmpx(const point&a,const point&b){return (a.x==b.x ? a.y<b.y : a.x<b.x);} int cmpy(const point&a,const point&b){return (a.y==b.y ? a.x<b.x : a.y<b.y);} bool xin(int x,int i){return (x>bi[i].x1&&x<bi[i].x2);} bool yin(int y,int i){return (y>bi[i].y1&&y<bi[i].y2);} void find(int x,int y,int ii){ int i,j;getn(x,y,ii); j=(x==xs&&y==ys ? si : ti); if (!ii){ for (i=1;i<=rxz;++i) if (rx[i].p==x&&y>=rx[i].l&&y<=rx[i].r) ai[j].id=rx[i].id; for (i=1;i<=ryz;++i) if (ry[i].p==y&&x>=ry[i].l&&x<=ry[i].r) ai[j].id=ry[i].id; }for (i=1;i<=rxz;++i) if (rx[i].p>x&&y>=rx[i].l&&y<=rx[i].r){ getn(rx[i].p,y,rx[i].id); break; } for (i=rxz;i;--i) if (rx[i].p<x&&y>=rx[i].l&&y<=rx[i].r){ getn(rx[i].p,y,rx[i].id); break; } for (i=1;i<=ryz;++i) if (ry[i].p>y&&x>=ry[i].l&&x<=ry[i].r){ getn(x,ry[i].p,ry[i].id); break; } for (i=ryz;i;--i) if (ry[i].p<y&&x>=ry[i].l&&x<=ry[i].r){ getn(x,ry[i].p,ry[i].id); break; } } void pre(){ int i,j,k,x1,y1,x2,y2; sort(xi+1,xi+xz+1); xz=unique(xi+1,xi+xz+1)-xi-1; sort(yi+1,yi+yz+1); yz=unique(yi+1,yi+yz+1)-yi-1; xs=getx(xs);ys=gety(ys); xt=getx(xt);yt=gety(yt); si=ti=0; for (i=1;i<=n;++i){ x1=bi[i].x1=getx(bi[i].x1); y1=bi[i].y1=gety(bi[i].y1); x2=bi[i].x2=getx(bi[i].x2); y2=bi[i].y2=gety(bi[i].y2); rx[++rxz]=(edge){x1,y1,y2,i}; rx[++rxz]=(edge){x2,y1,y2,i}; ry[++ryz]=(edge){y1,x1,x2,i}; ry[++ryz]=(edge){y2,x1,x2,i}; }sort(rx+1,rx+rxz+1); sort(ry+1,ry+ryz+1); for (i=1;i<=n;++i){ x1=bi[i].x1;y1=bi[i].y1; x2=bi[i].x2;y2=bi[i].y2; find(x1,y1,i); find(x1,y2,i); find(x2,y1,i); find(x2,y2,i); }find(xs,ys,0); find(xt,yt,0); sort(ai+1,ai+tt+1,cmpx); for (i=1;i<=tt;i=j+1){ for (j=i;j<tt&&ai[j+1].x==ai[j].x;++j); for (k=i+1;k<=j;++k){ if (ai[k].id&&xin(ai[k].x,ai[k].id)&&ai[k].y==bi[ai[k].id].y2) continue; if (ai[k-1].id&&xin(ai[k-1].x,ai[k-1].id)&&ai[k-1].y==bi[ai[k-1].id].y1) continue; add(ai[k-1].po,ai[k].po,getd(ai[k],ai[k-1])); } }sort(ai+1,ai+tt+1,cmpy); for (i=1;i<=tt;i=j+1){ for (j=i;j<tt&&ai[j+1].y==ai[j].y;++j); for (k=i+1;k<=j;++k){ if (ai[k].id&&yin(ai[k].y,ai[k].id)&&ai[k].x==bi[ai[k].id].x2) continue; if (ai[k-1].id&&yin(ai[k-1].y,ai[k-1].id)&&ai[k-1].x==bi[ai[k-1].id].x1) continue; add(ai[k-1].po,ai[k].po,getd(ai[k],ai[k-1])); } } } LL spfa(){ int i,u,v,head,tail; head=tail=0; memset(dis,127,sizeof(dis)); dis[si]=0LL; vi[que[tail=1]=si]=true; while(head!=tail){ vi[u=que[head=head%M+1]]=false; for (i=poi[u];i;i=next[i]) if (dis[v=en[i]]>dis[u]+va[i]){ dis[v]=dis[u]+va[i]; if (!vi[v]) vi[que[tail=tail%M+1]=v]=true; } }return dis[ti];} int main(){ int i,t,x1,y1,x2,y2; LL dd;t=in(); memset(cg,false,sizeof(cg)); while(t--){ xz=yz=tot=tt=rxz=ryz=0; memset(poi,0,sizeof(poi)); xs=in();ys=in();xt=in();yt=in(); xi[++xz]=xs;xi[++xz]=xt; yi[++yz]=ys;yi[++yz]=yt; n=in(); for (i=1;i<=n;++i){ x1=in();y1=in();x2=in();y2=in(); if (x1>x2) swap(x1,x2); if (y1>y2) swap(y1,y2); xi[++xz]=x1;xi[++xz]=x2; yi[++yz]=y1;yi[++yz]=y2; bi[i]=(rec){x1,y1,x2,y2}; }pre();dd=spfa(); if (dd==dis[0]) printf("No Path\n"); else printf("%I64d\n",dd); for (i=1;i<=tt;++i) cg[ai[i].x][ai[i].y]=false; } }
(四)强连通分量
bzoj1051 受欢迎的牛
题目大意:给定一张有向图,求其他所有点都能到的点的数量。
思路:强连通分量缩点之后,那些答案的点一定是出度为0的强连通分量里的,并且要求其他点可达,即有且仅有一个出度为0的强连通分量,它的大小就是答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 50005 using namespace std; int point[maxnode]={0},next[maxnode]={0},en[maxnode]={0},sccno[maxnode]={0},pre[maxnode]={0}, lowlink[maxnode]={0},scnt=0,ti=0,tot=0,zhan[maxnode]={0},du[maxnode]={0},siz[maxnode]={0}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void tarjan(int u) { int i,j,v; pre[u]=lowlink[u]=++ti;zhan[++zhan[0]]=u; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){ tarjan(v);lowlink[u]=min(lowlink[u],lowlink[v]); }else if(!sccno[v]){ lowlink[u]=min(lowlink[u],pre[v]); } } if (lowlink[u]==pre[u]){ ++scnt; while(zhan[0]){ v=zhan[zhan[0]--];sccno[v]=scnt; ++siz[scnt];if (v==u) break; } } } int main() { int n,m,i,j,u,v,ans=0;scanf("%d%d",&n,&m); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);} for (i=1;i<=n;++i) if (!pre[i]) tarjan(i); tot=0; for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if (sccno[en[j]]!=sccno[i]) ++du[sccno[i]]; for (i=1;i<=scnt;++i) if (du[i]==0){ if (ans){ans=0;break;} else ans=siz[i]; }printf("%d\n",ans); }
bzoj1179 Atm
题目大意:给定一张有向图,和每个点的点权,求从某一点出发到某些点的最大权值和(每个点可以重复走,但权值只能加一遍)。
思路:强连通分量缩点之后,最长路就是答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 500005 #define len 1000005 using namespace std; int point[maxnode]={0},next[maxnode]={0},en[maxnode]={0},sccno[maxnode]={0},ti=0,scnt=0, pre[maxnode]={0},lowlink[maxnode]={0},zhan[maxnode]={0},dis[maxnode]={0},va[maxnode]={0}, point2[maxnode]={0},next2[maxnode]={0},en2[maxnode]={0},pub[maxnode]={0},tot=0, val[maxnode]={0},que[len+1]={0},n; bool visit[maxnode]={0}; void add(int u,int v) { next[++tot]=point[u];point[u]=tot;en[tot]=v; } void add2(int u,int v) { next2[++tot]=point2[u];point2[u]=tot;en2[tot]=v; } void tarjan(int u) { int v,i,j;pre[u]=lowlink[u]=++ti; zhan[++zhan[0]]=u; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){tarjan(v);lowlink[u]=min(lowlink[u],lowlink[v]);} else if (!sccno[v]) lowlink[u]=min(pre[v],lowlink[u]); } if (lowlink[u]==pre[u]){ ++scnt; while(zhan[0]){ v=zhan[zhan[0]--];sccno[v]=scnt; val[scnt]+=va[v];if (v==u) break; } } } void prew() { int i,j; for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if (sccno[i]!=sccno[en[j]]) add2(sccno[i],sccno[en[j]]); } void spfa(int st) { int i,j,u,v,head,tail;head=tail=0; que[++tail]=st;dis[st]=val[st];visit[st]=true; while(head<tail){ head=head%len+1;u=que[head];visit[u]=false; for (i=point2[u];i;i=next2[i]){ v=en2[i]; if (dis[u]+val[v]>dis[v]){ dis[v]=dis[u]+val[v]; if (!visit[v]){ tail=tail%len+1; que[tail]=v;visit[v]=true; } } } } } int main() { int m,i,j,p,u,v,st,ans=0;scanf("%d%d",&n,&m); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);} for (i=1;i<=n;++i) scanf("%d",&va[i]); scanf("%d%d",&st,&p); for (i=1;i<=p;++i) scanf("%d",&pub[i]); for (i=1;i<=n;++i) if (!pre[i]) tarjan(i); tot=0;prew();spfa(sccno[st]); for (i=1;i<=p;++i) ans=max(ans,dis[sccno[pub[i]]]); printf("%d\n",ans); }
bzoj2438 杀人游戏
题目大意:有n个人和m种认识关系(如a认识b),一名警察如果问到平民,他会告诉警察所有他认识的人的身份,如果问到杀手,他会杀了警察。求最大的能使警察自保并找出罪犯的概率。
思路:强连通分量缩点之后我们发现,只要询问那些入度为0的点,就可以知道所有人的身份,这个的概率是(n-cnt)/n(cnt为缩点后图中入度为0的点的个数,n为原图中的点数),但是我们又发现如果一个点入度为0,并且所有孩子的入度>1,并且它在原图中仅有一个点(!!!),那么这个点可以不用询问。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 300005 using namespace std; int point[maxnode]={0},next[maxnode]={0},en[maxnode]={0},sccno[maxnode]={0},tot=0,scnt, pre[maxnode]={0},lowlink[maxnode]={0},ti=0,zhan[maxnode]={0},ru[maxnode]={0}, point2[maxnode]={0},next2[maxnode]={0},en2[maxnode]={0},siz[maxnode]={0}; void add(int u,int v) { next[++tot]=point[u];point[u]=tot;en[tot]=v; } void add2(int u,int v) { next2[++tot]=point2[u];point2[u]=tot;en2[tot]=v; } void tarjan(int u) { int v,i,j;pre[u]=lowlink[u]=++ti; zhan[++zhan[0]]=u; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){tarjan(v);lowlink[u]=min(lowlink[u],lowlink[v]);} else if (!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); } if (pre[u]==lowlink[u]){ ++scnt; while(zhan[0]){ v=zhan[zhan[0]--];sccno[v]=scnt; ++siz[scnt];if (u==v) break; } } } int main() { int n,m,i,j,u,v;double ans; bool f;scanf("%d%d",&n,&m); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);} for (i=1;i<=n;++i) if (!pre[i]) tarjan(i); tot=0; for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]){ v=en[j]; if (sccno[v]!=sccno[i]) {++ru[sccno[v]];add2(sccno[i],sccno[v]);} }ans=(double)n; for (i=1;i<=scnt;++i) if (!ru[i]) ans-=1.0; for (i=1;i<=scnt;++i) if (ru[i]==0&&siz[i]==1){ f=true; for (j=point2[i];j;j=next2[j]) if (ru[en2[j]]==1) f=false; if (f){ans+=1.0;break;} }printf("%.6f\n",ans*1.0/(double)n); }
bzoj2427 软件安装
题目大意:给定n个软件的体积、价值和依赖的软件。一个软件的价值能累加给答案只有它依赖的选中才能发生。
思路:强连通分量缩点之后一定是一颗树(选了父亲才能选儿子的树),所以在树上背包一下。这里注意是多重背包,同时必须要选父亲(一开始因为这里wa了好久)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1005 using namespace std; int point[maxnode]={0},next[maxnode]={0},en[maxnode]={0},sccno[maxnode]={0},scnt=0, zhan[maxnode]={0},point2[maxnode]={0},next2[maxnode]={0},en2[maxnode]={0},wi[maxnode]={0}, vi[maxnode]={0},fi[maxnode][maxnode]={0},tot=0,pre[maxnode]={0},lowlink[maxnode]={0},ti=0, ww[maxnode]={0},vv[maxnode]={0},ru[maxnode]={0},m; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void add2(int u,int v){next2[++tot]=point2[u];point2[u]=tot;en2[tot]=v;} void tarjan(int u){ int v,i,j;pre[u]=lowlink[u]=++ti; zhan[++zhan[0]]=u; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){tarjan(v);lowlink[u]=min(lowlink[u],lowlink[v]);} else if (!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); } if (pre[u]==lowlink[u]){ ++scnt; while(zhan[0]){ v=zhan[zhan[0]--];ww[scnt]+=wi[v]; vv[scnt]+=vi[v];sccno[v]=scnt; if (u==v) break; } } } void dp(int u){ int i,j,k; for (i=point2[u];i;i=next2[i]){ dp(en2[i]); for (j=m;j>=0;--j) for (k=0;k<=j;++k) fi[u][j]=max(fi[u][j],fi[u][j-k]+fi[en2[i]][k]); } for (i=m;i>=0;--i) { if (i>=ww[u]) fi[u][i]=fi[u][i-ww[u]]+vv[u]; else fi[u][i]=0; } } int main() { int n,i,j,k,ans=0;scanf("%d%d",&n,&m); for (i=1;i<=n;++i)scanf("%d",&wi[i]); for (i=1;i<=n;++i)scanf("%d",&vi[i]); for (i=1;i<=n;++i){scanf("%d",&j);if (j) add(j,i);} for (i=1;i<=n;++i) if (!pre[i]) tarjan(i); tot=0; for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if (sccno[i]!=sccno[en[j]]){add2(sccno[i],sccno[en[j]]);++ru[sccno[en[j]]];} j=scnt+1; for (i=1;i<=scnt;++i) if (!ru[i]) add2(j,i); dp(j); printf("%d\n",fi[j][m]); }
bzoj1093 最大半联通子图
题目大意:求一个最大的子图,使得其中任意两点都能联通(一个到另一个就可以了,不一定要互相到达),求这种子图的个数。
思路:强连通分量缩点,最长路就是答案了,在做的时候更新一下个数。但是写spfa tle了,用拓扑序的dp能a。关于去重边的问题,可以用map也可以拓扑的时候判断一下上一次到这个点的点是不是同一个。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define maxm 1000005 #define len 10000000 using namespace std; int point[maxm]={0},en[maxm]={0},next[maxm]={0},sccno[maxm]={0},scnt=0,ti=0,zhan[maxm]={0}, pre[maxm]={0},lowlink[maxm]={0},point2[maxm]={0},next2[maxm]={0},en2[maxm]={0}, siz[maxm]={0},sum[maxm]={0},x,tot=0,ru[maxm]={0},dis[maxm]={0}; bool visit[maxm]={false}; map <int,int>cnt[maxm]; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void add2(int u,int v){ if (cnt[u][v]>0) return;++ru[v]; next2[++tot]=point2[u];point2[u]=tot;en2[tot]=v; cnt[u][v]=1; } void tarjan(int u){ int i,j,v;pre[u]=lowlink[u]=++ti; zhan[++zhan[0]]=u; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){tarjan(v);lowlink[u]=min(lowlink[u],lowlink[v]);} else if(!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); }if (pre[u]==lowlink[u]){ ++scnt; while(zhan[0]){ v=zhan[zhan[0]--];sccno[v]=scnt; ++siz[scnt];if (u==v) break; } } } void dp(){ int i,j,u,v;zhan[0]=0; for (i=1;i<=scnt;++i) if (!ru[i]){zhan[++zhan[0]]=i;dis[i]=siz[i];sum[i]=1;} while(zhan[0]){ u=zhan[zhan[0]--]; for (i=point2[u];i;i=next2[i]){ j=en2[i];--ru[j]; if (!ru[j]) zhan[++zhan[0]]=j; if (dis[u]+siz[j]>=dis[j]){ if (dis[u]+siz[j]>dis[j]){ dis[j]=dis[u]+siz[j];sum[j]=sum[u]%x; } else sum[j]=(sum[j]+sum[u])%x; } } } } int main() { int n,m,i,j,u,v,ans1=0,ans2=0;scanf("%d%d%d",&n,&m,&x); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);} for (i=1;i<=n;++i) if(!pre[i]) tarjan(i); tot=0; for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if (sccno[en[j]]!=sccno[i]) add2(sccno[i],sccno[en[j]]); dp(); for (i=1;i<=scnt;++i){ if (dis[i]>ans1){ans1=dis[i];ans2=sum[i]%x;} else if (dis[i]==ans1){ans2=(ans2+sum[i])%x;} }printf("%d\n%d\n",ans1,ans2); }
bzoj1924 所驼门王的宝藏
题目大意:给定一个r*c的网格,有n个点上有传送门(三种:同行传送、同列传送、周围8个传送),从某个点入某个点出,求最多能进入多少个点。
思路:用vector、map等维护连边后,强连通分量缩点后最长路就是了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #include<vector> #define maxm 5000005 #define maxe 100005 #define maxx 1000005 using namespace std; struct use{ int x,y; bool operator<(const use u)const{ return (x==u.x ? y<u.y : x<u.x); } }zz[maxe]={0}; int point[maxe]={0},en[maxm]={0},next[maxm]={0},sccno[maxe]={0},pre[maxe]={0},scnt=0,ti=0, lowlink[maxm]={0},point2[maxe]={0},en2[maxx]={0},next2[maxx]={0},tot=0,zhan[maxe]={0}, val[maxe]={0},n,m,tt[maxm]={0},dx[8]={-1,-1,-1,0,0,1,1,1},dy[8]={-1,0,1,-1,1,-1,0,1}; map<use,int> di; vector<int> hang[maxe]; vector<int> lie[maxe]; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void add2(int u,int v){next2[++tot]=point2[u];point2[u]=tot;en2[tot]=v;} void tarjan(int u){ int v,i,j;pre[u]=lowlink[u]=++ti; zhan[++zhan[0]]=u; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){tarjan(v);lowlink[u]=min(lowlink[u],lowlink[v]);} else if (!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); }if (pre[u]==lowlink[u]){ ++scnt; while(zhan[0]){ v=zhan[zhan[0]--];sccno[v]=scnt; ++val[scnt];if (v==u) break; } } } int work(){ int i,j,u,v,head=0,tail=0,ans=0; memset(pre,128,sizeof(pre)); for (i=1;i<=scnt;++i) if (!lowlink[i]){zhan[++tail]=i;pre[i]=val[i];} while(head!=tail){ u=zhan[++head]; for (i=point2[u];i;i=next2[i]){ v=en2[i];pre[v]=max(pre[v],pre[u]+val[v]); --lowlink[v];if (!lowlink[v]) zhan[++tail]=v; } }for (i=1;i<=scnt;++i) ans=max(ans,pre[i]); return ans; } int main(){ int i,j,x,y,x1,y1,r,c,t;scanf("%d%d%d",&n,&r,&c); for (i=1;i<=n;++i){ scanf("%d%d%d",&zz[i].x,&zz[i].y,&tt[i]); x=zz[i].x;y=zz[i].y; hang[x].push_back(i);lie[y].push_back(i); di[(use){x,y}]=i; }for (i=1;i<=n;++i){ x=zz[i].x;y=zz[i].y; if (tt[i]==1) for (j=0;j<hang[x].size();++j){if (hang[x][j]==i) continue;add(i,hang[x][j]);} if (tt[i]==2) for (j=0;j<lie[y].size();++j){if (lie[y][j]==i) continue;add(i,lie[y][j]);} if (tt[i]==3) for (j=0;j<8;++j){ x1=x+dx[j];y1=y+dy[j]; if (x1<1||x1>r||y1<1||y1>c) continue; t=di[(use){x1,y1}];if (t==0) continue; add(i,t); } }for (i=1;i<=n;++i) if (!pre[i]) tarjan(i); memset(lowlink,0,sizeof(lowlink)); for (tot=0,i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if (sccno[i]!=sccno[en[j]]){add2(sccno[i],sccno[en[j]]);++lowlink[sccno[en[j]]];} printf("%d\n",work()); }
bzoj1194 潘多拉的盒子
题目大意:给定s个咒语机,每个咒语机是二进制的自动机,有n个点,m个输出节点,一开始都在0号点。如果i能产生的序列j都能产生,j是i的升级,求最长的升级链长。
思路:先枚举ij,然后记忆化搜索判断能否升级。连出有向图之后,tarjan缩点,dp。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 55 #define M 10000 using namespace std; int tr[N][N][2]={0},nt[N],mt[N],gi[N]={0},point[N]={0},next[M],en[M],siz[N]={0},s, po1[N]={0},ne1[M],en1[M],du[N]={0},scnt=0,sccno[N]={0},zh[N]={0},pre[N]={0},low[N]={0}, tot=0,tt=0; bool vi[N][N],f,wr[N][N]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} void add1(int u,int v){ne1[++tot]=po1[u];po1[u]=tot;en1[tot]=v;++du[v];} void dfs(int a,int b,int x,int y){ if (!f) return; if (vi[x][y]) return;vi[x][y]=true; if (wr[a][x]&&!wr[b][y]){f=false;return;} dfs(a,b,tr[a][x][0],tr[b][y][0]); dfs(a,b,tr[a][x][1],tr[b][y][1]); } void tarjan(int u){ int i,v;zh[++zh[0]]=u; pre[u]=low[u]=++tt; for (i=point[u];i;i=next[i]){ v=en[i]; if (!pre[v]){ tarjan(v);low[u]=min(low[u],low[v]); }else if (!sccno[v]) low[u]=min(low[u],pre[v]); }if (low[u]==pre[u]){ ++scnt; while(zh[0]){ ++siz[sccno[v=zh[zh[0]--]]=scnt]; if (v==u) break; } } } int dp(){ int i,u,v,head=0,tail=0,ans=0; memset(gi,0,sizeof(gi)); for (i=1;i<=scnt;++i) if (!du[i]) gi[zh[++tail]=i]=siz[i]; while(head<tail){ u=zh[++head]; for (i=po1[u];i;i=ne1[i]){ --du[v=en1[i]];if (!du[v]) zh[++tail]=v; gi[v]=max(gi[v],gi[u]+siz[v]); } }for (i=1;i<=scnt;++i) ans=max(ans,gi[i]); return ans; } int main(){ int i,j,k; scanf("%d",&s); for (i=1;i<=s;++i){ scanf("%d%d",&nt[i],&mt[i]); for (j=1;j<=mt[i];++j){ scanf("%d",&k);wr[i][++k]=true; }for (j=1;j<=nt[i];++j){ scanf("%d%d",&tr[i][j][0],&tr[i][j][1]); ++tr[i][j][0];++tr[i][j][1]; } }for (i=1;i<=s;++i) for (j=1;j<=s;++j){ if (i==j) continue; memset(vi,false,sizeof(vi));f=true; dfs(i,j,1,1);if (f) add(i,j); }for (i=1;i<=s;++i) if (!pre[i]) tarjan(i); for (tot=0,i=1;i<=s;++i) for (j=point[i];j;j=next[j]) if (sccno[i]!=sccno[en[j]]) add1(sccno[i],sccno[en[j]]); printf("%d\n",dp()); }
(五)双连通分量
poj3177 Redundant Paths
题目大意:给定一张无向图,问最少加多少条边使得整个图是边双连通的。
思路:可以求出所有边双连通分量,缩点之后形成一棵树结构,度数是1的点数个数为x,答案就是(x+1)/2。边双连通分量的求法:求出所有的桥,删掉之后形成的每个联通块缩点,再连上桥边就形成一棵树了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 20005 using namespace std; int point[N]={0},next[N],en[N],low[N],pre[N],dt=0,tot,sccno[N]={0},scnt=0,du[N]={0}; bool fb[N]={false}; void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u; } int dfs(int u,int fe){ int i,lowv,v,sz=0; low[u]=pre[u]=++dt; for (i=point[u];i;i=next[i]){ if (!pre[v=en[i]]){ ++sz;lowv=dfs(v,i); low[u]=min(low[u],lowv); if (lowv>pre[u]) fb[i]=fb[i^1]=true; }else if ((i^1)!=fe) low[u]=min(low[u],pre[v]); }return low[u]; } void getg(int u){ int i;if (sccno[u]) return; sccno[u]=scnt; for (i=point[u];i;i=next[i]){ if (fb[i]) continue; getg(en[i]); } } int main(){ int n,m,i,j,u,v,ans=0; scanf("%d%d",&n,&m);tot=1; for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);} dfs(1,0); for (i=1;i<=n;++i) if (!sccno[i]){++scnt;getg(i);} for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if (fb[j]){++du[sccno[i]];++du[sccno[en[j]]];} for (i=1;i<=scnt;++i) if (du[i]==2) ++ans; printf("%d\n",(ans+1)>>1); }
(六)分层图
bzoj2763 飞行路线
题目大意:给定一个无向图,求起点到终点的最短路(可以将其中不多于k条边的价值改为0)。
思路:分层图。建k+1层,每一层连出所有的边,对每一条边从上一层起点向下一层终点、上一层终点向下一层起点连有向边,求最短路之后,取每一层t处的最小值。(好像卡spfa,反正我tle了。。。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define maxm 4000005 using namespace std; struct use{ int u,dis; bool operator<(const use x)const {return dis>x.dis;} }; int point[maxm]={0},next[maxm]={0},en[maxm]={0},dis[maxm]={0},va[maxm]={0}, tot=0,cnt[15][10005]={0}; bool visit[maxm]={false}; priority_queue<use> que; void add(int u,int v,int vaa){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vaa;} void dij(int s){ int i,j,v;use u; memset(dis,127,sizeof(dis));dis[s]=0; que.push((use){s,0}); while(!que.empty()){ u=que.top();que.pop(); if (!visit[u.u]){ visit[u.u]=true; for (i=point[u.u];i;i=next[i]){ v=en[i]; if (dis[v]>dis[u.u]+va[i]){ dis[v]=dis[u.u]+va[i]; que.push((use){v,dis[v]}); } } } } } int main() { int n,m,s,t,u,v,vaa,i,j,k,ans; scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);u=n;++s;++t; for (i=1;i<=n;++i) cnt[0][i]=i; for (i=1;i<=k;++i) for (j=1;j<=n;++j) cnt[i][j]=++u; for (i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&vaa);++u;++v; for (j=1;j<=k;++j){add(cnt[j-1][u],cnt[j][v],0);add(cnt[j-1][v],cnt[j][u],0);} for (j=0;j<=k;++j){add(cnt[j][u],cnt[j][v],vaa);add(cnt[j][v],cnt[j][u],vaa);} }dij(s);ans=dis[t]; for (i=1;i<=k;++i) ans=min(ans,dis[cnt[i][t]]); printf("%d\n",ans); }
(七)斯坦纳树
一类求在给定图上某些节点的最小生成树(可以经过其他点)。
bzoj2595 游览计划
题目大意:斯坦纳树(点带权)+输出方案。
思路:设fi[i][j][k]表示到(i,j)这个点、联通状态为k的时候的最小权值和。fi[i][j][k]=min(fi[i][j][p]+fi[i][j][k-p|bi[i][j]]),fi[x][y][k|bi[x][y]]=min(fi[i][j][k]+map[x][y]),第一个方程就是状压dp,第二个spfa更新一下。
有一个技巧:穷举k的子集可以写成(x=k;x;x=k&(x-1))
注意状压dp的时候,如果当前是景点并且状态中未联通这个就要continue掉。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define len 5000000 #define inf 2100000000LL using namespace std; struct use{ int x,y,k; }que[len+1],pre[15][15][1<<11]; int n,m,fi[15][15][1<<11],map[15][15]={0},bi[15][15]={0},tot=0,ans[15][15]={0},head,tail, dx[4]={0,1,0,-1},dy[4]={1,0,-1,0}; bool visit[15][15][1<<11]={false}; void spfa(int k){ int x,y,t;use u,v; while(head!=tail){ head=head%len+1;u=que[head];visit[u.x][u.y][k]=false; for (t=0;t<4;++t){ x=u.x+dx[t];y=u.y+dy[t]; if (x<1||x>n||y<1||y>m) continue; if (fi[x][y][u.k|bi[x][y]]>fi[u.x][u.y][k]+map[x][y]){ fi[x][y][u.k|bi[x][y]]=fi[u.x][u.y][k]+map[x][y]; pre[x][y][u.k|bi[x][y]]=(use){u.x,u.y,k}; if (!visit[x][y][k]){ visit[x][y][k]=true;tail=tail%len+1; que[tail]=(use){x,y,k}; } } } } } void dfs(int x,int y,int k){ if (!x||!y) return; if (!map[x][y]) ans[x][y]=3; else ans[x][y]=2; use v=pre[x][y][k]; dfs(v.x,v.y,v.k); if (v.x==x&&v.y==y) dfs(x,y,k-v.k|bi[x][y]); } int main(){ int i,j,k,u,v,x,y,dis;scanf("%d%d",&n,&m); memset(fi,127/3,sizeof(fi)); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ scanf("%d",&map[i][j]); if (!map[i][j]){ fi[i][j][1<<tot]=0; bi[i][j]=1<<tot;++tot; }fi[i][j][0]=map[i][j];ans[i][j]=1; }for (k=1;k<(1<<tot);++k){ head=tail=0; for (i=1;i<=n;++i) for (j=1;j<=m;++j){ if (!map[i][j]&&!(k&bi[i][j])) continue; for (x=k;x;x=k&(x-1)){ y=fi[i][j][x|bi[i][j]]+fi[i][j][(k-x)|bi[i][j]]-map[i][j]; if (y<fi[i][j][k]){ fi[i][j][k]=y;pre[i][j][k]=(use){i,j,x|bi[i][j]}; } }if (fi[i][j][k]<inf){ visit[i][j][k]=true;que[++tail]=(use){i,j,k}; } }spfa(k); }for (dis=inf,i=1;i<=n;++i) for (j=1;j<=m;++j) if (!map[i][j]&&fi[i][j][(1<<tot)-1]<dis){dis=fi[i][j][(1<<tot)-1];x=i;y=j;} printf("%d\n",dis);dfs(x,y,(1<<tot)-1); for (i=1;i<=n;++i){ for (j=1;j<=m;++j){ if (ans[i][j]==1) printf("_"); if (ans[i][j]==2) printf("o"); if (ans[i][j]==3) printf("x"); }printf("\n"); } }
bzoj4006 管道连接
题目大意:给定一张有边权的无向图,有p(p<=10)个关键点,有不同的频道,要求相同频道的必须连接,求最小代价。
思路:带颜色的斯坦纳树。可以状压那些颜色一起连接,求斯坦纳树,最后小dp一下,更新最优答案。
注意:spfa的时候u即使在que中,dis也是不断更新的,所以不能在que中存dis来用。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define N 1500 #define M 10005 #define len 1000000 #define up 10 #define inf 1061109567 using namespace std; int point[N]={0},next[M],tot,fm[15][15]={0},zh[15][2],fi[N][N],gi[N],ci[N],head,tail,ct[N], en[M],va[M],que[len+1]; bool vi[N]={false}; void add(int u,int v,int vv){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vv;} void spfa(int kk){ int i,u,v; while(head!=tail){ vi[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]) if (fi[kk][v=en[i]]>fi[kk][u]+va[i]){ fi[kk][v]=fi[kk][u]+va[i]; if (!vi[v]) vi[que[tail=tail%len+1]=v]=true; } } } int main(){ int n,m,p,i,j,k,u,v,vv,c,x; scanf("%d%d%d",&n,&m,&p); for (tot=i=1;i<=m;++i){scanf("%d%d%d",&u,&v,&vv);add(u,v,vv);} for (i=1;i<=p;++i){ scanf("%d%d",&u,&v); fm[u][++fm[u][0]]=v; }for (p=c=0,i=1;i<=up;++i){ if (fm[i][0]<=1) continue; for (++c,j=1;j<=fm[i][0];++j){ zh[++p][0]=fm[i][j]; zh[p][1]=c; } }for (i=1;i<(1<<c);++i){ for (j=0;j<(1<<p);++j) for (k=1;k<=n;++k) fi[j][k]=inf; memset(ct,0,sizeof(ct)); for (tot=0,j=1;j<=p;++j) if ((1<<(zh[j][1]-1))&i){ fi[1<<tot][zh[j][0]]=0; ct[zh[j][0]]=1<<tot;++tot; } for (j=1;j<(1<<tot);++j){ head=tail=0; for (k=1;k<=n;++k){ if (ct[k] && !(j&ct[k])) continue; for (x=j;x;x=(x-1)&j) fi[j][k]=min(fi[j][k],fi[x][k]+fi[j-x][k]); if (fi[j][k]<inf) vi[que[++tail]=k]=true; }spfa(j); }for (gi[i]=inf,j=1;j<=n;++j) gi[i]=min(gi[i],fi[(1<<tot)-1][j]); }for (i=1;i<(1<<c);++i) for (j=i;j;j=(j-1)&i) gi[i]=min(gi[i],gi[j]+gi[j^i]); printf("%d\n",gi[(1<<c)-1]); }
省队集训 graph
题目大意:给出n*m的矩形,每个点有颜色(-1~n*m-1)和权值,求一个四联通块至少包含k种颜色且没有颜色-1的最小权值和。
思路:如果颜色比较少,可以用斯坦纳树做,fi[i][j][k]表示到(i,j)选的颜色是k的集合的四联通块最小权值和。颜色比较多,但k比较小,可以把所有颜色rand一个0~k-1的颜色,然后就是所有k种颜色都选的最小权值和,可以用斯坦纳树做。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<ctime> #define N 16 #define M 230 #define up 7 #define T 200 using namespace std; struct use{int x,y;}que[M+1]; int ai[N][N],co[N][N],cc[N][N],gi[M],fi[N][N][1<<up],n,m,kk,ans=0,head,tail, dx[4]={1,0,-1,0},dy[4]={0,1,0,-1}; bool vi[N][N]; void add(int &x,int y){x=min(x,y);} void spfa(int k){ int i,x,y,xx,yy; while(head<tail){ x=que[head=head%M+1].x; y=que[head].y; for (i=0;i<4;++i){ xx=x+dx[i];yy=y+dy[i]; if (xx<=0||xx>n||yy<=0||yy>m||cc[xx][yy]<0) continue; if (fi[xx][yy][k|(1<<cc[xx][yy])]>fi[x][y][k]+ai[xx][yy]){ fi[xx][yy][k|(1<<cc[xx][yy])]=fi[x][y][k]+ai[xx][yy]; if (!vi[xx][yy]){ vi[xx][yy]=true; que[tail=tail%M+1]=(use){xx,yy}; } } } } } void work(){ int i,j,k,a,b,inf; memset(fi,127/3,sizeof(fi)); inf=fi[0][0][0]; for (i=0;i<n*m;++i) gi[i]=rand()%kk; for (i=1;i<=n;++i) for (j=1;j<=m;++j){ if (co[i][j]<0) cc[i][j]=-1; else{ cc[i][j]=gi[co[i][j]]; fi[i][j][1<<cc[i][j]]=ai[i][j]; } } for (k=1;k<(1<<kk);++k){ head=tail=0; memset(vi,false,sizeof(vi)); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ if (cc[i][j]<0) continue; if (!((k>>cc[i][j])&1)) continue; for (a=k;a;a=(a-1)&k){ if (!((a>>cc[i][j])&1)) continue; b=(k-a)|(1<<cc[i][j]); add(fi[i][j][k],fi[i][j][a]+fi[i][j][b]-ai[i][j]); }if (fi[i][j][k]<inf){ vi[i][j]=true;que[++tail]=(use){i,j}; } } spfa(k); }for (i=1;i<=n;++i) for (j=1;j<=m;++j) ans=min(ans,fi[i][j][(1<<kk)-1]); } int main(){ freopen("graph.in","r",stdin); freopen("graph.out","w",stdout); int i,j;scanf("%d%d%d",&n,&m,&kk); for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&co[i][j]); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ scanf("%d",&ai[i][j]); ans+=ai[i][j]; } srand(time(0)); for (i=1;i<=T;++i) work(); printf("%d\n",ans); }
(八)对偶图
bzoj4423 Bytehattan
题目大意:给定一张网格图,每次删掉网格图中一条边,问边的两端点是否连通。(强制在线)
思路:考虑原图的对偶图,每次把删的边两侧的区域连起来,会形成一些联通块,如果某次删的边两侧的区域已经在一个联通块内了,就说明删边后他们互不可达(!!!)。这个过程用并查集维护。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1505 using namespace std; int fa[N*N],la=0,n; char in(){ char ch=getchar(); while(ch<'A'||ch>'Z') ch=getchar(); return ch;} int root(int x){ if (fa[x]!=x) fa[x]=root(fa[x]); return fa[x];} int idx(int x,int y){ if (x<=0||x>=n||y<=0||y>=n) return 0; return (x-1)*(n-1)+y;} int work(int a,int b,char cc){ int x,y,r1,r2; if (cc=='N'){--a;x=a+1;y=b;} if (cc=='E'){--b;x=a;y=b+1;} r1=root(idx(a,b));r2=root(idx(x,y)); if (r1==r2){printf("NIE\n");return 1;} else{fa[r1]=r2;printf("TAK\n");return 0;} } int main(){ int i,a,b,k;char ch; scanf("%d%d",&n,&k); for (i=n*n;i>=0;--i) fa[i]=i; for (i=1;i<=k;++i){ scanf("%d%d",&a,&b);ch=in(); if (!la){ la=work(a,b,ch); scanf("%d%d",&a,&b);ch=in(); }else{ scanf("%d%d",&a,&b);ch=in(); la=work(a,b,ch); } } }
(九)其他
CODEVS3294 车站分级
题目描述 Description
一条单向的铁路线上,依次有编号为1, 2, …, n的n个火车站。每个火车站都有一个级别,最低为1级。现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次停靠了火车站x,则始发站、终点站之间所有级别大于等于火车站x的都必须停靠。(注意:起始站和终点站自然也算作事先已知需要停靠的站点)
例如,下表是5趟车次的运行情况。其中,前4趟车次均满足要求,而第5趟车次由于停靠了3号火车站(2级)却未停靠途经的6号火车站(亦为2级)而不满足要求。
现有m趟车次的运行情况(全部满足要求),试推算这n个火车站至少分为几个不同的级别。
思路: 拓扑排序,将这一趟火车始发站和终点站中没有出现的站点向出现的站点连一条有向边,拓扑排序查出有几层就可以了。但是,这里有一个问题,就是o(mn^2)的读入,和较大的计算量,于是就将读入稍稍减小了一些循环范围,达到最差o(mn^2/4)的读入复杂度。在拓扑排序中,也有一个小小的优化,每一层的入度为0的点都是又上一层删边时产生的,所以在删边的时候就可以将点加入到下一层中去,减少时间复杂度。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; struct use{ int num[1001],l; }uc,ucc; bool map[1001][1001]={false}; int r[1001]={0},bian[1001][1001]={0}; void work() { int tot=0,n,m,i,j,k,q,x,ans=0; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d",&q); uc.l=q;ucc.l=0; scanf("%d",&uc.num[1]); for (j=2;j<=q;++j) { scanf("%d",&uc.num[j]); for (k=uc.num[j-1]+1;k<uc.num[j];++k) { ++ucc.l;ucc.num[ucc.l]=k; } } for (j=1;j<=uc.l;++j) for (k=1;k<=ucc.l;++k) { if (!map[ucc.num[k]][uc.num[j]]) { map[ucc.num[k]][uc.num[j]]=true; ++bian[ucc.num[k]][0]; bian[ucc.num[k]][bian[ucc.num[k]][0]]=uc.num[j]; ++r[uc.num[j]]; } } } uc.l=0; for (i=1;i<=n;++i) if (r[i]==0) { ++uc.l; uc.num[uc.l]=i; } tot=uc.l; ans=1; while (tot<n) { ucc.l=0; for (i=1;i<=uc.l;++i) for (j=1;j<=bian[uc.num[i]][0];++j) { --r[bian[uc.num[i]][j]]; if (r[bian[uc.num[i]][j]]==0) { ++ucc.l; ucc.num[ucc.l]=bian[uc.num[i]][j]; } } uc=ucc; tot=tot+uc.l; ++ans; } cout<<ans<<endl; } int main() { work(); }
bzoj4531 路径
题目大意:给出一张无向图,点上有数字或者运算符或者小括号,问走k步能形成合法表达式的方案数(起终点不限)。(可以/0)
思路:fi[i][j][a][b]表示走了i步,到j,数字串开头是否是0,有多少个没有匹配的左括号的方案数。类似数位dp的转移。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define N 25 #define M 35 #define up 500 #define p 1000000007 using namespace std; int point[N],next[up],en[up],tot=0,fi[M][N][2][M],n,m,kk,fu[N]={0},ff[N]={0}; char ss[N]; void adde(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u;} void add(int &x,int y){x=(x+y)%p;} int dp(){ int i,k,a,b,c,ni,na,nb,ans=0; memset(fi,0,sizeof(fi)); for (i=1;i<=n;++i){ if (fu[i]){ if (ss[i]=='-') fi[1][i][0][0]=1; if (ss[i]=='(') fi[1][i][0][1]=1; }else{ if (ss[i]=='0') fi[1][i][1][0]=1; else fi[1][i][0][0]=1; } }for (k=1;k<kk;++k) for (i=1;i<=n;++i) for (a=0;a<2;++a) for (b=0;b<=k;++b){ if (!fi[k][i][a][b]) continue; for (c=point[i];c;c=next[c]){ ni=en[c]; if (!fu[i]&&!fu[ni]){ if (a) continue; na=a;nb=b; }else if (!fu[i]&&fu[ni]){ if (ss[ni]=='(') continue; na=0;nb=b-(ss[ni]==')'); }else if (fu[i]&&!fu[ni]){ if (ss[i]==')') continue; if (ss[ni]=='0') na=1; else na=0; nb=b; }else if (fu[i]&&fu[ni]){ na=0;nb=-1; if (ff[i]&&ss[ni]=='(') nb=b+1; if (ss[i]==')'&&ff[ni]) nb=b; if (ss[i]=='('&&ss[ni]=='(') nb=b+1; if (ss[i]==')'&&ss[ni]==')') nb=b-1; if (ss[i]=='('&&ss[ni]=='-') nb=b; }if (nb<0) continue; add(fi[k+1][ni][na][nb],fi[k][i][a][b]); } } for (i=1;i<=n;++i){ if (fu[i]&&ss[i]!=')') continue; add(ans,fi[kk][i][0][0]); add(ans,fi[kk][i][1][0]); }return ans; } int main(){ int m,i,u,v;scanf("%d%d%d",&n,&m,&kk); scanf("%s",ss+1); for (i=1;i<=n;++i){ if (ss[i]>='0'&&ss[i]<='9') fu[i]=0; else fu[i]=1; if ((ss[i]>='0'&&ss[i]<='9')||ss[i]=='('||ss[i]==')') ff[i]=0; else ff[i]=1; }for (i=1;i<=m;++i){ scanf("%d%d",&u,&v); adde(u,v); }printf("%d\n",dp()); }
bzoj4383 Pustynia
题目大意:一个数列ai(1<=ai<=1000000000),已知一些位置的数和一些信息,信息是l~r中k个数比其他位置数大。求一个可行解或者判断无解。
思路:暴力建图是n^2,用线段树优化建图,每条信息建新点x,k个数向x连边,x再向不选的最多k+1个区间用线段树优化建边,统计答案的时候,用拓扑扫一遍,注意判断无解的情况。
注意:线段树的节点不是连续的从1开始的一段。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 2000005 #define M 4000005 #define inf 1000000000 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int ai[N]={0},n,fi[N],gi[N]={0},point[N]={0},next[M],en[M],tot=0,tt=0,xi[N],du[N]={0},que[N]; bool f=false,vi[N]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++du[v];} void build(int i,int l,int r){ tt=max(tt,i);vi[i]=true; if (l==r){gi[i]=l;fi[l]=i;return;} int mid=(l+r)>>1; add(i,i<<1);add(i,i<<1|1); build(i<<1,l,mid);build(i<<1|1,mid+1,r); } void tch(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr){add(tt,i);return;} int mid=(l+r)>>1; if (ll<=mid) tch(i<<1,l,mid,ll,rr); if (rr>mid) tch(i<<1|1,mid+1,r,ll,rr); } void topu(){ int head=0,tail=0,i,u,v; for (i=1;i<=tt;++i){ if (!du[i]&&vi[i]) que[++tail]=i; xi[i]=inf; }while(head<tail){ u=que[++head]; if (xi[u]<ai[u]&&ai[u]){f=true;return;} else{ if (!ai[u]) ai[u]=xi[u]; }for (i=point[u];i;i=next[i]){ v=en[i];--du[v]; xi[v]=min(xi[v],ai[u]-(gi[u]>0)); if (!du[v]) que[++tail]=v; } }for (i=1;i<=tt;++i) if (vi[i]&&ai[i]<=0){f=true;return;} } int main(){ int s,m,i,j,k,l,r; n=in();s=in();m=in(); build(1,1,n); for (i=1;i<=s;++i){l=in();r=in();ai[fi[l]]=r;} for (i=1;i<=m;++i){ l=in();r=in();k=in(); vi[++tt]=true; xi[0]=l-1;xi[k+1]=r+1; for (j=1;j<=k;++j){ xi[j]=in();add(fi[xi[j]],tt); }for (j=0;j<=k;++j) if (xi[j+1]-xi[j]>1) tch(1,1,n,xi[j]+1,xi[j+1]-1); }topu(); if (f) printf("NIE\n"); else{ printf("TAK\n"); for (i=1;i<=n;++i) printf("%d ",ai[fi[i]]); printf("\n"); } }
bzoj2109&&2535 航空管制
题目大意:给出n个飞机最晚序号和m条相对关系(a在b前)。1)求一个合法的序列;2)每个飞机可能的最小序号。
思路:第一问倒着建边,考虑时间和入度,能选就选,n^2。第一问可以把一些最晚时间限制提前。第二问枚举每个点,这个点尽量不选,看最晚什么时候能选,因为是倒着建边,所以是最小序号,这个过程可以用优先队列做到n^2logn;也可以贪心nm(!!!),按最晚时间排升序,枚举每个点x,先dfs出x之后的点,个数为cnt,一开始ans=cnt,一定序号小于x,还有那些最晚时间小于cnt的点,也是序号小的,++ans;还有一种情况也必须先出现,就是那些最晚时间比当前判断过和最开始访问的点少的点,x必须在i之后,ans=ti+1。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 2005 #define M 10005 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int n,point[N]={0},next[M],en[M],tot=0,ti[N],aq[N],du[N]={0},ai[N],cnt,ans; bool vi[N]={false}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++du[v];} void work(){ int i,j,k; for (i=n;i;--i) for (j=1;j<=n;++j) if (!du[j]&&!vi[j]&&i<=ti[j]){ vi[j]=true;aq[i]=j; for (k=point[j];k;k=next[k]){ ti[en[k]]=min(ti[en[k]],ti[j]-1); --du[en[k]]; }break; } for (i=1;i<=n;++i) printf("%d ",aq[i]); printf("\n"); } int cmp(int x,int y){return ti[x]<ti[y];} void dfs(int u){ int i,v;vi[u]=true;++cnt; for (i=point[u];i;i=next[i]) if (!vi[v=en[i]]) dfs(v); } void work2(){ int i,j; sort(ai+1,ai+n+1,cmp); for (i=1;i<=n;++i){ memset(vi,false,sizeof(vi)); cnt=0;dfs(i);ans=cnt; for (j=1;j<=n;++j){ if (vi[ai[j]]) continue; ++cnt; if (ti[ai[j]]<=ans) ++ans; else if (ti[ai[j]]<cnt) ans=ti[ai[j]]+1; }printf("%d ",ans); }printf("\n"); } int main(){ int m,u,v,i;n=in();m=in(); for (i=1;i<=n;++i){ti[i]=in();ai[i]=i;} for (i=1;i<=m;++i){u=in();v=in();add(v,u);} work();work2(); }