(一)弦圖:當且僅當存在完美消除序列。node
bzoj1006 神奇的國度ios
題目大意:給定一張弦圖,求它的最小染色數。算法
思路:之因此看出是弦圖,由於題目中說只有三元環,而沒有更多的。求最小染色數就是求出完美消除序列後,從後往前儘可能染能染的最小的顏色。其餘分析的複雜度是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 netide
題目大意:給定一張圖,判斷是不是弦圖。優化
思路:從新對圖編號(至關於求一遍完美消除序列),而後判斷這是不是個完美消除序列(對於序列中的第i個點,判斷它後面全部與他相鄰的點中的第一個是否與其餘全部相連)。ui
#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"); } }
求最大獨立集:求出完美消除序列後,從前日後能選就選。spa
求最小團覆蓋:最小團覆蓋數=最大獨立集數3d
(二)區間圖:當它是若干區間的相交圖。(必定是弦圖)code
(三)最短路
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(); }