游戏
有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); }