A.机器人
题目大意:给定一个n*m的地图,有一些障碍物和k个机器人,你每次可以选择一个机器人往任意一个方向推,遇到转向器会转向,两个编号相邻的机器人可以合并,求最少推多少次可以全部合并。
$n,mleqslant 500 kleqslant 9$
题解:用f[i][j][k][l]表示i到j的机器人在(k,l)合并的最小次数,那么当(k',l')推一次可以到(k,l)的时候,它可以从f[i][j][k'][l']+1转移。当然,它还可以从f[i][p][k][l]+f[p+1][j][k][l]转移,所以我们先计算出
所有点推一次到哪里,每次做完一个f[i][j]之后,就做一次spfa。
但是spfa会T,并不能过。我们考虑开两个队列,在spfa之前把起始的位置拍好序扔到队列1,然后每次取出两个队列队头较小的松弛,松弛到的点加入队列2,这样可以保证队列1和队列2有序,spfa就进化为了bfs,排序最好用O(n)的计数排序,跑的飞快。
#include<iostream> #include<cstdio> #include<queue> #include<cstring> #include<algorithm> #define INF 32639 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } unsigned short d[11][11][501][501];int mark[501][501][4],top=0; int n,h,w,cnt=0,L,R; int v[180005],sa[180005]; char st[501][501]; struct P{short x,y; P(short x=0,short y=0):x(x),y(y){} bool operator !(){return x==0&&y==0;} }pos[11],to[505][505][4],q[180005]; bool b[501][501]; const int dis[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; queue<P> q1,q2; P dfs(int x,int y,int dir) { if(mark[x][y][dir]==cnt) return to[x][y][dir]=P(-1,-1);mark[x][y][dir]=cnt; if(!(!to[x][y][dir])) return to[x][y][dir]; int pre=dir; if(st[x][y]=='A') dir=(dir+3)%4; if(st[x][y]=='C') dir=(dir+1)%4; int xx=x+dis[dir][0],yy=y+dis[dir][1]; if(xx<1||yy<1||xx>h||yy>w||st[xx][yy]=='x') return to[x][y][pre]=P(x,y); return to[x][y][pre]=dfs(xx,yy,dir); } void spfa() { memset(v,0,sizeof(v)); for(int i=1;i<=top;i++)v[d[L][R][q[i].x][q[i].y]]++; for(int i=1;i<=INF;i++)v[i]+=v[i-1]; for(int i=1;i<=top;i++) sa[v[d[L][R][q[i].x][q[i].y]]--]=i; for(int i=1;i<=top;i++) q1.push(q[sa[i]]); top=0; while((!q1.empty())||(!q2.empty())) { P now; if(q1.empty()||((!q2.empty())&&d[L][R][q1.front().x][q1.front().y]>d[L][R][q2.front().x][q2.front().y])) now=q2.front(),q2.pop(); else now=q1.front(),q1.pop(); b[now.x][now.y]=0; for(int i=0;i<4;i++) { P t=to[now.x][now.y][i]; if(t.x==-1||t.y==-1) continue; if(d[L][R][now.x][now.y]+1<d[L][R][t.x][t.y]) { d[L][R][t.x][t.y]=d[L][R][now.x][now.y]+1; if(!b[t.x][t.y]) { b[t.x][t.y]=1; q2.push(t); } } } } } int main() { n=read();w=read();h=read(); memset(d,127,sizeof(d)); for(int i=1;i<=h;i++) { scanf("%s",st[i]+1); for(int j=1;j<=w;j++) if(st[i][j]>'0'&&st[i][j]<='9') pos[st[i][j]-'0']=P(i,j); } for(int i=1;i<=h;i++) for(int j=1;j<=w;j++) if(st[i][j]!='x') for(int k=0;k<4;k++) ++cnt,dfs(i,j,k); for(int i=1;i<=n;i++) { b[pos[i].x][pos[i].y]=1;q[++top]=pos[i]; L=R=i;d[i][i][pos[i].x][pos[i].y]=0;spfa(); } for(int l=2,j;l<=n;l++) for(int i=1;(j=i+l-1)<=n;i++) { for(int x=1;x<=h;x++) for(int y=1;y<=w;y++) { for(int k=i;k<j;k++) d[i][j][x][y]=min(d[i][j][x][y],(unsigned short)(d[i][k][x][y]+d[k+1][j][x][y])); if(d[i][j][x][y]<INF) q[++top]=P(x,y),b[x][y]=1; } L=i;R=j;spfa(); } unsigned short ans=30000; for(int i=1;i<=h;i++) for(int j=1;j<=w;j++) ans=min(ans,d[1][n][i][j]); if(ans<30000) printf("%u ",ans); else puts("-1"); return 0; }
B.[Apio2013]道路费用
$nleqslant 10^{5},mleqslant 3*10^{5},kleqslant 20$
题解:强制选这K条边,然后我们发现K条边把整个图割成了K+1块,每一块内选的边肯定是相同的。我们把这几块缩点,然后枚举每条边选不选,对每种情况,暴力算出每条边的最大长度,通过dp算出这种情况的答案就可以啦。
复杂度$mlogm+k^{2}*2^{k}$
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long #define INF 200000000000000000LL #define MN 100000 #define MM 300000 #define MK 20 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } struct edge{ int from,to;ll w;int kind; }e[MM+MK+5],ek[MK+5],e3[MK+5]; struct nedge{int to,next,kind;}e2[MN*2+5]; ll ans=0,sum[MK+5],p[MN+5],val[MK+5],mn[MK+5]; int n,m,K,cnt=0,s[MN+5],head[MN+5],cn=0,bel[MN+5],dep[MK+5],fa2[MK+5]; bool mark[MN+5],b[MK+5]; bool cmp(edge x,edge y){return x.w<y.w||(x.w==y.w&&x.kind>y.kind);} int find(int x) { return !s[x]?x:s[x]=find(s[x]); } void ins(int f,int t,int kind=0) { e2[++cnt]=(nedge){t,head[f],kind};head[f]=cnt; e2[++cnt]=(nedge){f,head[t],kind};head[t]=cnt; } void kruscal() { for(int i=1;i<=K;i++) { int x=find(ek[i].from),y=find(ek[i].to); s[x]=y,ins(ek[i].from,ek[i].to,1); } for(int i=1;i<=m;i++) { int x=find(e[i].from),y=find(e[i].to); if(x!=y)s[x]=y,ins(e[i].from,e[i].to,0); } } void getv(int x) { mark[x]=1;bel[x]=cn;val[cn]+=p[x]; for(int i=head[x];i;i=e2[i].next) if(!mark[e2[i].to]&&!e2[i].kind) getv(e2[i].to); } void dp(int x,int fa) { sum[x]=val[x];fa2[x]=fa; for(int i=head[x];i;i=e2[i].next) if(e2[i].to!=fa) { dep[e2[i].to]=dep[x]+1; dp(e2[i].to,x);sum[x]+=sum[e2[i].to]; } } int tms=0; void solve() { ll tot=0;cnt=0; for(int i=1;i<=cn;i++)s[i]=0,head[i]=0,mn[i]=INF; for(int i=1;i<=K;i++) if(b[i]) { int x=find(ek[i].from),y=find(ek[i].to); if(x==y)return;s[x]=y;ins(ek[i].from,ek[i].to); } tms++; for(int i=1;i<=K;i++) { int x=find(e3[i].from),y=find(e3[i].to); if(x!=y) s[x]=y,ins(e3[i].from,e3[i].to); } dp(1,0); for(int i=1;i<=K;i++) { int u=e3[i].from,v=e3[i].to; if(dep[u]<dep[v]) swap(u,v); while(dep[u]>dep[v]) mn[u]=min(mn[u],e3[i].w),u=fa2[u]; while(u!=v) { mn[u]=min(mn[u],e3[i].w); mn[v]=min(mn[v],e3[i].w); u=fa2[u],v=fa2[v]; } } for(int i=1;i<=K;i++) if(b[i]) { int u=ek[i].from,v=ek[i].to; if(dep[u]<dep[v]) swap(u,v); tot+=1LL*mn[u]*sum[u]; } ans=max(ans,tot); } void dfs(int x) { if(x>K) { solve(); return; } b[x]=1;dfs(x+1); b[x]=0;dfs(x+1); } int main() { n=read();m=read();K=read(); for(int i=1;i<=m;i++) e[i].from=read(),e[i].to=read(),e[i].w=read(); for(int i=1;i<=K;i++) ek[i].from=read(),ek[i].to=read(); for(int i=1;i<=n;i++)p[i]=read(); sort(e+1,e+m+1,cmp);kruscal(); for(int i=1;i<=n;i++)if(!mark[i]) ++cn,getv(i); cnt=0; for(int i=1;i<=cn;i++) s[i]=0; for(int i=1;i<=m+K;i++) if(!e[i].kind) { int x=bel[e[i].from],y=bel[e[i].to],f1=find(x),f2=find(y); if(f1!=f2) s[f1]=f2,e3[++cnt]=(edge){x,y,e[i].w,0}; } for(int i=1;i<=K;i++) ek[i].from=bel[ek[i].from],ek[i].to=bel[ek[i].to]; dfs(1); cout<<ans; return 0; }