先给一个Virtual Judge 上挂的地址 dp
A - A Spy in the Metro
题意
起点站和终点站之间有若干个站点,同时,在起点站和终点站会在若干的固定时间始出列车。某人将从起点站出发,在t时到终点站见人,但是希望在站点等尽量少的时间。求出最少的等待时间。
分析
dp。。
“时间 是 一个天然的序”,所以,影响决策的只有时间和当前的站点。
dp[i][j] 表示 在 第i时间 在 j 站点的最小等待时间。所以每一个都面临最多三种的决策:
- 在原地等待一分钟
- 选择向左开的列车
选择向右开的列车
三种决策中选择最小等待时间的。
考虑边界条件:dp[T][n]= 0, 其他时间的dp[T][i] 预先设成正无穷,在dp过程中无法选择。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 50 + 5;
const int maxt = 200 + 5;
const int INF = 1000000000;
// has_train[t][i][0]表示时刻t,在车站i是否有往右开的火车
int t[maxn], has_train[maxt][maxn][2];
int dp[maxt][maxn];
int main()
{
int kase = 0, n, T;
while(cin >> n >> T && n)
{
int M1, M2, d;
for(int i = 1; i <= n-1; i++) cin >> t[i];
// 预处理,计算has_train数组
memset(has_train, 0, sizeof(has_train));
cin >> M1;
while(M1--)
{
cin >> d;
for(int j = 1; j <= n-1; j++)
{
if(d <= T) has_train[d][j][0] = 1;
d += t[j];
}
}
cin >> M2;
while(M2--)
{
cin >> d;
for(int j = n-1; j >= 1; j--)
{
if(d <= T) has_train[d][j+1][1] = 1;
d += t[j];
}
}
// DP主过程
for(int i = 1; i <= n-1; i++) dp[T][i] = INF;
dp[T][n] = 0;
for(int i = T-1; i >= 0; i--)
for(int j = 1; j <= n; j++)
{
dp[i][j] = dp[i+1][j] + 1; // 等待一个单位
if(j < n && has_train[i][j][0] && i+t[j] <= T)
dp[i][j] = min(dp[i][j], dp[i+t[j]][j+1]); // 右
if(j > 1 && has_train[i][j][1] && i+t[j-1] <= T)
dp[i][j] = min(dp[i][j], dp[i+t[j-1]][j-1]); // 左
}
// 输出
//cout << dp[0][1] << endl;
cout << "Case Number " << ++kase << ": ";
if(dp[0][1] >= INF) cout << "impossible\n";
else cout << dp[0][1] << "\n";
}
return 0;
}
后续的题目待续。。
C-Tour
这一道问题有一个十分高大上的问题,叫做 双调旅行商问题。
题意
某人从最左的一点出发,按照严格从左到右的顺序移动到最右一点。然后从最右点返回,按照严格从右到左的顺序回到最左点,并且每一个点都只能访问一次。求最小的距离。
抽象
给你 n 个二维坐标上的点,你需要经过所有的点然后走回原地,求最短路径。
思路
首先,想成是两个人从最左点出发,一同走到最右点,两人中间的访问地点不能相同。可以用dp(i,j),表示1~max{i,j}的点全部走完了,第一个人走到i点,第二个人走到j点,还需要多长的距离。
状态间的转移方式:
为防止dp(i,j) = dp(j,i),对解的产生冲突。
现在同时加上 强制条件:i > j。
原先 可以写成:dp(i,j)= dp(i+1,j)+ dp(i,j+1)。(这是向i+1点转移的方式)
但是 由 j 点转移到 i+1点是不符合条件的,所以先将i,j 对调,再让j点移动到i+1点。于是可以变成 dp(i+1, i);
所以, dp(i,j) = dp(i+1, j) + dp(i+1, i);
边界条件:dp(n-1, j) = dis(n, n-1) + dis(n,j)
代码
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#define MEM(a,x) memset(a,x,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int maxn = 1010;
double dp[maxn][maxn];
double dis[maxn][maxn];
struct Point
{
int x, y;
};
Point p[maxn];
double dist(Point a, Point b)
{
return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n;
while(cin >> n)
{
for(int i = 1; i <= n; i++)
{
cin >> p[i].x >> p[i].y;
}
for(int i = 1; i <= n; i++)
{
for(int j = i+1; j <= n; j++)
{
dis[i][j] = dis[j][i] = dist(p[i], p[j]);
}
}
for(int j = 1; j <= n; j++)
dp[n-1][j] = dis[n][n-1] + dis[n][j];
for(int i = n-2; i >= 1; i--)
{
for(int j = 1; j <= n; j++)
{
dp[i][j] = min(dp[i+1][j]+dis[i][i+1], dp[i+1][i]+dis[j][i+1]);
}
}
printf("%.2lf\n", dp[1][1]);
}
return 0;
}
D-单向TSP问题
题意
给出二维数组,从最左一列任意位置出发每次往右、右上或者右下的方向移动,到最后的一列。需要注意的是,第一行的右上为最后一行对应的位置,同理,最后一行的情况相同。
每一个都有三种决策的情况。思路比较简单,关键实现。还有就是要在开一个数组来打印路径。
看代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#define MEM(a,x) memset(a,x,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int INF = 999999999;
int a[105][105], dp[105][105], p[105][105];
int main()
{
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n, m;
while(cin >> n >> m)
{
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
{
cin >> a[i][j];
dp[i][j] = INF;
}
for(int i = 0; i < n; i++)
dp[i][m-1] = a[i][m-1];
for(int j = m-2; j >=0; j--)
{
for(int i = 0; i < n; i++)
{
int c[] = {i-1, i, i+1};
if(i == 0) c[0] = n-1;
if(i == n-1) c[2] = 0;
sort(c, c+3);
// for(int k = 1; k < 3; k++)
// cout << c[k] << " ";
// cout << endl;
dp[i][j] = INF;
for(int k = 0; k < 3; k++)
{
int v = dp[c[k]][j+1] + a[i][j];
if(dp[i][j] > v)
{
dp[i][j] = v;
p[i][j] = c[k];
}
}
}
}
// for(int i = 0; i < n; i++)
// {
// for(int j = 0; j < m-1; j++)
// cout << p[i][j] << " ";
// cout << endl;
// }
int ans = 999999999;
int first = 0;
for(int i = 0; i < n; i++)
{
if(ans > dp[i][0])
{
ans = dp[i][0];
first = i;
}
}
printf("%d", first+1);
for(int i = p[first][0], j = 1; j < m; i = p[i][j], j++) printf(" %d", i+1);
cout << endl << ans << endl;
}
return 0;
}
F - Lighting System Design
题意
设计一个照明系统,一共有n种灯泡进行选择。不同种类的灯泡必须用不同的电源,但同一种灯泡却可以共用一个电源。每种灯泡给定:电压,电源费用,每个灯泡的费用和所需灯泡数。同时可以把一些灯泡换成电压更高的另一种灯泡来节省电源的费用,计算出最优的花费。
分析
贪心的思考,每种电压的灯泡要换就全换。
先将所用种类的灯泡按照电压值进行从小到大的排序,每种灯泡都是可以将前面的灯泡替换成本身的灯泡,所以可以dp(i) 表示截止到第i种灯泡的最优花费。
dp(i) = min{dp(j) + (j~i num) * price[i] + k [i]} ,同时也可以用前缀数组进行优化。
代码
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#define MEM(a,x) memset(a,x,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int maxn = 1005;
const int INF = 1000000000;
struct L
{
int v, k, cost, num;
};
L l[maxn];
int dp[maxn], s[maxn];
bool cmp(L a, L b)
{
return a.v < b.v;
}
int main()
{
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n;
while(cin >> n && n)
{
for(int i = 1; i <= n; i++)
{
cin >> l[i].v >> l[i].k >> l[i].cost >> l[i].num;
}
sort(l+1, l+n+1, cmp);
s[0] = 0;
for(int i = 1; i <= n; i++)
s[i] = s[i-1] + l[i].num;
dp[0] = 0;
for(int i = 1; i <= n; i++)
{
dp[i] = s[i] * l[i].cost + l[i].k;
for(int j = 0; j < i; j++)
{
int v = dp[j] + (s[i] - s[j])*l[i].cost + l[i].k;
dp[i] = min(dp[i], v);
}
}
cout << dp[n] << endl;
}
return 0;
}
G- Partitioning by Palindromes
题意
输入一个由小写字母组成的字符串,划分成尽量少的回文串。
分析
设dp(i) 表示 0~ i 划分成最小回文串的个数。
状态转移方程为:
dp(i) = min{ dp(j) + 1 | s[j+1…i] 是回文串};
边界: dp(i) = i+1;
在实现的时候,注意对开头就能构成回文串的个数进行特判为1.
代码
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#define MEM(a,x) memset(a,x,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int INF = 10000000;
const int maxn = 1000+10;
int p[maxn][maxn], dp[maxn];
string s;
bool is_p(int l, int r)
{
while(l <= r)
{
if(s[l] != s[r]) return false;
l++;
r--;
}
return true;
}
int main()
{
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int t;
cin >> t;
while(t--)
{
cin >> s;
int n = s.size();
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
p[i][j] = is_p(i,j);
}
}
for(int i = 0; i < n; i++) dp[i] = i+1;
for(int i = 1; i < n; i++)
{
if(p[0][i]) dp[i] = 1;
for(int j = 1; j <= i; j++)
{
if(p[j][i])
{
// cout << dp[j] << endl;
dp[i] = min(dp[i], dp[j-1]+1);
}
}
}
cout << dp[n-1] << endl;
}
return 0;
}
OK。。。