传教士和野人问题
2015-10-18 19:26
357 查看
题目链接:codeforces上的一次比赛,tourist参加了
题目描述:
[b]m个传教士和m个野人撑船过河,船上最多有n个人,最少有一个人(否则没人撑船了).[/b][b]野人爱吃人,当他们人多势众时,就会把传教士给吃掉.也就是说,当野人的人数大于传教士的人数时,野人就把传教士全吃掉.[/b]
[b]有三个吃人的地点:船上,左岸,右岸.[/b]
[b]面对这滔滔河水,他们想尽快过河.给定m和n两个正整数,问能不能成功过河?如果能过河,最少需要撑几次船才能全部过去?[/b]
分析
这是一个经典的益智游戏.关键在于状态描述,用三元组来描述(左岸羊的个数,左岸狼的个数,船在左岸还是右岸),符号表示(goat,wolf,boat).
那么右岸的状态就知道了(m-goat,m-wolf,!boat).
如何判断一个状态是否合法?
对于状态(goat,wolf,boat),满足左岸上goat>=wolf,右岸上m-goat>=m-wolf.
若要这两个不等式同时成立,必有goat=wolf.
或者是goat=0,羊的个数确实比狼少,狼想吃但没有.
或者是goat=m,这样跟上面那种情况对称,也是一种安全状态.
有三种合法状态:
(0)goat=0
(1)goat=wolf
(2)goat=m
于是,就有了改良了的状态描述方案:用三元组来表示(状态的类别编号,狼的个数,船的位置).空间复杂度骤减为O(m*3*2).
状态虽然有m*m*2个,但是合法的却只有6*m个.
状态转移:(g,w,b)的dg+dw<=n.于是有2种运输方案:
(0)把船所在的那个岸的羊全部运到对岸去,狼随便运,只要不超重.这样产生0,2两类状态.
(1)把dg个羊和dw个狼运到对岸去,还是保持平衡.这样保持1状态.
我的方法有点暴力,所以超时了.
#include<iostream> #include<stdio.h> #include<algorithm> #include<queue> #include<math.h> #include<string.h> #include<stdlib.h> using namespace std; typedef long long ll; typedef unsigned long long ull; #define re(i,n) for(int i=0;i<n;i++) const int maxn = 1e5 + 7; int n, m; struct Node{ int wolf, kind, boat; int fa;//记录是从那个节点到来的,用于回溯怎么到达这个节点 }; bool vis[maxn][3][2];//有没有访问过这个状态 int f[maxn][3][2];//到达此状态所需的步数 struct Q{ Node a[maxn * 6]; int h, r; void init(){ memset(vis, 0, sizeof(vis)); h = r = 0; } void enq(int wolf, int kind, int boat, int step){ if (vis[wolf][kind][boat])return; vis[wolf][kind][boat] = true; f[wolf][kind][boat] = step; a[r].wolf = wolf, a[r].boat = boat, a[r].kind = kind; a[r].fa = h - 1; r++; } Node deq(){ return a[h++]; } }q; int go(){ q.init(); q.enq(m, 2, 0, 0); while (q.h != q.r){ Node now = q.deq(); if (now.kind == 2 && now.boat == 1 && now.wolf == m) return f[now.wolf][now.kind][now.boat]; int w = now.wolf, g; int step = f[now.wolf][now.kind][now.boat] + 1; if (now.kind == 0)g = 0; else if (now.kind == 1)g = w; else if (now.kind == 2)g = m; /*只有两种决策方案:把羊全部运过去 运相同数量的狼和羊 */ //羊的个数不为0且小于n,把羊全部运过去.生成2状态. if (g <= n){ int sp = n - g; //把狼运过去,至少运一只 for (int i = 1; i <= w&&i <= sp; i++){ q.enq(m - w + i, 2, 1 - now.boat, step); } //狼1只也不运,只运羊就行了 if (g)q.enq(m - w, 2, 1 - now.boat, step); } //如果全是羊,但一只羊也不运过去,那就至少运去一只狼.生成0状态 if (g == m){ for (int i = 1; i <= w&&i <= n; i++){ q.enq(m - w + i, 0, 1 - now.boat, step); } } //运相等数量的狼羊,并且羊不能全部运过去,且至少运过去一只,只有1状态可以生成1状态 if (now.kind == 1){ int by = min(g - 1, w); by = min(by, n >> 1); for (int i = 1; i <= by; i++){ q.enq(m - w + i, 1, 1 - now.boat, step); } } //我这边羊满着,运一部分过去,使得两岸相等.由2状态可以到1状态, if (g == m&&w < m&&n >= m - w){//w<g必为2状态 //需要运过去m-w只羊,因为河对岸有m-w只狼在等着 q.enq(m - w, 1, 1 - now.boat, step); } } return -1; } int main(){ freopen("in.txt", "r", stdin); for (m = 1; m < 100; m++){ for (n = 1; n <= m*2; n++){ int ans = go(); printf("%3d", ans); } puts(""); } return 0; }
我还不会,先贴上那次比赛中的几份神代码.第一份是正确的,后两份是错误的.
import java.io.PrintWriter; import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import java.util.Scanner; public class F5 { Scanner in; PrintWriter out; // String INPUT = "100000 10"; String INPUT = ""; int[] dec(int n, int m) { if(n <= m){ return new int[]{n, 0}; }else if(n <= 2 * m){ return new int[]{n - m, n - m}; }else{ return new int[]{n - 2 * m - 1, m}; } } int enc(int x, int y, int m) { if(y == 0){ return x; }else if(x == y){ return x + m; }else{ return x + 2 * m + 1; } } void solve() { int m = ni(); int n = ni(); if(n <= Math.sqrt(Math.sqrt(m))){ solveH(m, n); }else{ solveB(m, n); } } void solveB(int m, int n){ // (a, 0), (a, a), (a, m) BitSet visitedo = new BitSet(); BitSet visitedh = new BitSet(); BitSet q = new BitSet(); q.set(0); boolean iso = true; int step = 0; outer: while(!q.isEmpty()){ // tr(step, q); BitSet nq = new BitSet(); if(iso){ visitedh.or(q); }else{ visitedo.or(q); } for(int cur = q.nextSetBit(0);cur != -1;cur = q.nextSetBit(cur+1)){ if(!iso && cur == 2 * m)break outer; int[] co = dec(cur, m); if(iso){ { if(co[1] == 0){ int inf = co[0] + 1; int sup = Math.min(m, co[0] + n); if(inf <= sup){ nq.set(enc(inf, 0, m), enc(sup, 0, m) + 1); } } } { int inf = Math.max(co[0], co[1]); if(co[0] == co[1])inf++; int sup = Math.min(m, (n + co[0] + co[1]) / 2); if(inf <= sup){ nq.set(enc(inf, inf, m), enc(sup, sup, m) + 1); } } { int inf = co[0]; if(co[1] == m)inf++; int sup = Math.min(m - 1, co[0] + n - m + co[1]); if(inf <= sup){ nq.set(enc(inf, m, m), enc(sup, m, m) + 1); } } }else{ { if(co[1] == m){ int inf = co[0] - 1; int sup = Math.max(0, co[0] - n); if(sup <= inf){ nq.set(enc(sup, m, m), enc(inf, m, m) + 1); } } } { int inf = Math.min(co[0], co[1]); if(co[0] == co[1])inf--; int sup = Math.max(0, (-n + co[0] + co[1]) / 2); if(sup == 0)sup++; if(sup <= inf){ nq.set(enc(sup, sup, m), enc(inf, inf, m) + 1); } } { int inf = co[0]; if(co[1] == 0)inf--; int sup = Math.max(0, co[0] - n + co[1]); if(sup <= inf){ nq.set(enc(sup, 0, m), enc(inf, 0, m) + 1); } } } } if(iso){ nq.andNot(visitedo); }else{ nq.andNot(visitedh); } q = nq; step++; iso = !iso; } if(q.isEmpty()){ out.println(-1); }else{ out.println(step); } } void solveH(int m, int n){ // (a, 0), (a, a), (a, m) HashSet<Integer> visitedo = new HashSet<Integer>(); HashSet<Integer> visitedh = new HashSet<Integer>(); HashSet<Integer> q = new HashSet<Integer>(); q.add(0); boolean iso = true; int step = 0; outer: while(!q.isEmpty()){ // tr(step, q); HashSet<Integer> nq = new HashSet<Integer>(); if(iso){ visitedh.addAll(q); }else{ visitedo.addAll(q); } for(int cur : q){ if(!iso && cur == 2 * m)break outer; int[] co = dec(cur, m); if(iso){ { if(co[1] == 0){ int inf = co[0] + 1; int sup = Math.min(m, co[0] + n); if(inf <= sup){ for(int i = enc(inf, 0, m);i <= enc(sup, 0, m);i++){ nq.add(i); } } } } { int inf = Math.max(co[0], co[1]); if(co[0] == co[1])inf++; int sup = Math.min(m, (n + co[0] + co[1]) / 2); if(inf <= sup){ for(int i = enc(inf, inf, m);i <= enc(sup, sup, m);i++){ nq.add(i); } } } { int inf = co[0]; if(co[1] == m)inf++; int sup = Math.min(m - 1, co[0] + n - m + co[1]); if(inf <= sup){ for(int i = enc(inf, m, m);i <= enc(sup, m, m);i++){ nq.add(i); } } } }else{ { if(co[1] == m){ int inf = co[0] - 1; int sup = Math.max(0, co[0] - n); if(sup <= inf){ for(int i = enc(sup, m, m);i <= enc(inf, m, m);i++){ nq.add(i); } } } } { int inf = Math.min(co[0], co[1]); if(co[0] == co[1])inf--; int sup = Math.max(0, (-n + co[0] + co[1]) / 2); if(sup == 0)sup++; if(sup <= inf){ for(int i = enc(sup, sup, m);i <= enc(inf, inf, m);i++){ nq.add(i); } } } { int inf = co[0]; if(co[1] == 0)inf--; int sup = Math.max(0, co[0] - n + co[1]); if(sup <= inf){ for(int i = enc(sup, 0, m);i <= enc(inf, 0, m);i++){ nq.add(i); } } } } } if(iso){ nq.removeAll(visitedo); }else{ nq.removeAll(visitedh); } q = nq; step++; iso = !iso; } if(q.isEmpty()){ out.println(-1); }else{ out.println(step); } } void run() throws Exception { in = INPUT.isEmpty() ? new Scanner(System.in) : new Scanner(INPUT); out = new PrintWriter(System.out); // long s = System.currentTimeMillis(); solve(); out.flush(); // long g = System.currentTimeMillis(); // System.out.println(g - s + "ms"); } public static void main(String[] args) throws Exception { new F5().run(); } int ni() { return Integer.parseInt(in.next()); } void tr(Object... o) { if(INPUT.length() != 0)System.out.println(o.length > 1 || o[0].getClass().isArray() ? Arrays.deepToString(o) : o[0]); } }
tourist的代码第十个样例,第十四个样例......是错误的,在比赛开始3小时之前答案是错的,所以tourist过了这道题.之后改了答案,tourist的代码就错了,然而系统没有进行重判,导致tourist一发就过.所以下面的代码是错的.于是乎,全场只有一份代码过了这道题:上面的java代码.
var yy,zz,n,m,i,e: longint; lg,rg: array [0..2,0..1,0..110] of longint; kg: array [0..2,0..1] of longint; was: array [0..100010,0..2,0..1] of boolean; x,y,z,km: array [0..666666] of longint; procedure put(x1,x2,yy,zz:longint); var u1,u2,v,j,xx: longint; begin if x2 < 0 then exit; if x1 > n then exit; zz:=1-zz; yy:=2-yy; if x1 < 0 then x1:=0; if x2 > n then x2:=n; xx:=x1; x1:=n-x2; x2:=n-xx; u1:=x1; u2:=x2; for v:=1 to kg[yy,zz] do begin if x1 < lg[yy,zz,v] then x1:=lg[yy,zz,v]; if x2 > rg[yy,zz,v] then x2:=rg[yy,zz,v]; for j:=x1 to x2 do if not was[j,yy,zz] then begin inc(e); x[e]:=j; y[e]:=yy; z[e]:=zz; km[e]:=km[i]+1; was[j,yy,zz]:=True; end; if x1 <= x2 then if (x1 > lg[yy,zz,v]) and (x2 < rg[yy,zz,v]) then begin inc(kg[yy,zz]); lg[yy,zz,kg[yy,zz]]:=x2+1; rg[yy,zz,kg[yy,zz]]:=rg[yy,zz,v]; rg[yy,zz,v]:=x1-1; end else if x2 < rg[yy,zz,v] then lg[yy,zz,v]:=x2+1 else rg[yy,zz,v]:=x1-1; x1:=u1; x2:=u2; end; end; begin // assign(input,'in'); reset(input); // assign(output,'out'); rewrite(output); read(n,m); for yy:=0 to 2 do for zz:=0 to 1 do begin kg[yy,zz]:=1; lg[yy,zz,1]:=0; rg[yy,zz,1]:=n; end; fillchar(was,sizeof(was),False); i:=1; e:=1; x[1]:=n; y[1]:=2; z[1]:=0; km[1]:=0; was[n,2,0]:=True; while i <= e do begin if (x[i] = n) and (y[i] > 0) and (z[i] = 1) then begin writeln(km[i]); halt; end; if y[i] = 0 then begin put(x[i]-m,x[i]-1,0,z[i]); end else if y[i] = 1 then begin put(x[i]-(m shr 1),x[i]-1,1,z[i]); if (x[i] > 0) and (x[i] <= m) then put(x[i]-(m-x[i]),x[i],0,z[i]); end else begin put(x[i]-m,x[i]-1,2,z[i]); if (n-x[i]) <= m then if x[i] < n then put(x[i]-((m-(n-x[i])) shr 1),x[i],1,z[i]) else put(x[i]-(m shr 1),x[i]-1,1,z[i]); end; inc(i); end; writeln(-1); close(input); close(output); end.
第二名的代码跟tourist有点像,他的代码是错的.
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <sstream> #include <algorithm> #include <iostream> #include <iomanip> #include <map> #include <set> #include <list> #include <queue> #include <stack> #include <vector> #include <cassert> using namespace std; #define pb push_back #define mp make_pair #define REP(i, n) for (int i = 0; i < (int)(n); ++i) typedef long long LL; typedef pair<int, int> PII; struct S { int a, b, c; S(int a, int b, int c) : a(a), b(b), c(c) {} S() {} }; int m, n, cur; int d[2][3][35]; queue<S> q; bool aCunningPlan() { return m - cur + 1 <= n; } int updVal; void upd(int a, int b, int c) { if (d[a][b][c] == -1) { d[a][b][c] = updVal; if (a != 1 || b != 0 || c != m) { q.push(S(a, b, c)); } } } int main() { scanf("%d%d", &m, &n); if (n < 4 && m > 33) { printf("-1\n"); return 0; } if (m > 33) { if (n >= 2 * m) { printf("1\n"); return 0; } int ans = 0; cur = 0; while (true) { int ncur = min(m, cur + n / 2 - 1); if (ncur == m) { ++ans; break; } if (aCunningPlan()) { ans += 3; printf("%d\n", ans); return 0; } cur = ncur; ans += 2; } printf("%d\n", ans); return 0; } REP(i, 2) REP(j, 3) REP(k, m + 1) d[i][j][k] = -1; d[0][0][0] = 0; q.push(S(0, 0, 0)); while (!q.empty()) { S s = q.front(); q.pop(); updVal = d[s.a][s.b][s.c] + 1; if (s.a == 0) { if (s.b == 0) { for (int nc = s.c + 1; nc <= min(s.c + n / 2, m); ++nc) upd(1, 0, nc); if (m - s.c <= n) upd(1, 1, s.c); if (s.c == 0) for (int nc = s.c + 1; nc <= min(s.c + n, m); ++nc) upd(1, 2, nc); } else if (s.b == 1) { for (int nc = s.c + 1; nc <= min(s.c + n, m - 1); ++nc) upd(1, 1, nc); if (s.c + n >= m) upd(1, 0, m); } else { for (int nc = s.c + 1; nc <= min(s.c + n, m); ++nc) upd(1, 2, nc); if (m - s.c + m <= n) upd(1, 0, m); if (s.c <= n) upd(1, 0, s.c); } } else { if (s.b == 0) { for (int nc = s.c - 1; nc >= max(0, s.c - n / 2); --nc) upd(0, 0, nc); if (s.c <= n) upd(0, 2, s.c); } else if (s.b == 1) { for (int nc = s.c - 1; nc >= max(0, s.c - n); --nc) upd(0, 1, nc); if (m - s.c <= n) upd(0, 0, s.c); } else { for (int nc = s.c - 1; nc >= max(1, s.c - n); --nc) upd(0, 2, nc); } } } printf("%d\n", d[1][0][m]); return 0; }
相关文章推荐
- 传教士和野人问题
- Android开发之ImageView通过matrix实现两点缩放和图片拖动
- # Linux Whois3获取 运营商信息
- 两个线程交替打印数字-Condition唤醒与等待
- 算法导论13.4删除 练习总结
- The Swift Programming Language 中国版
- ViewPager相互嵌套,导致子ViewPager无法滑动,且子ViewPager中的view无法被点击
- Linux定时任务Crontab详解_定时备份
- 多线程
- 字符串逆序
- 【Linux】删除目录及其子文件夹
- 【SICP练习】150 练习4.6
- 欢迎使用CSDN-markdown编辑器
- Qt 4.8中使用ActiveQt
- 基于距离的计算方法
- Happy 2004(快速幂+乘法逆元)
- Codevs 传染病控制
- Excel工作簿的拆分
- USACO 3.1 Stamps
- vsftpd配置文件详解