@LinKArftc
2015-08-17T15:55:05.000000Z
字数 6497
阅读 2341
动态规划
某公司要建立一套通信系统,该通信系统需要n种设备,而每种设备分别可以有m1、m2、m3、...、mn个厂家提供生产,而每个厂家生产的同种设备都会存在两个方面的差别:带宽bandwidths 和 价格prices。
现在每种设备都各需要1个,考虑到性价比问题,要求所挑选出来的n件设备,要使得B/P最大。
其中B为这n件设备的带宽的最小值,P为这n件设备的总价。
我们定义状态 dp[i][j]
表示选择了前 i 个宽带其容量为 j 的最小费用。
很容易得到转移方程 : dp[i][j] = min(dp[i][j], dp[i-1][k] + p);
注意选择 j 的时候的大小情况。
int dp[105][1005];//前 i 个device在带宽为 j 时的最小价格
int main() {
int T;
scanf("%d", &T);
while (T --) {
memset(dp, 0x3f, sizeof(dp));
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i ++) {
int mi;
scanf("%d", &mi);
for (int j = 1; j <= mi; j ++) {
int b, p;
scanf("%d %d", &b, &p);
if (i == 1) dp[1][b] = min(dp[1][b], p);
else {
for (int k = 0; k < 1005; k ++) {
if (dp[i-1][k] < inf) {
if (k <= b) dp[i][k] = min(dp[i][k], dp[i-1][k] + p);
else dp[i][b] = min(dp[i][b], dp[i-1][k] + p);
}
}
}
}
}
double ans = 0.0;
for (int i = 0; i < 1005; i ++) {
if (dp[n][i] < inf) {
ans = max(ans, (double)i / dp[n][i]);
}
}
printf("%.3f\n", ans);
}
return 0;
}
求最大子矩阵和 (n <= 100)
将二维问题转化为一维,对于第 k 列,暴力求第 i 行到第 j 行的和,这样就转化为一维数组上求最大子段和。
复杂度 O(n^3)
const int maxn = 105;
int arr[maxn][maxn];
int sum[maxn];
int n;
int max_arr() {
int ret = 0;
int cur = 0;
for (int i = 1; i <= n; i ++) {
cur += sum[i];
if (cur > 0) ret = max(ret, cur);
else cur = 0;
}
return ret;
}
int main() {
while (~scanf("%d", &n)) {
for (int i = 1; i <= n; i ++) {
for (int j = 1;j <= n; j ++) {
scanf("%d", &arr[i][j]);
}
}
int ans = 0;
for (int i = 1; i <= n; i ++) {
memset(sum, 0, sizeof(sum));
for (int j = i; j <= n; j ++) { //i -> j row
for (int k = 1; k <= n; k ++) sum[k] += arr[j][k];
int cur = max_arr();
ans = max(ans, cur);
}
}
printf("%d\n", ans);
}
return 0;
}
经典的滑雪问题,连题号我都记住了= =,没事写来练练手
记忆话搜索
const int maxn = 105;
int n, m;
int mp[maxn][maxn];
int dp[maxn][maxn];
int dx[] = { -1, 0, 1 , 0 };
int dy[] = { 0, 1, 0, -1 };
int search(int x, int y) {
if (dp[x][y]) return dp[x][y];
for (int i = 0; i < 4; i ++) {
int xx = x + dx[i];
int yy = y + dy[i];
if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && mp[xx][yy] > mp[x][y]) {
int cur = search(xx, yy);
dp[x][y] = max(dp[x][y], cur + 1);
}
}
return dp[x][y];
}
int main() {
while (~scanf("%d %d", &n, &m)) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
scanf("%d", &mp[i][j]);
}
}
memset(dp, 0, sizeof(dp));
int ans = 0;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
int cur = search(i, j);
ans = max(ans, cur);
}
}
printf("%d\n", ans + 1);
}
return 0;
}
写的第一道概率dp。
第i层有2*i+1种可能的位置(从第0个空位到最后一个空位总共i+1个和i个钉子位置),注意题目中下标从0开,用d[i][j]表示第i行在第j个位置掉落的概率的分子(分母是2^i)。
如果位置是空位,那么有3种情况:
如果位置是有钉子的,有2种情况:
int mp[110][110];
ll dp[110][210];
int n, m;
int main() {
scanf("%d %d", &n, &m);
char tmp;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i; j ++) {
scanf(" %c", &tmp);
if (tmp == '*') mp[i][j] = 1;
else mp[i][j] = 0;
}
}
if (mp[1][1] == 1) {
dp[1][1] = 1;
dp[1][2] = 0;
dp[1][3] = 1;
} else {
dp[1][1] = 0;
dp[1][2] = 2;
dp[1][3] = 0;
}
for (int i = 2; i <= n; i ++) {
dp[i][1] = mp[i][1] == 1 ? dp[i-1][1] : 0;
dp[i][2*i+1] = mp[i][i] == 1 ? dp[i-1][2*i-1] : 0;
for (int j = 2; j <= 2*i ; j ++) {
ll cur;
if (j % 2) {
cur = dp[i-1][j-1] * 2;
cur += mp[i][j/2] == 1 ? dp[i-1][j-2] : 0;
cur += mp[i][j/2+1] == 1 ? dp[i-1][j] : 0;
} else {
cur = mp[i][j/2] == 1 ? 0 : dp[i-1][j-1] * 2;
}
dp[i][j] = cur;
}
}
ll a = dp[n][2*m+1];
ll b = (ll)1 << n;
while ((a % 2 == 0) && (b % 2 == 0)) {
a /= 2;
b /= 2;
}
printf("%lld/%lld\n", a, b);
return 0;
}
一共有c种巧克力, 每种的个数都是一样多并且是足够多,现在从包里面拿出n次巧克力一次一个,当桌面上有2个相同的时候就吃掉这两个, 现在问你在拿出n次之后桌面上恰好有m个巧克力的概率.
状态dp[i][j]: 第i次操作后, 桌面上出现j个巧克力的概率.因为每次吃两个,所以(i+j)为奇数是dp[i][j]=0。
状态转移:dp[i][j] = dp[i-1][j-1] * (c-j+1.0) / c + dp[i-1][j+1] * (j+1.0) / c;
第i次只与第i-1次有关,所以空间可以压缩。
另外,由于C (C <= 100), N and M (N, M <= 1000000),所以 O(C*N) 华丽丽的超时了,看网上的代码才知道因为精度是三位小数,所以当拿的次数大于1000时概率已经是趋于平衡。。= =||,万万没想到~~~
const int maxn = 105;
int c, n, m;
double dp[2][maxn];
int main() {
while (~scanf("%d", &c) && c) {
scanf("%d %d", &n, &m);
if (m > c || m > n || ((m+n)&1)) {
printf("0.000\n");
continue;
}
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
if (n > 10000) n = 10000 + (n&1);
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= c; j ++) {
if ((i+j)&1) continue;
dp[i&1][j] = dp[(i-1)&1][j-1] * (c-j+1.0) / c + dp[(i-1)&1][j+1] * (j+1.0) / c;
}
}
printf("%.3f\n", dp[n&1][m]);
}
return 0;
}
n 个木板,每个木板有l,m两个属性,当a木板的l和m都大于等于b木板时,a可以放在b上面,问最多可以堆叠多少个木板
num[i][j]
表示l=i,m=j的木板数目, dp[i][j]
表示l=i,m=j时最多可以堆叠的木板数目,则dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + num[i][j];
//把多余的头文件、注释和num数组去掉之后仍然有260K,不知道那些44K的大神是怎么写的= =
const int maxn = 105;
int n;
int dp[maxn][maxn];
int num[maxn][maxn];
int main() {
while (~scanf("%d", &n) && n) {
int a, b;
memset(num, 0, sizeof(num));
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i ++) {
scanf("%d %d", &a, &b);
num[a][b] ++;
}
for (int i = 1; i <= 100; i ++) {
for (int j = 1; j <= 100; j ++) {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + num[i][j];
}
}
printf("%d\n", dp[100][100]);
}
printf("*\n");
return 0;
}
一个线性棋盘,一开始玩家在0位置,重点位置为m+1,每一轮抛硬币决定向右一步还是两步,概率各为0.5。棋盘每格上都会有一个说明+n,-n,L,或者0,分别意味着向右走n格,向左走n格,失去一轮机会,无任何说明。给出棋盘的大小,以及棋盘上每一格的说明,求在第t轮或者少于t轮便走到终点处的概率。概率恰好等于0.5,printf("Push. 0.5000\n");
,大于0.5,printf("Bet for. %.4f\n", ans);
,小于0.5,printf("Bet against. %.4f\n", ans);
不难,dp[i][j]
表示第i次走到j位置的概率
#define eps 1e-8
const int inf = 0x3f3f3f3f;
const int maxn = 60;
int ins[maxn];
double dp[maxn][maxn]; //第i次走到j位置的概率
char str[5];
int m, t;
int main() {
int T;
scanf("%d", &T);
while (T --) {
scanf("%d %d", &m, &t);
ins[0] = 0;
ins[m + 1] = 0;
ins[m + 2] = -1;
for (int i = 1; i <= m; i ++) {
scanf("%s", str);
if (str[0] == 'L') ins[i] = inf;
else sscanf(str, "%d", &ins[i]);
}
memset(dp, 0, sizeof(dp));
dp[0][0] = 1.0;
for (int i = 0; i <= t; i ++) {
for (int j = 0; j <=m; j ++) {
if (ins[j+1] == inf) dp[i+2][j+1] += dp[i][j] * 0.5;
else dp[i+1][j+1+ins[j+1]] += dp[i][j] * 0.5;
if (ins[j+2] == inf) dp[i+2][j+2] += dp[i][j] * 0.5;
else dp[i+1][j+2+ins[j+2]] += dp[i][j] * 0.5;
}
}
double ans = 0.0;
for (int i = 0; i <= t; i ++) ans += dp[i][m+1];
if (fabs(ans - 0.5) < eps) printf("Push. 0.5000\n");
else if (ans > 0.5) printf("Bet for. %.4f\n", ans);
else printf("Bet against. %.4f\n", ans);
}
return 0;
}
有两棵苹果树,标号分别为1,2。每分钟其中一棵树会掉下一个苹果,但不知道是哪棵,奶牛初始位置为1,从一个树跑到另一个树需要一分钟,但它最多只会跑W次。问你在T分钟内,奶牛最多能接到多少苹果。
刚开始是想用三维 dp[i][j][k]
表示奶牛第i分钟跑了j次在位置k最多能接到的苹果数,后来突然发现既然奶牛初始位置为1且只有两棵树,那j为奇数奶牛位置肯定为2,为偶数肯定为1,所以二维dp就可以了,dp[i][j]
表示奶牛第i分钟跑了j次最多能接到的苹果数,所以j > 0
时,用cur表示当前是哪颗苹果树掉苹果,则dp[i][j] = max(dp[i-1][j], dp[i-1][j-1[) + cnt;
如果j为奇数cnt = (cur == 2 ? 1 : 0);
,否则cnt = (cur == 1 ? 1 : 0);
const int maxt = 1005;
const int maxw = 35;
int dp[maxt][maxw];
int pos[maxt];
int main() {
int t, w, cur;
scanf("%d %d", &t, &w);
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= t; i ++) {
scanf("%d", &cur);
for (int j = 0; j <= w; j ++) {
if (j) dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]);
else dp[i][j] = dp[i-1][j];
if (j & 1) dp[i][j] += (cur == 2 ? 1 : 0);
else dp[i][j] += (cur == 1 ? 1 : 0);
}
}
int ans = -1;
for (int j = 1; j <= w; j ++) ans = max(ans, dp[t][j]);
printf("%d\n", ans);
return 0;
}
n(1 <= n <= 150000) 个数组成的序列,sum初始值为0,求它的一个子序列,sum加上这个序列的每个奇数项,减去这个序列的每个偶数项,使得sum最大。
dp[i][0]
表示前i个数,总共选了偶数次得到的最大sum
dp[i][1]
表示前i个数,总共选了奇数次得到的最大sum
所以:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - num[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + num[i]);
const int maxn = 150005;
int dp[maxn][2];
int num[maxn];
int main() {
int n;
while (~scanf("%d", &n)) {
dp[0][0] = dp[0][1] = 0;
for (int i = 1; i <= n; i ++) scanf("%d", &num[i]);
for (int i = 1; i <= n; i ++) {
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - num[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + num[i]);
}
printf("%d\n", max(dp[n][0], dp[n][1]));
}
return 0;
}