A - Bottles
[Problem]
有n个苏打水的瓶子,每瓶中装有一定量苏打水,给你每瓶的体积,现在像把这些苏打水装到尽量少的瓶子中,并且移动尽可能少的苏打水。
[Solution]
f[i]代表凑齐i体积需要瓶子的最小数量,g[i]代表盛有的苏打水体积,在满足f[i]尽可能小的情况下,g[i]最大
[Code]
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100000 + 500;
const int inf = 0xfffffff;
int n, p, k;
long long g[N], f[N], remain[N], v[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &remain[i]);
for(int i = 1; i <= n; i++)
scanf("%d", &v[i]);
for(int i = 0; i <= 10000; i++)
{
g[i] = 0;
f[i] = inf;
}
f[0] = 0;
for(int i = 1; i <= n; i++)
for(int j = 10000; j >= v[i]; j--)
{
int res = j - v[i];
if (f[res] + 1 < f[j] || (f[res] + 1 == f[j] && g[res] + remain[i] > g[j]))
{
f[j] = f[res] + 1;
g[j] = g[res] + remain[i];
}
}
long long sum = 0;
for(int i = 1; i <= n; i++)
sum += remain[i];
int ans = inf, tot = 0;
for(int i = sum; i <= 10000; i++)
{
if (f[i] < ans || (f[i] == ans && g[i] > tot) )
{
ans = f[i];
tot = g[i];
}
}
printf("%d %d", ans, sum - tot);
return 0;
}
B - Sonya and Problem Wihtout a Legend - Bottles
[Problem]
给定一个序列,询问将其变成单调增加序列的花费
[Solution]
上述问题是这个问题的变式:
给定一个序列,将其变成单调不下降序列的花费。
先说一个事实:目标序列中所有出现的数都在原数列出现过。
下面简单说一下证明,首先我们对于第k个数,即a[k],如果我们选择增加这个数,首先考虑我们为什么非要增加呢?因为前面的数比a[k]大,那我们要增加到什么程度呢?增加到a[k-1]就好呀,因为在满足条件的情况下,a[k]越小, 对于以后的选择就越多,代价便越小。
如果我们选择减少这个数,因为后面的一些数太小,我们改动他后面的数代价太高,从而选择选择减少这个数,那减少到什么程度呢,减少到a[k+1]就好呀
总结一下就是,我们对于更改a[k],原因变式a[k]有一些约束条件,而这个条件的边界便是原系列的值,我们本着贪心的思想,在满足条件的情况下尽可能产生较小的花费
这样我们用f[i][j]代表把前i个数排成单调不下降序列,其a[i]调整到原数列的第j小数,
f[i][j] min(f[i -1 ][k]) + cost(a[i] - > b[j]) (1 <= k <= j)
我们每次转移的时候保留一个最小值即可,实现O(1)的转移
下面回到正题:
此题是单调序列呀,这怎么搞?
a[i] < a[i + 1] <=> a[i] <= a[i + 1] - 1 <=> a[i] - i <= a[i + 1] - (i + 1) <=> b[i] <= b[i + 1]
构造b数列,问题转化为上面的那个问题
[Code]
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 3000 + 500;
int n, p, k;
int a[N], b[N];
long long f[N][N], g[N][N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i] -= i;
b[i] = a[i];
}
sort(b + 1, b + n + 1);
memset(f, 110, sizeof(f));
memset(g, 110, sizeof(g));
for(int i = 1; i <= n; i++)
{
f[1][i] = abs(a[1] - b[i]);
g[1][i] = min(g[1][i - 1], f[1][i]);
}
for(int i = 2; i <= n; i++)
for(int j = 1; j <= n; j++)
{
f[i][j] = g[i - 1][j] + abs(a[i] - b[j]);
g[i][j] = min(g[i][j -1], f[i][j]);
}
long long ans = g[n][1];
for(int i = 1; i <= n ; i++)
ans = min(ans, f[n][i]);
cout<<ans;
return 0;
}
C - Couple Cover
[Problem]
给定n(1e6)个数,每次取出 (i, j), 询问满足a[i] * a[j] >= p 的对数有多少? 共用3e6组询问, 读入数据保证小于等于3e6
[Solution]
很玄学的复杂度,M^1.5竟然不超时。。。。
[Code]
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 3000000 + 500;
#define inf 3000000
int n, m, p, k, top;
long long num[N], f[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
num[x]++;
}
for(int i = 1; i <= inf; i++)
if (num[i])
for(int j = 1; j <= i; j++)
if (num[j])
{
if (i * j > inf)
break;
if (i == j)
f[i * j] += num[i] * (num[i] - 1);
else
f[i * j] += num[i] * num[j] * 2;
}
for(int i = 2; i <= inf; i++)
f[i] = f[i] + f[i - 1];
scanf("%d", &m);
long long ans = 1LL * n * (n - 1);
while(m--)
{
int x;
scanf("%d", &x);
x--;
printf("%I64d\n",ans - f[x]);
}
return 0;
}
D Boredom
[Solution]
很裸的递推
[Code]
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100000 + 500;
int n, p, k;
long long num[N], f[N];
int main()
{
//freopen("b.in", "r", stdin);
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
num[x]++;
}
f[0] = 0;
f[1] = num[1];
long long ans = 0;
for(int i = 1; i <= 100000 + 1; i++)
{
f[i] = max(f[i - 1], f[i - 2] + num[i] * i);
ans = max(ans, f[i]);
}
cout<<ans;
return 0;
}
[Summary]
这次的题目比较少,恐怕自己现在就dp的bug率很低吧。。。。。