10.16 NOIP模拟赛 期望斜率优化 + 模拟 + 状压

来源:互联网 发布:js面向对象的思想 编辑:程序博客网 时间:2024/06/08 12:16

游戏 (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了…啊被罚唱歌.

原创粉丝点击