トピックリンク:
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4496
主人公とドラゴンの間に最後の門が1つあります。しかし、門を開くのは簡単なことではありません。
ゲートの前に直線でn個のボタンリストがあり 、それぞれに整数が付いています。主人公が以前に解いた他のパズルのように、すべてのボタンがいつか押された場合、ゲートが開きます。したがって、パズルを解くために、主人公はすべてのボタンを1つずつ押す必要があります。
いくつかの試行の後、主人公は、彼が押したボタンがすべてのボタンを押すことができる前に、しばらくするとポップアップすることを発見しました。彼はすぐに、ボタンの整数が、ボタンを押した後に自動的にポップアップする時間(秒単位)であることに気付きました。そして、彼はすべてのボタンと最初のボタンの間の距離を、ヒーローが1秒あたりに到達できる最大距離の単位で測定しました。この情報があっても、主人公はボタンを押す順序を理解できませんでした。だからあなたは才能のあるプログラマーであり、彼がパズルを解くのを手伝うように割り当てられています。
パズルを簡単にするために、主人公が1つのボタンから別のボタンに移動するのに常に整数秒かかり、向きを変えたりボタンを押したりするのに時間がかからなかったと仮定します。そして、ヒーローはどのボタンからでも始めることができます。
入力
入力ファイルには複数のケースが含まれます。各ケースには3行が含まれています。ファイルの終わりまで処理します。
最初の行は、単一の整数含ま N(1≤ N ≤200)、ボタンの数。
2行目は含ま nは 整数 T 1、 T 2、...、 T nは、ここで T I(1≤ T I ≤1,000,000)私の時間である番目の ボタンは、自動秒単位で、それを押した後ポップアップだろう。
三行目は含ま nは 整数 D 1、 D 2、...、 D nは、ここで、 D I(1≤ D I ≤1,000,000は、i)との間で移動するために必要な時間の主人公である番目 の単位でボタンと最初のボタン、 2番目。シーケンスは昇順で、最初の要素は常に0です。
出力
主人公が押すボタンのシーケンスであるn個の整数を含む1行を出力します。乗算シーケンスがある場合は、誰でも実行できます。主人公がパズルを解く方法がない場合は、「ミッションインポッシブル」(引用符なし)を1行で出力するだけです。
サンプル入力
2 4 3 0 3 2 3 3 0 3 4 5200 1 2 0 1 2 3
サンプル出力
12 ミッションインポッシブル 12 4 3
質問:
直線上にn(1 <= n <= 200)個のスイッチがあります。スイッチiの属性d [i]は左端のスイッチからの距離を示し、t [i]はスイッチが押されていることを示します。t[ i] 1秒後に自動的にポップアップします。
すべてのスイッチが特定の時間に押されるように、スイッチを押すシーケンスを見つけます。どのスイッチからでも開始でき、手の動きの速度は1秒あたり1単位の長さであり
、スイッチを押す時間は無視されます。
解決策:
dp [i] [j]は、すべてのスイッチiからjを押すのに必要な時間を意味し、どのスイッチからでも開始できます。
ここで、結論を証明する必要があります。区間[i、j]の解がある場合、最適解(つまり、最短時間)スキームの開始点は、区間の終わり(左端)にある必要があります。または右端)。
テキスト証明:
明らかにn <= 2に当てはまります。
对于n>=3,假设起始点不是端点,而是中间的某个点,在按完一些按钮后必然会按一个端点按钮(此时另一个端点还没有被按下),在按下第一个端点和按下另一个端点按钮过程中,必然要经过该区间内所有的点,其中有一些点已经在之前被按下,很显然,
这些点“晚按”比“早按”更优。所以,在端点被按下之前的所有按下操作都是多余的,浪费时间的,完全可以在端点按下之后再按。
(口述证明比较难懂,自己琢磨)
现在整个过程就能这样看了:开始从[1,n]区间取一个端点按下后,剩下一段连续的区间,再选取一个端点按下,还是剩下一段连续的区间...
现在就用
dp[i][j][0] 表示先按下区间[i,j]的左端点
dp[i][j][1] 表示先按下右端点
要求[1,n]就可以递归到求[1,1],[2,2]...[n,n],而dp[i,i]=0;
以上参考自:http://blog.csdn.net/morgan_xww/article/details/6844333
//区间DP, AC代码
#include <stdio.h>
#include <algorithm>
#include <string.h>
#define maxn 225
#define inf 0x3f3f3f3f
using namespace std;
int dp[maxn][maxn][2];
int path[maxn][maxn][2];
//dp[i][j][0]表示区间i~j从左边端点开始按 dp[][][1]表示从右边端点开始
//path[i][j][k]表示与之对应的状态。
int d[maxn], t[maxn];
int main()
{
int n;
while(scanf("%d", &n)!=EOF)
{
for(int i=0; i<n; i++)
scanf("%d", &t[i]);
for(int i=0; i<n; i++)
scanf("%d", &d[i]);
memset(dp, 0, sizeof(dp));
memset(path, 0, sizeof(path));
for(int len=2; len<=n; len++)
for(int i=0; i<n; i++)
{
int j = i + len - 1;
//处理左端点
int le = dp[i+1][j][0] + d[i+1] - d[i];
int re = dp[i+1][j][1] + d[j] - d[i];
if(le < re)
{
path[i][j][0] = 1; //从左端点来
dp[i][j][0] = le;
}
else
{
path[i][j][0] = 0; //去右端点
dp[i][j][0] = re;
}
if(t[i] <= dp[i][j][0] || dp[i][j][0]>=inf)
dp[i][j][0] = inf;
//处理右端点
le = dp[i][j-1][0] + d[j] - d[i];
re = dp[i][j-1][1] + d[j] - d[j-1];
if(le < re)
{
path[i][j][1] = 1; //去左端点
dp[i][j][1] = le;
}
else
{
path[i][j][1] = 0; //从右端点
dp[i][j][1] = re;
}
if(t[j] <= dp[i][j][1] || dp[i][j][1]>=inf)
dp[i][j][1] = inf;
}
int r, l, va;
if(dp[0][n-1][0]<inf)
{
printf("1");
l = 1, r = n-1;
va = path[0][n-1][0];
}
else
if(dp[0][n-1][1]<inf)
{
printf("%d", n);
l = 0, r = n-2;
va = path[0][n-1][1];
}
else
{
printf("Mission Impossible\n");
continue;
}
while(l<=r)
{
if(va==0)
{
printf(" %d", r+1);
va = path[l][r][1];
r--;
}
else
{
printf(" %d", l+1);
va = path[l][r][0];
l++;
}
}
printf("\n");
}
return 0;
}