1 条题解
-
0
自动搬运
来自洛谷,原作者为

yzy1
Меня мое сердце, в тревожную даль зовёт.搬运于
2025-08-24 22:36:12,当前版本为作者最后更新于2022-02-09 18:25:59,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
这里是出题人官方题解。
Task 1
只有一颗子弹,所以向右或者向上移动一步就可以,但是由于题目限制 ,指令会重复 次,机器人会移动到画面之外而爆炸,所以我们需要在躲避子弹后回到原来的位置。
if(n==1)cout<<"401"; else cout<<"302";Task 2
我们可以写一个子弹可视化的程序来辅助分析,下面是一个示例:
const int N = 2e6 + 9; int n, m, b, d, k, maxc, p[5], X, Y, tp, cost; struct B { int l, r, x, y, p, q; } a[N]; list<B> now; signed main() { ifstream fin("0.in"); fin.tie(0); fin >> n >> m >> b >> d >> k >> maxc; char mp[n + 3][m + 3]; rep (i, 0, 4) fin >> p[i]; re (i, b) fin >> a[i].l >> a[i].r >> a[i].x >> a[i].y >> a[i].p >> a[i].q; cerr<<"end\n"; char c; do c = getchar(); while (c != '\n'); sort(a + 1, a + b + 1, [](const B &a, const B &b) { return a.l < b.l; }); re (t, d) { each (x, now) x.x += x.p, x.y += x.q; while (a[tp + 1].l == t) now.push_back(a[++tp]); memset(mp, '.', sizeof mp); each (x, now) if (0 <= x.x && x.x <= n && 0 <= x.y && x.y <= m) mp[x.x][x.y] = 'x'; cerr << "time: " << t << '\n'; per (i, min(m,100), 0) { rep (j, 0, min(n,100)) putchar(mp[j][i]); putchar('\n'); } for (auto it = now.begin(); it != now.end();) { if (it->r <= t || it->x < 0 || it->x > n || it->y < 0 || it->y > m) it = now.erase(it); else ++it; } char c; do c = getchar(); while (c != '\n'); } return 0; }通过可视化程序,我们发现一些子弹墙组成了一个空心矩形,而有一颗子弹一直在赶着机器人跑。且转动为顺时针或者逆时针。分别判断这两种情况,写一个沿着边缘转的程序即可。
注意数据中的 我们需要转的圈数。也就是我们需要手动循环多次,注意不要让代价超过 。
Task 3
发现这个点 很小,且让我们输出的是最优方案,直接考虑 dp,设 为在 时刻走到 的最少代价,转移即可。复杂度看你的实现,可以是 ,或者 。
re(t,d){ memset(vis,0,sizeof vis); each (x, now){ rep(i,0,n)rep(j,0,m)if(Ck(x.x,x.y,x.x+x.p,x.y+x.q,i,j))vis[i][j]=1; x.x += x.p, x.y += x.q; } while (a[tp + 1].l == t) now.push_back(a[++tp]),vis[now.back().x][now.back().y]=1; rep(i,0,n)rep(j,0,m)if(!vis[i][j]) rep(di,0,4)if(Ck(i-DX[di],j-DY[di])){ int nw=dp[t-1][i-DX[di]][j-DY[di]]+p[di]; if(nw<dp[t][i][j])dp[t][i][j]=nw,lst[t][i][j]=di; } for (auto it = now.begin(); it != now.end();) { if (it->r <= t || it->x < 0 || it->x > n || it->y < 0 || it->y > m) it = now.erase(it); else ++it; } }Task 4
这些子弹数据全部都是随机的,且子弹比较稀疏。写个暴搜即可,一旦找到一种小于等于 的方案就输出。
我们发现静止的代价远低于移动的代价,所以暴搜的逻辑里要优先静止。
void Dfs(int t,int c,int X,int Y,vector<int>res){ if(c>maxc)return; if(X<0||Y<0||X>n||Y>m)return; if(t==d){ if(c<=maxc){ ans=res,maxc=c; each(x,ans)cout<<x; cout<<'\n'; } exit(0); } re(i,b) if(a[i].l==t&&a[i].x==X&&a[i].y==Y)return; else if(a[i].l<t&&t<=a[i].r&& Ck(a[i].x+(t-a[i].l-1)*a[i].p, a[i].y+(t-a[i].l-1)*a[i].q, a[i].x+(t-a[i].l)*a[i].p, a[i].y+(t-a[i].l)*a[i].q,X,Y))return; int rnk[]={0,1,2,3,4}; sort(rnk,rnk+5,[](int a,int b){return p[a]<p[b];}); shuffle(rnk+1,rnk+5,rnd); rep(ii,0,4){ int i=rnk[ii]; auto ress=res;ress.push_back(i); Dfs(t+1,c+p[i],X+dx[i],Y+dy[i],ress); } }Task 5
一些静止的子弹形成了一些迷宫,而且在最后一秒会给除了一个点外其他所有点来一个「全屏秒杀」。所以这几个 task 的目标就变成了「最少的移动步数移动到指定的目标点」。跑一个最短路就可以。注意开
long long。bool vis[N]; bool ban[3333][3333]; int dis[N]; vector<pair<int,int>>e[N]; inline bool Ck(int x,int y){return 0 <= x && x <= n && 0 <= y && y <= m && !ban[x][y];} inline int Id(int x,int y){return x*(m+1)+y;} void Spfa(int s) { memset(dis,0x3f,sizeof dis); dis[s] = 0; queue<int> q; q.push(s); vis[s] = false; while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; each(i,e[u]){ int v=i.first; if (dis[u] + i.second < dis[v]) { dis[v] = dis[u] + i.second; if (!vis[v]) q.push(v),vis[v] = true; } } } } signed main(){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n>>m>>b>>d>>k>>maxc; rep(i,0,4)cin>>p[i]; re(i,b)cin>>a[i].l>>a[i].r>>a[i].x>>a[i].y>>a[i].p>>a[i].q; re(i,b)if(a[i].l==1)ban[a[i].x][a[i].y]=1; rep(i,0,n)rep(j,0,m)re(dir,4)if(Ck(i+DX[dir],j+DY[dir]))e[Id(i,j)].push_back({Id(i+DX[dir],j+DY[dir]),p[dir]}); Spfa(0); memset(ban,0,sizeof ban); re(i,b)if(a[i].l==inf)ban[a[i].x][a[i].y]=1; int X=0,Y=0; rep(i,0,n)rep(j,0,m)if(!ban[i][j]){X=i,Y=j;break;} cout<<dis[Id(X,Y)]<<'\n'; return 0; }Task 6
容易发现对于 ,第 个子弹一直会定格在 位置,在 时刻消失。又因为 很大,最优解显然是先定格一段时间,再向右走一格。经过缜密的计算,可以发现答案就是:。
注意要开高精度。
Task 7
还是一堆静止的子弹,但是你发现这个 都特别大,而且这次的 不是 了,这东西好像没有什么好方法能做。实际上出题人也没有根据数据直接做的解法。解决这个 Task 需要从 gen 本身入手。
使用反编译软件进行破解,用 位以 PD 格式打开可执行文件,找到
main函数进入,按下F5生成伪代码:int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax __main(); freopen("0.in", "w", &__iob[1]); scanf("%d", &seed); if ( (unsigned int)(seed - 1) > 0x3FFFFFFE ) { fprintf(&__iob[2], "Invalid seed\n"); result = -1; } else { puts("100000 100000 300000 1000000000 100 1000"); puts("999999999 1 1 1 1"); GenAns(); GenHintData(); GenData(); result = 0; } return result; }main中调用了GenAns(生成答案)、GenHintData(生成提示数据)、GenData(生成其它数据) 三个函数。你会发现这个程序是先生成答案,再根据答案生成数据的。再来看看
GenHintData:int GenHintData(void) { unsigned int v0; // ebx v0 = rnd(); printf("1 1 "); printf("%d ", v0 >> 24); printf("%d ", BYTE2(v0)); printf("%d ", BYTE1(v0)); return printf("%d\n", (unsigned __int8)v0); }可以发现,该函数所做的就是生成一个随机数,然后分四段打印出来。
接着来看
GenAns:int GenAns(void) { signed int v0; // ebx int result; // eax int v2; // edx int v3; // edi v0 = 1; do { result = (rnd() & 3) + 1; // 生成 [1,4] 随机数 ans[v0] = result; v2 = dword_84397C[v0] + dx[result]; // 移动后的 x 坐标 X[v0] = v2; v3 = dword_47303C[v0] + dy[result]; // 移动后的 y 坐标 Y[v0] = v3; if ( v2 < 0 || v3 < 0 ) // 检查是否超出画面 { ans[v0] = 5 - result; // 取反方向(左下上右)->(右上下左) X[v0] = dword_84397C[v0] + dx[5 - result]; result = dword_47303C[v0] + dy[5 - result]; Y[v0] = result; } ++v0; } while ( v0 != 1001 ); // 循环 1000 次 return result; }可以看到,反编译后的函数还是比较易读的,它用
rnd函数生成了一个长度为 的答案序列。再来看看
rnd:unsigned int rnd(void) { unsigned int v0; // edx unsigned int result; // eax v0 = 32 * ((((seed << 13) ^ (unsigned int)seed) >> 17) ^ (seed << 13) ^ seed); result = v0 ^ (((seed << 13) ^ (unsigned int)seed) >> 17) ^ (seed << 13) ^ seed; seed ^= v0 ^ (((seed << 13) ^ (unsigned int)seed) >> 17) ^ (seed << 13); return result; }发现这就是个 Xorshift,其等同于
x^=x<<13,x^=x>>17,x^=x<<5。现在问题变成了,根据一个生成的随机数,推算出一开始的
seed,进而推算出最开始生成的 个随机数是什么。我们可以把每一个二进制位当成一个未知数,用高斯消元解异或方程组来根据下一个随机数解出上一个随机数。重复跑 次即可得到最开始的seed,然后模拟数据生成即可。signed main(){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n>>m>>b>>d>>k>>maxc; rep(i,0,4)cin>>p[i]; re(i,b)cin>>a[i].l>>a[i].r>>a[i].x>>a[i].y>>a[i].p>>a[i].q; seed=(a[1].x<<24)|(a[1].y<<16)|(a[1].p<<8)|a[1].q; per(i,1000,1)ans[i]=Rev()%4+1; re(i,1000){ X[i]=X[i-1]+dx[ans[i]],Y[i]=Y[i-1]+dy[ans[i]]; if(X[i]<0||Y[i]<0)ans[i]=5-ans[i],X[i]=X[i-1]+dx[ans[i]],Y[i]=Y[i-1]+dy[ans[i]]; cout<<ans[i]; } cout<<'\n'; return 0; }Task 8
我们发现所有的子弹全都是从最右面往正左方发射且速度相同。而且我们只能向上走、向下走和静止不动。
我们可以认为子弹不动,而是相对的机器人在动。
那么问题就转化为了机器人每次可以从 开始移动,有 种移动方案:
- 移动到 ,前提是 之间没有子弹。
- 移动到 ,前提是 之间没有子弹。
- 移动到 ,前提是 之间没有子弹。
设 为到达 的最小代价,那么转移就很显然了。
一共 个状态,每次转移前缀和判断合法,总时间复杂度为
#include <bits/stdc++.h> #define fi first #define sc second using namespace std; typedef pair<int,int> pi; const int maxn=1205,inf=0x3f3f3f3f; int f[2][maxn],n,m,b,d,p0,p1,p4,cnt,ans=inf,ls[maxn]; pi p[maxn*20]; bool book[maxn]; int main() { int x,y,z; scanf("%d%d%d%d%*d%*d",&n,&m,&b,&d); scanf("%d%d%*d%*d%d",&p0,&p1,&p4); for(int i=1; i<=b; i++) { scanf("%*d%*d%d%d%*d%d",&x,&y,&z),z=-z; if((y+z-1)/z<=d)p[++cnt]=pi((y+z-1)/z,x),ls[x]=max(ls[x],(y+z-1)/z); if(y%z==0&&y/z+1<=d)p[++cnt]=pi(y/z+1,x),ls[x]=max(ls[x],y/z+1); } sort(p+1,p+cnt+1),memset(f,0x3f,sizeof(f)),f[0][0]=0; int lst=1; for(int i=0; i<=n; i++)cout<<ls[i]<<" \n"[i==n]; for(int i=1; i<=d; i++) { memset(book,0,sizeof(book)); while(lst<=cnt&&p[lst].fi<=i)book[p[lst].sc]=1,lst++; int now=i&1,lst=now^1; memset(f[now],0x3f,sizeof(f[now])); for(int j=0; j<=n; j++) { if(book[j])continue; f[now][j]=min(inf,f[lst][j]+p0); if(j)f[now][j]=min(f[now][j],f[lst][j-1]+p4); if(j<n)f[now][j]=min(f[now][j],f[lst][j+1]+p1); } for(int j=0; j<=n; j++)if(ls[j]<i)ans=min(ans,f[now][j]); if(lst>cnt)break; } printf("%d",ans); return 0; }
- 1
信息
- ID
- 7430
- 时间
- 2500ms
- 内存
- 512MiB
- 难度
- (无)
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者