游戏
有n个数,编号从1到n。现在把n个数分成k组编号为1到k,使得每组内的数必须连续,组与组之间不能相交并且每个数必须属于一个组。
游戏进行的过程如下:
1. 如果n个数都已经获得了,游戏结束。否则,找到编号最小没有全部获得的组X。
2. 游戏系统会给一个空的盒子,对于组X中已经获得的数i,将ti张写着数i的卡片放入盒子中,对于组X中最小的没有获得的数j,将tj张写着数j的卡片放入盒子中。
3. 随机从盒子中抽取一张卡片,表示当前获得的数字,然后等待1小时的冷却时间后跳转到过程1。
现在需要确定一个最好的分组,使得这个游戏期望结束的时间最小。
对于100%的数据,1 <= n <= 200000, 1 <= k <= min(50, n),1 <= ti <= 100000。
题解
对于每一组的时间很好推,就是$\sum_{i=1}^{j} \frac{sum_{i}}{t_{i}}$
f[i][j]表示前i个数字分成j组的最小时间,很容易得到dp方程$dp[i][j]=min(dp[k][j-1]+\sum_{p=k+1}^{i} \frac{sum_{p}-sum_{k}}{t_{p}})$
化简$f[k][j-1]+\sum_{p=k+1}^{i} \frac{sum_{p}}{t_{p}}-\sum_{p=k+1}^{i} \frac{sum_{k}}{t_{p}}$
设$f_{i}=\sum_{p=1}^{i} \frac{sum_{p}}{t_{p}},g_{i}=\sum_{p=1}^{i} \frac{1}{t_{p}}$
所以$dp[i][j]=min(dp[k][j-1]+f[i]-f[k]-sum[k]*(g[i]-g[k]))$
然后就是斜率优化的一些套路了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=200005;
int n,m;
int h,t,q[maxn];
double T[maxn],sum[maxn],f[maxn],g[maxn];
double dp[55][maxn];
double slope(int k,int p,int j){
return (dp[j][p]-f[p]+sum[p]*g[p]-dp[j][k]+f[k]-sum[k]*g[k])/(sum[p]-sum[k]);
}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lf",&T[i]),sum[i]=sum[i-1]+T[i];
for(int i=1;i<=n;i++){
f[i]=f[i-1]+sum[i]/T[i];
g[i]=g[i-1]+1.0/T[i];
}
for(int i=1;i<=n;i++) dp[1][i]=f[i];
for(int j=2;j<=m;j++){
h=1,t=0;
q[++t]=0;
for(int i=1;i<=n;i++){
while(h<t&&slope(q[h],q[h+1],j-1)<g[i]) h++;
int p=q[h];
dp[j][i]=dp[j-1][p]+f[i]-f[p]-sum[p]*(g[i]-g[p]);
while(h<t&&slope(q[t-1],q[t],j-1)>slope(q[t],i,j-1)) t--;
q[++t]=i;
}
}
printf("%.2lf",dp[m][n]);
}
开关灯
有n个灯,初始时都是不亮的状态,每次你可以选择一个某一个灯,不妨记为x,所有满足和x距离不超过k的灯的状态都将被翻转,选择第i个灯的代价记为c[i],问最终所有灯都是亮的状态的最小花费。
1 <= N <= 10000 , 0 <= k <= 1000 , 0 <= c[i] <= 1000000000。
题解
这可能是最简单的一道,可以知道一个灯最多动一次。
通过感性分析得到翻转区间不能重叠,所以就over了。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=10005;
const ll inf=100000000000000ll;
int n,k;
ll ans,c[maxn];
template<class T>inline void read(T &x){
x=0;int f=0;char ch=getchar();
while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x = f ? -x : x ;
}
ll get(int s){
ll ret=0;
for(;s<=n;s+=2*k+1) ret+=c[s];
s-=2*k+1;
if(s+k<n) return inf;
return ret;
}
int main(){
freopen("lamp.in","r",stdin);
freopen("lamp.out","w",stdout);
read(n);read(k);
for(int i=1;i<=n;i++) read(c[i]);
ans=inf;
for(int i=1;i<=k+1;i++) ans=min(ans,get(i));
printf("%lld",ans);
}
