【题目描述】

有三个没有刻度的水壶,容量分别为a,b和c(单位为升,都是<=200的正整数)。初始时前两个水壶是空的,而第三个装满了水。每次可以从一个水壶往一个水壶里倒水,直到一个水壶倒空或者另一个水壶倒满。为了让某个水壶恰好有d升水,至少要倒多少升的水?如果无解,找一个小于且最接近d的d’代替d。

【输入输出】

输入

第一行一个整数 t ,代表有 t 组测试数据,接下来 t 行每行包括 a, b, c, d 四个整数,分别代表三个水壶的容量 a, b, c 和目标水量 d 。

输出

输出共 t 行,每一行包括两个整数,分别是最少的倒水量和目标水量(d 或者 d′)。

【分析】

假设某一时刻,第一个杯子中有v₁升水,第二个杯子中有v₂升水,第三个杯子中有v₃升水,称此时的状态为(v₁, v₂, v₃)。我们可以把“状态”想成途中的一个结点,通过如何倒水就可以实现状态的转化,那么就可以构成一个状态图。不过提到了图,不代表一定就要建图,因为每一个状态的倒水方式都有最多6种(3² - 3,不能自己往自己倒),那么完全可以求最图上最短路时现算出每一个结点的出边,这种不建图却用到了图论的方法称为隐式图。然后跑最短路的话这里采用 spfa。

不过先来谈谈建图的方法。对于每一个结点,它的出边可以算出来,那么就可以用 bfs 来建图,然后存图的话就可以用 vector。值得注意的是,三个水壶中的水永远是不变的,恒等于第三个水壶的容量,所以可以用前两个水壶中的水表示第三个水壶中的水,建图就可以从三维降低到二维。

 #include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int INF = ;
const int maxn = ;
struct Cup //用结构体比较方便
{
int wa[];
};
int wmax[], d; //wmax三个杯子容量,d目标水量
bool exist[maxn][maxn]; //本来三维,可用二维表示,判断点是否存在
vector<Cup>v[maxn][maxn];//同理
vector<int>c[maxn][maxn];
int daoshui(int a, int b, int amax, int bmax, int& newa, int& newb) //a往b倒
{
if(a >= bmax - b) //b被灌满
{
newa = a - (bmax - b); newb = bmax; return bmax - b;
}
else //a被倒空
{
newa = ; newb = b + a; return a;
}
}
void init()
{
memset(exist, , sizeof(exist));
for (int i = ; i < maxn; i++)
for(int j = ; j < maxn; ++j)
{
v[i][j].clear(); c[i][j].clear();
}
return;
}
void build() //用bfs建图
{
init();
exist[][] = ;
queue<Cup>q; q.push((Cup){, , wmax[]});
while(!q.empty())
{
Cup now = q.front(); q.pop();
for(int i = ; i < ; ++i)
for(int j = ; j < ; ++j)
{
if(i == j) continue; //不能自己往自己倒
int t[]; Cup neww = now;
int cost = daoshui(now.wa[i], now.wa[j], wmax[i], wmax[j], t[i], t[j]);
neww.wa[i] = t[i]; neww.wa[j] = t[j];
if(!exist[neww.wa[]][neww.wa[]]) //要进入的这一个状态没被访问过
{
/*在强调一下,之所以开二维,是因为已知前两个杯子里的水,就可以
求第三个,所以像exist这样的数组,永远是exist[x.wa[0][x.wa[1]],
而不可能出现exist[x.wa[2]][x.wa[]]。我刚开始wa就是因为写成了
exist[neww.wa[i]][neww.wa[j]]*/
exist[neww.wa[]][neww.wa[]] = ;
v[now.wa[]][now.wa[]].push_back(neww);//从now这个状态添加一条出边
c[now.wa[]][now.wa[]].push_back(cost);
q.push(neww);
}
}
} }
int dis[maxn][maxn], vis[maxn][maxn];
void spfa()
{
for(int i = ; i < maxn; ++i)
for(int j = ; j < maxn; ++j)
{
dis[i][j] = INF; vis[i][j] = ;
}
queue<Cup>q; q.push((Cup){, , wmax[]}); //初始状态
dis[][] = ;
while(!q.empty())
{
Cup now = q.front(); q.pop();
vis[now.wa[]][now.wa[]] = ;
for(int i = ; i < v[now.wa[]][now.wa[]].size(); ++i)
{
Cup neww = v[now.wa[]][now.wa[]][i];
if(dis[now.wa[]][now.wa[]] + c[now.wa[]][now.wa[]][i] < dis[neww.wa[]][neww.wa[]])
{ // 有更好的结果就更新
dis[neww.wa[]][neww.wa[]] = dis[now.wa[]][now.wa[]] + c[now.wa[]][now.wa[]][i];
if(!vis[neww.wa[]][neww.wa[]])
{
vis[neww.wa[]][neww.wa[]] = ;
q.push(neww);
}
}
}
}
int ans1 = , ans2 = ;
for(int i = ; i < maxn; ++i)
for(int j = ; j < maxn; ++j)
{
if(exist[i][j]) //这个点存在
{
int newd = ;
if(i <= d && i >= newd) newd = i; //不断更新d′,使 d′不断趋近于 d。
if(j <= d && j >= newd) newd = j;
int cwa = wmax[] - i - j;
if(cwa <= d && cwa >= newd) newd = cwa;
if(newd > ans1 || (newd == ans1 && dis[i][j] < ans2))
{
ans1 = newd; ans2 = dis[i][j];
}
} }
printf("%d %d\n", ans2, ans1);
} int main()
{
int t; scanf("%d", &t);
while(t--)
{ scanf("%d%d%d%d", &wmax[], &wmax[], &wmax[], &d);
build();
spfa();
}
return ;
}

代码不短,不过要是改成隐式图搜索,建图的函数几乎就可以全省了。

那么怎么改呢?想一想建图只不过就是求出了 vector<Cup>v[maxn][maxn] 和 vector<Cup>c[maxn][maxn],那么我们只要将 spfa中的 vector<Cup>v[maxn][maxn] 和 vector<Cup>c[maxn][maxn]替换成找出边和求边权的语句不就行了吗?

看看上面的代码,这个语句就是

 int daoshui(int a, int b, int amax, int bmax, int& newa, int& newb)
{
if(a >= bmax - b)
{
newa = a - (bmax - b); newb = bmax; return bmax - b;
}
else
{
newa = ; newb = b + a; return a;
}
} for(int i = ; i < ; ++i)
for(int j = ; j < ; ++j)
{
if(i == j) continue;
int t[]; Cup neww = now;
int cost = daoshui(now.wa[i], now.wa[j], wmax[i], wmax[j], t[i], t[j]);
}

那么就可以改了

 #include <bits/stdc++.h>
using namespace std;
const int maxn = ;
const int INF = 0x3f3f3f3f;
struct Point
{
int w[];
};
int wmax[], d;
int pour(int a, int b, int amax, int bmax, int& na, int& nb){ // a -> b
if (bmax - b >= a) {na = , nb = b + a; return a;}
else {na = a - (bmax - b), nb = bmax; return bmax - b;}
}
int dist[maxn][maxn];
int vis[maxn][maxn];
void spfa(){
queue<Point>q; q.push((Point){, , wmax[]});
for (int i = ; i < maxn; i++)
for (int j = ; j < maxn; j++) dist[i][j] = INF;
dist[][] = ;
while (!q.empty()){
Point now = q.front(); q.pop(); vis[now.w[]][now.w[]] = ;
for (int i = ; i < ; i++){
for (int j = ; j < ; j++){
if (i == j) continue;
int t[];
Point np = now;
int cost = pour(np.w[i], np.w[j], wmax[i], wmax[j], t[i], t[j]);
np.w[i] = t[i], np.w[j] = t[j];
if (dist[np.w[]][np.w[]] > dist[now.w[]][now.w[]] + cost){
dist[np.w[]][np.w[]] = dist[now.w[]][now.w[]] + cost;
if (!vis[np.w[]][np.w[]]){
vis[np.w[]][np.w[]] = ;
q.push(np);
}
}
}
}
}
int ans1 = , ans2 = ;
for (int i = ; i < maxn; i++){
for (int j = ; j < maxn; j++){
if (dist[i][j] < INF){
int nd = ;
if (i <= d && i >= nd) nd = i;
if (j <= d && j >= nd) nd = j;
int k = wmax[] - i - j;
if (k <= d && k >= nd) nd = k;
if (nd > ans1 || (nd == ans1 && dist[i][j] < ans2)) {ans1 = nd; ans2 = dist[i][j];}
}
}
}
printf("%d %d\n", ans2, ans1);
}
int main()
{
int t; scanf("%d", &t);
while(t--)
{
scanf("%d%d%d%d", &wmax[], &wmax[], &wmax[], &d);
spfa();
}
return ;
}

代码量骤减。。

倒水问题(Fill, UVa 10603)的更多相关文章

  1. 倒水问题(Fill,UVA 10603) lrj白书 p202

    看着lrj的代码自己敲了一遍,还没调试成功.... 有时间再进行完善 /* 状态start到各个状态u1,u2,u3..... 的倒水量分别为u1.dist,u2.dist,u3.dist.... * ...

  2. UVa 10603 Fill (BFS && 经典模拟倒水 && 隐式图)

    题意 : 有装满水的6升的杯子.空的3升杯子和1升杯子,3个杯子中都没有刻度.不使用道具情况下,是否可量出4升水呢? 你的任务是解决一般性的问题:设3个杯子的容量分别为a, b, c,最初只有第3个杯 ...

  3. UVA 10603 - Fill BFS~

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&c ...

  4. UVa 10603 Fill [暴力枚举、路径搜索]

    10603 Fill There are three jugs with a volume of a, b and c liters. (a, b, and c are positive intege ...

  5. UVA 10603 Fill

    题意: 题目的意思是倒水,给出的四个数据是第一个水杯,第二个水杯,第三个水杯,和目标水量.一开始只有第三个水杯是满的,剩下的水杯是空的.倒水的时候只能把倒水出来的这个杯子倒空,或是倒水进去的杯子倒满. ...

  6. UVA 10603 Fill(正确代码尽管非常搓,网上很多代码都不能AC)

    题目链接:option=com_onlinejudge&Itemid=8&page=show_problem&problem=1544">click here~ ...

  7. UVa 10603 倒水问题

    https://vjudge.net/problem/UVA-10603 题意:三个杯子,倒水问题.找出最少倒水量. 思路:路径寻找问题.不难,暴力枚举. #include<iostream&g ...

  8. UVa 10603 Fill (暴力BFS+优先队列)

    题意:给定4个数,a,b,c,d,分别代表空杯子容积为a,b,一个盛满水的杯子容积为c,让你不断倒水,找一个dd,是不是存在某个时刻, 某个杯子里的水dd,和d相同,或者无限接近.让求最少的倒水量和d ...

  9. UVA - 10603 Fill(隐式图搜索)

    题目大意:经典的倒水问题. 给你三个瓶子,体积为a,b,c. 刚開始a.b是空的,c是满的,如今要求你到出体积为d的水.倒水的规则为,要么倒水方为空,要么接水方满 问倒到容量为d时,倒水的最小体积是多 ...

随机推荐

  1. openssl speed和openssl rand

    openssl系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html 1.1 openssl speed 测试加密算法的性能. 支持的算法有: o ...

  2. Shell 示例:利用 $RANDOM 产生随机整数

    代码如下: #!/bin/bash # $RANDOM 在每次调用的时候,返回一个不同的随机整数 # 指定的范围是: 0 - 32767 MAXCOUNT=10 count=1 echo echo & ...

  3. ExtJS中xtype一览

    基本组件: xtype Class 描述 button Ext.Button 按钮 splitbutton Ext.SplitButton 带下拉菜单的按钮 cycle Ext.CycleButton ...

  4. Linux中ansible批量管理软件部署及剧本编写

    服务器版本信息: Centos6.9 [root@db02 ~]# uname -a Linux db02 2.6.32-696.el6.x86_64 #1 SMP Tue Mar 21 19:29: ...

  5. 跨站请求伪造CSRF(Cross-site request forgery)

    CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站 ...

  6. js 日期格式转换(转载)

    1.当前时间转为 “yyyy-MM-dd HH:MM:SS” function getNowFormatDate() { var date = new Date(); var seperator1 = ...

  7. vue规格新增一对多的例子

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. Ajax的实现及使用-原生对象

    正文 学习JavaScript就不得不提到Ajax,从2005年开始,Ajax技术就开始席卷整个Web世界.作为一个前端来说,大部分时间中都是使用的库中封装好的ajax模块(jQuery),即使已使用 ...

  9. css选择器:基本选择器

    基本选择器 1.通用元素选择器 *表示应用到所有的标签. *{ padding:0px; margin:0px; } 2.元素/标签选择器 匹配所有p标签的元素 p{ color:red; backg ...

  10. 转: Laravel 自定义公共函数的引入

    来源:Laravel 自定义公共函数的引入 背景习惯了 使用 ThinkPHP 框架,有一个公共方法类在代码编写上会快捷很多,所以有必要在此进行配置一番.测试框架:Laravel 5.5步骤指导1. ...