您的位置:首页 > 其它

传教士和野人问题

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: