游戏 (game.cpp/c/pas)
【题目描述】
有n 个数,编号从1 到n。现在把n 个数分成k 组编号为1 到k,使得每组内的数必须
连续,组与组之间不能相交并且每个数必须属于一个组。
游戏进行的过程如下:
1. 如果n 个数都已经获得了,游戏结束。否则,找到编号最小没有全部获得的组X。
2. 游戏系统会给一个空的盒子,对于组X 中已经获得的数i,将ti 张写着数i 的卡片放
入盒子中,对于组X 中最小的没有获得的数j,将tj 张写着数j 的卡片放入盒子中。
3. 随机从盒子中抽取一张卡片,表示当前获得的数字,然后等待1 小时的冷却时间后
跳转到过程1。
现在需要确定一个最好的分组,使得这个游戏期望结束的时间最小。
【输入格式】
第一行两个整数n,k。
第二行n 个整数,第i 个整数为ti。
【输出格式】
一行,游戏结束的最小期望时间,保留小数点后两位。
【样例输入】
4 2
100 3 5 7
【样例输出】
5.74
【数据范围】
对于30%的数据, 1 <= n <= 20。
对于100%的数据,1 <= n <= 200000, 1 <= k <= min(50, n),1 <= ti <= 100000。
【样例解释】
分组方式为{100},{3,5,7}。
这道题一开始感觉很难的样子, 不过仔细推算一下发现是非常naive的斜率优化.
首先对于每一段中的一个i来说, 每一个i的贡献是i到这一段开头的sum, 由于每一次花费是1h, 所以期望贡献是 sum / ti(每次是1h, 抽到的概率是 ti / sum, 所以1/(ti/sum)就是期望抽到的时间). 假设j+1到i是一段, 那么这一段每一个的贡献就是:
sigma (sum[x1] - sum[j]) / t[x1] (j+1<=x1<=i)
sum[x1] 可以用一个s1数组维护出来, sum[j] / t[x1]也可以维护一个1/t[x1]的前缀和. 乘以sum[j]就得到了要减的数.
所以
dp[i] = dp[j] + s1[i] - s1[j] - (s2[i] - s2[j]) * sum[j];
斜率优化即可.
#include<stdio.h>
const int maxn = 200005;
int n, k, q[maxn];
double dp[maxn][51], sum[maxn], s1[maxn], s2[maxn], t[maxn];
inline double up(int j1, int k1, int p, int i){
return dp[j1][p - 1] - dp[k1][p - 1] - s1[j1] + s1[k1] + s2[j1] * sum[j1] - s2[k1] * sum[k1];
}
inline double down(int j1, int k1){
return sum[j1] - sum[k1];
}
int main(){
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i){
scanf("%lf", &t[i]);
sum[i] = t[i] + sum[i - 1];
s1[i] = s1[i - 1] + sum[i] / t[i];
s2[i] = s2[i - 1] + 1.0 / t[i];
}
for(int i = 1; i <= n; ++i)
dp[i][1] = s1[i];
for(int j = 2; j <= k; ++j){
int h = 1, t = 0;
q[++t] = 0;
for(int i = 1; i <= n; ++i){
while(h < t && up(q[h + 1], q[h], j, i) <= s2[i] * down(q[h + 1], q[h])) ++h;
dp[i][j] = dp[q[h]][j - 1] + s1[i] - s1[q[h]] - (s2[i] - s2[q[h]]) * sum[q[h]];
while(h < t && up(q[t - 1], q[t], j, i) * down(q[t], i) >= up(q[t], i, j, i) * down(q[t - 1], q[t])) --t;
q[++t] = i;
}
}
printf("%0.2lf\n", dp[n][k]);
}
开关灯 (lamp.cpp/c/pas)
【题目描述】
有n个灯,初始时都是不亮的状态,每次你可以选择一个某一个灯,不妨记为x,所有满足和x距离不超过k的灯的状态都将被翻转,选择第i个灯的代价记为c[i],问最终所有灯都是亮的状态的最小花费。
【输入格式】
输入有两行,第一行包含两个正整数n , k。
第二行包含n个整数,分别表示c[i]。
【输出格式】
一行,表示最小花费。
【样例输入】
3 1
1 1 1
【样例输出】
1
【数据范围】
20%的数据保证:1 <= N <= 20。
100%的数据保证:1 <= N <= 10000 , 0 <= k <= 1000 , 0 <= c[i] <= 1000000000。
很明显当前选的不能跟之前选的有重叠.
O(n)模拟即可.
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long dnt;
const int maxn = 10005;
int n, k;
dnt f[maxn], c[maxn], ans;
int main(){
freopen("lamp.in", "r", stdin);
freopen("lamp.out", "w", stdout);
ans = -1;
memset(f, -1, sizeof(f));
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) scanf("%I64d", &c[i]);
for(int i = 1; i <= n; ++i){
if(i - k <= 1) f[i] = c[i];
if(i - 2 * k - 1 >= 1)
if(~f[i - 2 * k - 1])
f[i] = f[i - 2 * k - 1] + c[i];
if(~f[i] && i + k >= n){
if(ans == -1) ans = f[i];
else ans = min(ans, f[i]);
}
}
printf("%I64d\n", ans);
}
工作 (work.pas/cpp/c)
【题目描述】
有N件事,每件事有两个属性a,b,现在你要以某种顺序做完这N件事,考虑这个人目前做的事情是i,他做的前一件事是j,那么他做这件事的代价就是(a[i] | a[j]) – (a[i] & a[j]),如果前面没有做事,那么代价就是a[i],但是事情总有轻重缓急之分,按原本顺序的事i最多能推迟到做完任意件紧接着事i之后的事j,i < j <= i + b[i]后做,即原本顺序的事k,k > i + b[i] ,不能在事i之前完成。
【输入格式】
输入的第一行是一个整数N,表示要做N件事。
接下来N行,每行两个数,表示 a[i]和b[i]。
【输出格式】
输出一个整数表示最小可能的最大传递时间
【样例输入】
2
5 1
4 0
【样例输出】
5
【数据范围】
20%的数据保证:1 <= N <= 15。
100%的数据保证:1 <= N , a[i] <= 1000 , 1 <= b[i] <= 7。
f(i,j,k)表示前i-1人都吃过饭,j表示i与i之后7人的吃饭情况,k表示上一个吃饭的人与i的相对位置
转移
若j&1==1
f(i,j,k)->f(i+1,j>>1,k-1)
否则枚举j集合内没吃饭的人
f(i,j,k)+cal(i+k,i+l)->f(i,j+bin[l],l).
dp刷表法更新状态.
#include<stdio.h>
#include<algorithm>
using namespace std;
const int inf = 1e9;
const int maxn = 1005;
int n, ans;
int a[maxn], b[maxn], f[maxn][(1 << 8)][16], pw[9];
int calc(int x, int y){
if(!x) return 0;
return (a[x] | a[y]) - (a[x] & a[y]);
}
int main(){
freopen("work.in", "r", stdin);
freopen("work.out", "w", stdout);
ans = inf;
pw[0] = 1;
for(int i = 1; i <= 20; ++i) pw[i] = pw[i - 1] << 1;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d%d", &a[i], &b[i]);
for(int i = 1; i <= n + 1; ++i)
for(int j = 0; j < pw[8]; ++j)
for(int k = 0; k <= 15; ++k)
f[i][j][k] = inf;
f[1][0][7] = 0;
for(int i = 1; i <= n; ++i)
for(int j = 0; j < pw[8]; ++j)
for(int k = 0; k <= 15; ++k)
if(f[i][j][k] < inf){
if(j & 1) f[i + 1][j >> 1][k - 1] = min(f[i + 1][j >> 1][k - 1], f[i][j][k]);
else{
int lim = inf;
for(int l = 0; l <= 7; ++l)
if(!(pw[l] & j)){
if(i + l > lim) break;
lim = min(lim, i + l + b[i + l]);
f[i][j + pw[l]][l + 8] = min(f[i][j + pw[l]][l + 8], f[i][j][k] + calc(i + l, i + k - 8));
}
}
}
for(int i = -8; i <= -1; ++i)
ans = min(ans, f[n + 1][0][i + 8]);
printf("%d\n", ans);
}
今日MLE了…啊被罚唱歌.