首页 > 代码库 > APIO 2013
APIO 2013
这套好丧……跟别的画风好不一样(浓厚的中国风?)。提答没做也没测,假装只有两题吧。140/200
T1.ROBOTS
题目大意:h*w的网格上有n个机器人编号1~n,网格上有空地、墙、顺/逆时针转向器,每次可以把一个机器人朝一个方向推,机器人碰到空地会继续前进,碰到转向器会转向,碰到墙会在前一格停止,同一时间只能有一个机器人在动,编号连续的机器人可以合体,例如2号和3号合成[2,3],[2,3]和[4,6]合成[2,6],问把所有机器人合成一个最少要推几下,无解输出-1。(n<=9,h,w<=500)
思路:先记忆化搜索预处理出每个格子朝四个方向推各会推到哪里(注意可能会无限循环),用f[i][j][k][l]表示[i,j]并成一个机器人位于(k,l)格至少要推几下(内存较紧,需要hash),考虑dijkstra,堆的log难以接受,我想了个方法:答案肯定不会太大,于是每种权值开一个队列,这样复杂度是每次转移O(1),由于还要区间合并,最后时间复杂度大概是O(n^3hw),空间同样。不过实际上这个n^3不是满的,所以理论上除了爆内存应该是很科学的,不过我写了vector,常数好像有点大,本来想手写个链表加上内存回收搞不好就过了,因为懒而且还要打T2暴力就放弃了,最后得分85/100(都是T)。
#include<cstdio> #include<cstdlib> #include<vector> using namespace std; #define MN 500 #define MQ 11250000 struct pos{int x,y;}t[MN+5][MN+5][4]; const int o[4][2]={{0,-1},{-1,0},{0,1},{1,0}}; char g[MN+5][MN+5]; int d[MQ+5],p[9][9],pl[45],pr[45],pn,x,y,l,r; pos work(int x,int y,int f) { if(t[x][y][f].x)return t[x][y][f]; t[x][y][f]=(pos){-1,-1}; int r; if(g[x][y]==‘.‘||(g[x][y]>=‘1‘&&g[x][y]<=‘9‘))r=f; if(g[x][y]==‘A‘)r=f?f-1:3; if(g[x][y]==‘C‘)r=f<3?f+1:0; return t[x][y][f]=(g[x][y]&&g[x][y]!=‘x‘)? work(x+o[r][0],y+o[r][1],r):(pos){x-o[f][0],y-o[f][1]}; } inline int hash(int x,int y,int l,int r){return x<0?-1:((x-1)*500+y-1)*pn+p[l][r];} inline void dehash(int z){l=pl[z%pn];r=pr[z%pn];y=(z/=pn)%500+1;x=z/500+1;} struct MyPQ { vector< vector<int> > v;int x,y; void push(int x,int z) { if(x<0||(d[x]&&d[x]<=z))return;d[x]=z; while(v.size()<z)v.push_back(vector<int>()); v[z-1].push_back(x); } inline int top(){return v[x][y];} inline void next(){for(++y;x<v.size()&&y==v[x].size();++x)v[x].clear(),y=0;if(x==v.size())puts("-1"),exit(0);} inline void pop(){for(next();x>=d[v[x][y]];next());} }q; int main() { freopen("robots.in","r",stdin); freopen("robots.out","w",stdout); int n,w,h,i,j,k,z,zz; scanf("%d%d%d",&n,&w,&h);--n; for(i=1;i<=h;++i)scanf("%s",g[i]+1); for(i=0;i<9;++i)for(j=i;j<9;++j)pl[pn]=i,pr[pn]=j,p[i][j]=pn++; for(i=1;i<=h;++i)for(j=1;j<=w;++j) { for(k=0;k<4;++k)work(i,j,k); if(g[i][j]>=‘1‘&&g[i][j]<=‘9‘)q.push(hash(i,j,g[i][j]-‘1‘,g[i][j]-‘1‘),1); } while(true) { dehash(z=q.top());q.pop(); if(l==0&&r==n)return printf("%d",d[z]-1),0; for(i=0;i<l;++i)if(d[zz=hash(x,y,i,l-1)])q.push(hash(x,y,i,r),d[z]+d[zz]-1); for(i=8;i>r;--i)if(d[zz=hash(x,y,r+1,i)])q.push(hash(x,y,l,i),d[z]+d[zz]-1); for(j=0;j<4;++j)q.push(hash(t[x][y][j].x,t[x][y][j].y,l,r),d[z]+1); } fclose(stdin);fclose(stdout);return 0; }
正解:双队列广搜,每种机器人先出算出可以合成它的所有小机器人在各个位置的情况,然后开两个队列,一个是按从小机器人合成的按权值排序的队列,一个是被推着走拓展出的队列,每次取最小的,这种做法理论上和我的是同阶的,不过常数上还是内存上都比我优到不知道哪里去了。
T2.TOLL
题目大意:给出一张N点M边的带权联通无向图,每个点上有一些人,给定K条边,要求你定边权后加入原图,使得最后最小生成树上,每个人走到1号点,路径上经过的新加的边的权值和最大。(N<=100,000,M<=300,000,K<=20)
思路:暴力枚举最后保留哪些新加的边,每次O(n)算出每条边被保留下最大能取的权值,并算出答案,复杂度O(N*2^K),得分55/100。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; inline int read() { int x=0;char c; while((c=getchar())<‘0‘||c>‘9‘); for(;c>=‘0‘&&c<=‘9‘;c=getchar())x=(x<<3)+(x<<1)+c-‘0‘; return x; } #define MN 100000 #define MM 300000 #define MK 20 struct Edge{int x,y,w;}E[MM+5],S[MN+5],K[MK+5],T[MN+5]; bool cmp(Edge a,Edge b){return a.w<b.w;} struct edge{int nx,t,w,u,f;}e[MN*2+50]; int n,k,f[MN+5],h[MN+5],en,u[MN+5],p[MN+5],mx[MK+5]; int fa[MN+5],tf[MN+5],d[MN+5],ps[MN+5]; ll ans,fs[MN+5]; int gf(int k){return f[k]?f[k]=gf(f[k]):k;} inline void ins(int x,int y,int w,int f=0) { e[++en]=(edge){h[x],y,w,0,f};h[x]=en; e[++en]=(edge){h[y],x,w,0,f};h[y]=en; } void pre(int x) { for(int i=h[x];i;i=e[i].nx)if(!e[i].u&&e[i].t!=fa[x]) d[e[i].t]=d[x]+1,fa[e[i].t]=x,tf[e[i].t]=i,pre(e[i].t); } void dp(int x,int fa) { ps[x]=p[x];fs[x]=0; for(int i=h[x];i;i=e[i].nx)if(!e[i].u&&e[i].t!=fa) { dp(e[i].t,x); ps[x]+=ps[e[i].t]; fs[x]+=fs[e[i].t]; if(e[i].f)fs[x]+=(ll)mx[e[i].f]*ps[e[i].t]; } } void work() { int i,j=0,x,y,z,c; memset(f,0,sizeof(int)*(n+1)); for(i=1;i<=k;++i)if(u[i]&&gf(K[i].x)!=gf(K[i].y)) f[gf(K[i].x)]=gf(K[i].y),T[++j]=K[i]; memset(h,0,sizeof(int)*(n+1));en=1; for(i=1;i<n;++i)ins(S[i].x,S[i].y,S[i].w); memset(mx,127,sizeof(mx)); for(i=1;i<=j;++i) { pre(1); for(x=T[i].x,y=T[i].y,z=-1;x!=y;) if(d[x]>d[y]){if(e[tf[x]].w&&e[tf[x]].w>z)z=e[tf[x]].w,c=tf[x];x=fa[x];} else{if(e[tf[y]].w&&e[tf[y]].w>z)z=e[tf[y]].w,c=tf[y];y=fa[y];} e[c].u=e[c^1].u=1;mx[i]=z; for(x=T[i].x,y=T[i].y;x!=y;) if(d[x]>d[y]){if(!e[tf[x]].w)mx[e[tf[x]].f]=min(mx[e[tf[x]].f],z);x=fa[x];} else{if(!e[tf[y]].w)mx[e[tf[y]].f]=min(mx[e[tf[y]].f],z);y=fa[y];} ins(T[i].x,T[i].y,0,i); } dp(1,0); if(fs[1]>ans)ans=fs[1]; } void dfs(int x) { if(x>k){work();return;} u[x]=0;dfs(x+1); u[x]=1;dfs(x+1); } int main() { freopen("toll.in","r",stdin); freopen("toll.out","w",stdout); int m,i,j; n=read();m=read();k=read(); for(i=0;i<m;++i)E[i].x=read(),E[i].y=read(),E[i].w=read(); sort(E,E+m,cmp); for(i=j=0;i<m;++i)if(gf(E[i].x)!=gf(E[i].y)) f[gf(E[i].x)]=gf(E[i].y),S[++j]=E[i]; for(i=1;i<=k;++i)K[i].x=read(),K[i].y=read(); for(i=1;i<=n;++i)p[i]=read(); dfs(1); cout<<ans; fclose(stdin);fclose(stdout);return 0; }
正解:一开始先把K条边边权设为0和M条边放一起跑最小生成树,K条边把最小生成树分成K+1块,每一块最后无论如何一定是连在一起的,可以缩成一个点,然后再按上面的暴力做,复杂度O(MlogM+K*2^K)。
APIO 2013