3.动态规划专题
动态规划专题
\(A\) CF494C Helping People
-
观察到区间只有相离或包含关系,类似线段树的管辖区间,考虑将其构成树形关系。为方便代码书写,将原来的森林构成一棵树,即增加一个区间 \(l_{q+1}=1,r_{q+1}=n,p_{q+1}=0\) 。
-
由于对于一个区间 \([l,r]\) 的最大值在经历任意次操作后,一定有 \(\max\limits_{k=l}^{r} \{ a_{k} \} \le \max\limits_{k=l}^{r} \{ a_{k}' \} \le \max\limits_{k=l}^{r} \{ a_{k} \}+q\) ,故可以据此优化空间。设 \(f_{x,i}\) 表示第 \(x\) 个节点对应的区间的最大值 \(\le \max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i\) 的概率,状态转移方程为 \(\begin{cases} f_{x,i}=(1-p_{x}) \times \prod\limits_{y \in Son(x)}f_{y,\min(q+1,\max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i-\max\limits_{k=y_{l}}^{y_{r}} \{ a_{k} \})} & i=0 \\ f_{x,i}=p_{x} \times \prod\limits_{y \in Son(x)}f_{y,\min(q+1,\max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i-\max\limits_{k=y_{l}}^{y_{r}} \{ a_{k} \}-1)}+(1-p_{x}) \times \prod\limits_{y \in Son(x)}f_{y,\min(q+1,\max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i-\max\limits_{k=y_{l}}^{y_{r}} \{ a_{k} \})} & i \ne 0 \end{cases}\) 。
-
假设区间按照左端点升序,右端点降序的方式排序。最终,有 \(f_{1,0} \times \max\limits_{k=1}^{n} \{ a_{k} \}+\sum\limits_{i=1}^{q+1}(f_{1,i}-f_{1,i-1}) \times (i+ \max\limits_{k=1}^{n} \{ a_{k} \})\) 即为所求。
点击查看代码
struct edge { int nxt,to; }e[5010]; struct node { int l,r,maxx; double p; }b[5010]; int head[5010],a[100010],fmaxx[100010][20],cnt=0; double f[5010][5010]; bool cmp(node a,node b) { return (a.l==b.l)?(a.r>b.r):(a.l<b.l); } void init(int n) { for(int j=1;j<=log2(n);j++) { for(int i=1;i<=n-(1<<j)+1;i++) { fmaxx[i][j]=max(fmaxx[i][j-1],fmaxx[i+(1<<(j-1))][j-1]); } } } int query(int l,int r) { int t=log2(r-l+1); return max(fmaxx[l][t],fmaxx[r-(1<<t)+1][t]); } void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int q) { double sum1,sum2=1; for(int i=head[x];i!=0;i=e[i].nxt) { dfs(e[i].to,q); sum2*=f[e[i].to][min(q,b[x].maxx+0-b[e[i].to].maxx)]; } f[x][0]=(1-b[x].p)*sum2; for(int i=1;i<=q;i++) { sum1=sum2=1; for(int j=head[x];j!=0;j=e[j].nxt) { sum1*=f[e[j].to][min(q,b[x].maxx+i-b[e[j].to].maxx-1)]; sum2*=f[e[j].to][min(q,b[x].maxx+i-b[e[j].to].maxx)]; } f[x][i]=b[x].p*sum1+(1-b[x].p)*sum2; } } int main() { int n,q,i,j; double ans=0; cin>>n>>q; for(i=1;i<=n;i++) { cin>>a[i]; fmaxx[i][0]=a[i]; } init(n); for(i=1;i<=q;i++) { cin>>b[i].l>>b[i].r>>b[i].p; b[i].maxx=query(b[i].l,b[i].r); } q++; b[q].l=1; b[q].r=n; b[q].p=0; b[q].maxx=query(1,n); sort(b+1,b+1+q,cmp); for(i=1;i<=q;i++) { for(j=i-1;j>=1;j--) { if(b[j].l<=b[i].l&&b[i].r<=b[j].r) { add(j,i); break; } } } dfs(1,q); for(i=0;i<=q;i++) { ans+=(f[1][i]-(i==0?0:f[1][i-1]))*(i+b[1].maxx); } printf("%.9lf\n",ans); return 0; }
\(B\) CF922E Birds
-
观察到 \(w\) 极大,若使用正常的背包空间会爆炸。
-
依据 AT_dp_e Knapsack 2 的经验,考虑将背包“反”着用。设 \(f_{i,j}\) 表示到第 \(i\) 棵树时一共召唤了 \(j\) 只小鸟时剩余的最大魔力值,状态转移方程为 \(f_{i,j}=\min(\max\limits_{k=0}^{\min(j,c_{i})} \{ f_{i-1,j-k}-cost_{i} \times k+x \},w+b \times j)\) ,边界为 \(f_{0,0}=w\) 。
-
最终,有 \(\max\limits_{i=0}^{\sum\limits_{j=1}^{n}c_{j}} \{ [f_{n,i} \ge 0] \times i \}\) 即为所求。
点击查看代码
ll c[10010],sum[10010],cost[10010],f[1010][10010]; int main() { ll n,w,b,x,ans=0,i,j,k; cin>>n>>w>>b>>x; for(i=1;i<=n;i++) { cin>>c[i]; sum[i]=sum[i-1]+c[i]; } for(i=1;i<=n;i++) { cin>>cost[i]; } memset(f,-0x3f,sizeof(f)); f[0][0]=w; for(i=1;i<=n;i++) { for(j=0;j<=sum[i];j++) { for(k=0;k<=min(j,c[i]);k++) { if(f[i-1][j-k]-cost[i]*k>=0) { f[i][j]=max(f[i][j],f[i-1][j-k]-cost[i]*k+x); } } f[i][j]=min(f[i][j],w+b*j); } } for(i=sum[n];i>=0;i--) { if(f[n][i]>=0) { ans=i; break; } } cout<<ans<<endl; return 0; }
\(C\) CF285E Positions in Permutations
-
若第 \(i\) 个位置是好的,则有 \(p_{i}=i-1\) 或 \(p_{i}=i+1\) 。
-
设 \(f(m)\) 表示 \(n\) 个位置中恰好有 \(m\) 个位置是好的的排列数, \(g(m)\) 表示 \(n\) 个位置中至少有 \(m\) 个位置是好的的排列数。已知 \(g(m)= \sum\limits_{i=m}^{n} \dbinom{i}{m}f(i)\) ,由二项式反演有 \(f(m)= \sum\limits_{i=m}^{n} (-1)^{i-m} \dbinom{i}{m}g(i)\) 。
-
设 \(f_{i,j,0/1,0/1}\) 表示前 \(i\) 位,有 \(j\) 个好位置,且 \(i\) 不选/选, \(i+1\) 不选/选的方案数,状态转移方程为 \(\begin{cases} f_{i,j,0,0}=f_{i-1,j-1,0,0}+f_{i-1,j,0,0}+f_{i-1,j,1,0} \\ f_{i,j,1,0}=f_{i-1,j-1,0,1}+f_{i-1,j,0,1}+f_{i-1,j,1,1} \\ f_{i,j,0,1}=f_{i-1,j-1,0,0}+f_{i-1,j-1,1,0} \\ f_{i,j,1,1}=f_{i-1,j-1,0,1}+f_{i-1,j-1,1,1} \end{cases}\) ,边界为 \(f_{1,0,0,0}=f_{1,1,0,1}=1\) 。
-
最终,有 \(g(i)=A_{n-i}^{n-i}(f_{n,i,0,0}+f_{n,i,1,0})\) 。
点击查看代码
const ll p=1000000007; ll inv[1010],jc[1010],jc_inv[1010],f[1010][1010][3][3],g[1010]; ll A(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?jc[n]*jc_inv[n-m]%p:0; } ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0; } int main() { ll n,m,ans=0,i,j; cin>>n>>m; inv[1]=1; jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; for(i=2;i<=n;i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } f[1][0][0][0]=f[1][1][0][1]=1; for(i=2;i<=n;i++) { for(j=0;j<=i;j++) { f[i][j][0][0]=(f[i-1][j][0][0]+f[i-1][j][1][0])%p; f[i][j][1][0]=(f[i-1][j][0][1]+f[i-1][j][1][1])%p; if(j-1>=0) { f[i][j][0][0]=(f[i][j][0][0]+f[i-1][j-1][0][0])%p; f[i][j][1][0]=(f[i][j][1][0]+f[i-1][j-1][0][1])%p; f[i][j][0][1]=(f[i-1][j-1][0][0]+f[i-1][j-1][1][0])%p; f[i][j][1][1]=(f[i-1][j-1][0][1]+f[i-1][j-1][1][1])%p; } } } for(i=0;i<=n;i++) { g[i]=((f[n][i][0][0]+f[n][i][1][0])%p)*A(n-i,n-i,p)%p; } for(i=m;i<=n;i++) { ans=(ans+(((i-m)%2==0)?1:-1)*C(i,m,p)*g[i]%p+p)%p; } cout<<ans<<endl; return 0; }
\(D\) CF1168D Anagram Paths
\(E\) CF573D Bear and Cavalry
-
考虑如果没有骑士不能骑自己的马的限制,那么就转换成了 普及模拟2 T2 内积 的结论,将 \(w,h\) 分别排序后,有 \(\sum\limits_{i=1}^{n}w_{i}h_{i}\) 即为所求。
-
结论貌似有点多,详见 @wang54321 的 冲刺NOIP2024专题之dp专题 Bear and Cavalry 。
-
对于每次修改,至多对左右两边各 \(3\) 个产生影响,暴力修改即可。
-
设 \(f_{i}\) 表示当前分配到第 \(i\) 个士兵时的最大力量,状态转移方程为 \(f_{i}=\max \begin{cases} f_{i-1}+w_{i}h_{i} \\ f_{i-2}+w_{i}h_{i-1}+w_{i-1}h_{i} \\ f_{i-3}+\max(w_{i}h_{i-1}+w_{i-1}h_{i-2}+w_{i-2}h_{i},w_{i}h_{i-2}+w_{i-1}h_{i}+w_{i-2}h_{i-1}) \end{cases}\) 。
点击查看代码
struct node { ll w,id; }a[300010],b[300010]; ll posa[300010],posb[300010],ban[300010],w[300010][4],f[300010]; bool cmp(node a,node b) { return (a.w==b.w)?(a.id<b.id):(a.w<b.w); } int main() { ll n,m,l,r,x,y,i,j; cin>>n>>m; memset(w,-0x3f,sizeof(w)); for(i=1;i<=n;i++) { cin>>a[i].w; a[i].id=i; } for(i=1;i<=n;i++) { cin>>b[i].w; b[i].id=i; } sort(a+1,a+1+n,cmp); sort(b+1,b+1+n,cmp); for(i=1;i<=n;i++) { posa[a[i].id]=posb[b[i].id]=i; } for(i=1;i<=n;i++) { ban[i]=posb[a[i].id]; } for(i=1;i<=n;i++) { if(i-1>=0&&ban[i]!=i) { w[i][1]=max(w[i][1],a[i].w*b[i].w); } if(i-2>=0&&ban[i]!=i-1&&ban[i-1]!=i) { w[i][2]=max(w[i][2],a[i].w*b[i-1].w+a[i-1].w*b[i].w); } if(i-3>=0&&ban[i]!=i-2&&ban[i-1]!=i&&ban[i-2]!=i-1) { w[i][3]=max(w[i][3],a[i].w*b[i-2].w+a[i-1].w*b[i].w+a[i-2].w*b[i-1].w); } if(i-3>=0&&ban[i]!=i-1&&ban[i-1]!=i-2&&ban[i-2]!=i) { w[i][3]=max(w[i][3],a[i].w*b[i-1].w+a[i-1].w*b[i-2].w+a[i-2].w*b[i].w); } } for(j=1;j<=m;j++) { cin>>x>>y; swap(ban[posa[x]],ban[posa[y]]); l=max(1ll,posa[x]-3); r=min(n,posa[x]+3); for(i=l;i<=r;i++) { w[i][1]=w[i][2]=w[i][3]=-0x3f3f3f3f; if(i-1>=0&&ban[i]!=i) { w[i][1]=max(w[i][1],a[i].w*b[i].w); } if(i-2>=0&&ban[i]!=i-1&&ban[i-1]!=i) { w[i][2]=max(w[i][2],a[i].w*b[i-1].w+a[i-1].w*b[i].w); } if(i-3>=0&&ban[i]!=i-2&&ban[i-1]!=i&&ban[i-2]!=i-1) { w[i][3]=max(w[i][3],a[i].w*b[i-2].w+a[i-1].w*b[i].w+a[i-2].w*b[i-1].w); } if(i-3>=0&&ban[i]!=i-1&&ban[i-1]!=i-2&&ban[i-2]!=i) { w[i][3]=max(w[i][3],a[i].w*b[i-1].w+a[i-1].w*b[i-2].w+a[i-2].w*b[i].w); } } l=max(1ll,posa[y]-3); r=min(n,posa[y]+3); for(i=l;i<=r;i++) { w[i][1]=w[i][2]=w[i][3]=-0x3f3f3f3f; if(i-1>=0&&ban[i]!=i) { w[i][1]=max(w[i][1],a[i].w*b[i].w); } if(i-2>=0&&ban[i]!=i-1&&ban[i-1]!=i) { w[i][2]=max(w[i][2],a[i].w*b[i-1].w+a[i-1].w*b[i].w); } if(i-3>=0&&ban[i]!=i-2&&ban[i-1]!=i&&ban[i-2]!=i-1) { w[i][3]=max(w[i][3],a[i].w*b[i-2].w+a[i-1].w*b[i].w+a[i-2].w*b[i-1].w); } if(i-3>=0&&ban[i]!=i-1&&ban[i-1]!=i-2&&ban[i-2]!=i) { w[i][3]=max(w[i][3],a[i].w*b[i-1].w+a[i-1].w*b[i-2].w+a[i-2].w*b[i].w); } } f[0]=0; for(i=1;i<=n;i++) { f[i]=f[i-1]+w[i][1]; if(i-2>=0) { f[i]=max(f[i],f[i-2]+w[i][2]); } if(i-3>=0) { f[i]=max(f[i],f[i-3]+w[i][3]); } } cout<<f[n]<<endl; } return 0; }
\(F\) CF1392H ZS Shuffles Cards
-
设 \(f_{i}\) 表示有 \(i\) 张数字牌没进入 \(S\) ,即 \(S\) 中只有 \(n-i\) 张数字牌时的期望轮数,有 \(f_{i}= \frac{i}{i+m}f_{i-1}+ \frac{m}{i+m}(f_{i}+1)\) ,解得 \(f_{i}=f_{i-1}+\frac{m}{i}\) ,边界为 \(f_{0}=1\) 。
-
由于每一张数字牌在
joker
牌前被抽中的概率为 \(\frac{1}{m+1}\) ,故每一轮的期望牌数为 \(1+ \sum\limits_{i=1}^{n} \frac{1}{m+1}= 1+\frac{n}{m+1}\) 。 -
最终,有 \(f_{n}(1+\frac{n}{m+1})\) 即为所求。
点击查看代码
const ll p=998244353; ll f[2000010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { ll n,m,i; cin>>n>>m; f[0]=1; for(i=1;i<=n;i++) { f[i]=(f[i-1]+qpow(i,p-2,p)*m%p)%p; } cout<<f[n]*((qpow(m+1,p-2,p)*n%p+1)%p)%p<<endl; return 0; }
\(G\) CF838C Future Failure
- 貌似需要子集卷积等比较高深的东西,所以被 \(miaomiao\) 毙了,说我们不用写了。
- 组合数奇偶判断详见 2024寒假年后集训日记 2.16 做题纪要 luogu P1869 愚蠢的组合数 。
\(H\) CF536D Tavas in Kansas
-
分别以 \(s,t\) 为起点,预处理出到其他所有点的最短距离 \(dis_{s,k},dis_{t,k}\) 。
-
由于只关注 \(dis\) 的相对大小关系,故可以将 \(dis_{s,k},dis_{t,k}\) 分别离散化成 \(x_{k},y_{k}\) ,形成一个 \(n \times n\) 的矩阵,此时
Tavas
只能取从上到下的若干行,Nafas
只能取从左到右的若干列。 -
设 \(f_{i,j,0/1}\) 表示当前轮到
Tavas
/Nafas
取,Tavas
取到了第 \(i\) 行,Nafas
取到了第 \(j\) 列时Tavas
的权值减去Nafas
的权值的最大/最小值,状态转移方程为 \(f_{i,j,0}=\begin{cases} f_{i+1,j,0} & val(1,i,j,i,n)=0 \\ \max(f_{i+1,j,0},f_{i+1,j,1})+val(2,i,j,i,n) & val(1,i,j,i,n) \ne 0 \end{cases},f_{i,j,1}=\begin{cases} f_{i,j+1,0} & val(1,i,j,n,j)=0 \\ \min(f_{i,j+1,0},f_{i,j+1,1})-val(2,i,j,n,j) & val(1,i,j,n,j) \ne 0 \end{cases}\) ,其中 \(val(1,x_{1},y_{1},x_{2},y_{2})\) 表示左上角为 \(x_{1},y_{1}\) ,右下角为 \(x_{2},y_{2}\) 的矩形的点的权值之和, \(val(2,x_{1},y_{1},x_{2},y_{2})\) 表示左上角为 \(x_{1},y_{1}\) ,右下角为 \(x_{2},y_{2}\) 的矩形的点的个数之和。 -
最终,有 \(f_{1,1,0}<0\) 时输出
Cry
, \(f_{1,1,0}=0\) 时输出Flowers
, \(f_{1,1,0}>0\) 时输出Break a heart
。点击查看代码
struct node { ll nxt,to,w; }e[200010]; ll head[2010],dis[2010],vis[2010],p[2010],diss[2010],x[2010],y[2010],a[2010][2010],b[2010][2010],sum1[2010][2010],sum2[2010][2010],f[2010][2010][2],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } ll query(ll a[],ll x) { return lower_bound(a+1,a+1+a[0],x)-a; } void dijkstra(ll s,ll n,ll a[]) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<ll,ll> >q; ll x,i; dis[s]=0; q.push(make_pair(0,-s)); while(q.empty()==0) { x=-q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; q.push(make_pair(-dis[e[i].to],-e[i].to)); } } } } for(i=1;i<=n;i++) { diss[i]=dis[i]; } sort(diss+1,diss+1+n); diss[0]=unique(diss+1,diss+1+n)-(diss+1); for(i=1;i<=n;i++) { a[i]=query(diss,dis[i]); } } ll work(ll x1,ll y1,ll x2,ll y2,ll sum[2010][2010]) { return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1]; } int main() { ll n,m,s,t,u,v,w,i,j; cin>>n>>m>>s>>t; for(i=1;i<=n;i++) { cin>>p[i]; } for(i=1;i<=m;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dijkstra(s,n,x); dijkstra(t,n,y); for(i=1;i<=n;i++) { a[x[i]][y[i]]+=p[i]; b[x[i]][y[i]]++; } for(i=1;i<=n+1;i++) { for(j=1;j<=n+1;j++) { sum1[i][j]=sum1[i-1][j]+sum1[i][j-1]-sum1[i-1][j-1]+a[i][j]; sum2[i][j]=sum2[i-1][j]+sum2[i][j-1]-sum2[i-1][j-1]+b[i][j]; } } for(i=n+1;i>=1;i--) { for(j=n+1;j>=1;j--) { if(i!=n+1||j!=n+1) { f[i][j][0]=(work(i,j,i,n,sum2)==0)?f[i+1][j][0]:max(f[i+1][j][0],f[i+1][j][1])+work(i,j,i,n,sum1); f[i][j][1]=(work(i,j,n,j,sum2)==0)?f[i][j+1][1]:min(f[i][j+1][0],f[i][j+1][1])-work(i,j,n,j,sum1); } } } if(f[1][1][0]<0) { cout<<"Cry"<<endl; } if(f[1][1][0]==0) { cout<<"Flowers"<<endl; } if(f[1][1][0]>0) { cout<<"Break a heart"<<endl; } return 0; }
\(I\) CF1628D1 Game on Sum (Easy Version)
- CF1628D1 Game on Sum (Easy Version)
-
设 \(x_{i}\) 表示第 \(i\) 轮时
Alice
选择的数。 -
设 \(f_{i,j}\) 表示已经进行了 \(i\) 轮,且使用了 \(j\) 次加法时的最大得分,状态转移方程为 \(f_{i,j}= \max \{ \min(f_{i-1,j}-x_{i},f_{i-1,j-1}+x_{i}) \}=\frac{f_{i-1,j}+f_{i-1,j-1}}{2}\) ,边界为 \(\begin{cases} f_{i,0 \sim \infty}=0 & i=0 \\ f_{i,0}=0, f_{i,i}=i \times k & i \ne 0 \end{cases}\) 。
- 由于
Bob
想让结果尽可能小,所以有 \(f_{i,j}= \min(f_{i-1,j}-x_{i},f_{i-1,j-1}+x_{i})\) 。 - 由于
Alice
想让结果尽可能大,所以会让 \(\min(f_{i-1,j}-x_{i},f_{i-1,j-1}+x_{i})\) 取到最大值,即 \(f_{i-1,j}-x_{i}=f_{i-1,j-1}+x_{i}\) 时,解得 \(x_{i}= \frac{f_{i-1,j}-f_{i-1,j-1}}{2}\) ,代入原式有 \(f_{i,j}=\frac{f_{i-1,j}+f_{i-1,j-1}}{2}\) 。
- 由于
-
由于
Bob
想让结果尽可能小,所以至多使用 \(m\) 次加法,故最终 \(f_{n,m}\) 即为所求。 -
另外,由于求解 \(f_{n,m}\) 的过程中只有加法和 \(\times \frac{1}{2}\) 运算,故可以将 \(k\) 缩小至 \(1\) 进行预处理 \(f_{n,m}\) ,询问时再扩大到 \(k\) ,即 \(f_{n,m} \times k\) 。
点击查看代码
const ll p=1000000007; ll f[2010][2010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { ll t,n,m,k,i,j; cin>>t; for(i=1;i<=2000;i++) { f[i][0]=0; f[i][i]=i; for(j=1;j<=i-1;j++) { f[i][j]=((f[i-1][j]+f[i-1][j-1])%p)*qpow(2,p-2,p)%p; } } for(i=1;i<=t;i++) { cin>>n>>m>>k; cout<<f[n][m]*k%p<<endl; } return 0; }
-
- luogu P7137 [THUPC2021 初赛] 切切糕
-
从贪心的角度分析,
Tinytree
的“优先选糕权”要尽量留给 \(a_{i}\) 较大的切糕,故需要先将 \(a\) 按照降序排序。 -
设第 \(i\) 块切糕
Kiana
切成的切糕大小为 \(x_{i}\) 和 \(a_{i}-x_{i}\) ,规定有 \(x_{i} \ge a_{i}-x_{i}\) 。 -
设 \(f_{i,j}\) 表示已经切了 \(i\) 块切糕,且使用了 \(j\) 次“优先选糕权”时
Tinytree
的最大总大小,状态转移方程为 \(f_{i,j}= \max \{ \min(f_{i-1,j}+a_{i}-x_{i},f_{i-1,j-1}+x_{i}),f_{i-1,j} \}=\max(\frac{f_{i-1,j}+f_{i-1,j-1}+a_{i}}{2},f_{i-1,j})\) ,边界为 \(\begin{cases} f_{i,0 \sim \infty}=0 & i=0 \\ f_{i,0}=0, f_{i,i}=\frac{\sum\limits_{j=1}^{i}a_{j}}{2} & i \ne 0 \end{cases}\) 。- 由于算出的 \(x_{i}\) 可能使 \(a_{i}-x_{i}<0\) 成立,故最后需要与 \(f_{i-1,j}\) 取 \(\max\) 。
-
由于
Tinytree
想让Kiana
的总大小尽可能小,所以一定会使用 \(m\) 次“优先选糕权”,使自己的总大小尽可能大,故最终 \(\sum\limits_{i=1}^{n}a_{i}-f_{n,m}\) 即为所求。点击查看代码
ll a[2510],sum[2510]; double f[2510][2510]; int main() { ll n,m,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } sort(a+1,a+1+n,greater<ll>()); for(i=1;i<=n;i++) { sum[i]=sum[i-1]+a[i]; } for(i=1;i<=n;i++) { f[i][0]=0; f[i][i]=1.0*sum[i]/2; for(j=1;j<=i-1;j++) { f[i][j]=max((f[i-1][j]+f[i-1][j-1]+1.0*a[i])/2,f[i-1][j]); } } printf("%.6lf",sum[n]-f[n][m]); return 0; }
-
- CF1628D2 Game on Sum (Hard Version)
- 观察到 \(f\) 的转移过程比较像杨辉三角的转移过程。
- 当 \(n=m\) 时,有 \(f_{n,m}=m \times k\) 即为所求。
- 当 \(n \ne m\) 时,
-
考虑计算 \(f_{i,i}\) 对 \(f_{n,m}\) 产生的贡献。
- 从 \(f_{i,i}\) 到 \(f_{n,m}\) 一共进行了 \(n-i\) 次 \(\times \frac{1}{2}\) 操作。
- 由 \(f_{i,j}\) 会向 \(f_{i+1,j}\) 和 \(f_{i+1,j+1}\) 进行转移,故等价于 \((i,j)\) 一次可以走到 \((i+1,j)\) 或 \((i+1,j+1)\) ,求不经过形如 \((x,x)\) 的点时从 \((i,i)\) 走到 \((n,m)\) 的方案数,即从 \((i+1,i)\) 走到 \((n,m)\) 的方案数 \(\dbinom{n-(i+1)}{m-i}\) 。
-
故 \(\sum\limits_{i=1}^{m}\frac{i \times k \times \binom{n-(i+1)}{m-i}}{2^{n-i}}\) 即为所求。
点击查看代码
const ll p=1000000007; ll jc[2000010],inv[2000010],jc_inv[2000010],a[2000010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0; } int main() { ll t,n,m,k,ans=0,i,j; cin>>t; jc[0]=jc_inv[0]=1; for(i=1;i<=1000000;i++) { a[i]=i; jc[i]=jc[i-1]*a[i]%p; } for(i=1000001;i<=2000000;i++) { a[i]=qpow(2,i-1000000-1,p); jc[i]=jc[i-1]*a[i]%p; } jc_inv[2000000]=qpow(jc[2000000],p-2,p); for(i=2000000-1;i>=1;i--) { jc_inv[i]=jc_inv[i+1]*a[i+1]%p; } for(i=1;i<=2000000;i++) { inv[i]=jc_inv[i]*jc[i-1]%p; } for(i=1;i<=t;i++) { cin>>n>>m>>k; ans=0; if(n==m) { cout<<m*k%p<<endl; } else { for(j=1;j<=m;j++) { ans=(ans+((j*k%p)*C(n-(j+1),m-j,p)%p)*inv[1000000+1+n-j]%p)%p; } cout<<ans<<endl; } } return 0; }
-
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18083512,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。