sm2025 模拟赛16 (2025.10.11)
文章目录
- T1 一个困难的问题
- T2 数字游戏
- T3 子序列
- T4 新生
T1 一个困难的问题
link
题意
给定一个 nnn 个数的序列 aaa ,每次操作可以任意选择 [l,r][l,r][l,r] ,将此区间中的数从小到大排序。
有由 0/10/10/1 构成的长为 nnn 的序列 bbb ,求若干次操作后 ∑i=1naibi\sum\limits_{i=1}^{n} a_i b_ii=1∑naibi 的最小值。
思路
首先直接排序太复杂了。贪心的会有一个答案下界:从后往前扫,每遇到一个 bi=1b_i=1bi=1 ,就在 [i,n][i,n][i,n] 之中找一个最小的还没被取 aaa 。
考虑证明,设取得的数原下标为 x1,x2,…,xkx_1,x_2,\dots ,x_kx1,x2,…,xk ,满足 bi=1b_i=1bi=1 的位置为 i1,i2,…,iki_1,i_2,\dots,i_ki1,i2,…,ik ,依次对区间 [i1,x1],[i2,x2],…,[ik,xk][i_1,x_1],[i_2,x_2],\dots,[i_k,x_k][i1,x1],[i2,x2],…,[ik,xk] 排序即可。
反思
直接将序列按 bi=0/1b_i=0/1bi=0/1 分成多段,进行段内排序并不是最优解并且难以解决。所以构造可以尝试从答案下界入手。
代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int a[maxn];
string st;
priority_queue<int>q;
int main(){freopen("difficult.in","r",stdin);freopen("difficult.out","w",stdout);int t; cin>>t;while(t--){int n; cin>>n;for(int i=1;i<=n;i++)cin>>a[i];cin>>st; st=" "+st;while(!q.empty()) q.pop();ll ans=0;for(int i=n;i>=1;i--){q.push(-a[i]);if(st[i]=='1'){ans-=q.top();q.pop();}}cout<<ans<<"\n";}return 0;
}
T2 数字游戏
link
博弈论dp →\rightarrow→ 类似题
博弈论dp 小记
思路
容易想到 dp ,设 fi,jf_{i,j}fi,j 表示游戏还剩 iii 轮,Bob 还剩 jjj 次 add
时的答案。转移为 fi,j=maxx∈[0,k]{min(fi−1,j−1+x,fi−1,j−x)}f_{i,j}=\max\limits_{x\in[0,k]} \{\min (f_{i-1,j-1}+x,f_{i-1,j}-x)\}fi,j=x∈[0,k]max{min(fi−1,j−1+x,fi−1,j−x)} 。考虑将 max 拆开,发现当 x=fi−1,j−fi−1,j−12x=\frac{f_{i-1,j}-f_{i-1,j-1}}{2}x=2fi−1,j−fi−1,j−1 ,fi,jf_{i,j}fi,j 取到最大值 fi−1,j−1+fi−1,j2\frac{f_{i-1,j-1}+f_{i-1,j}}{2}2fi−1,j−1+fi−1,j ,因为 0≤fi−1,j−fi−1,j−1≤2k0\le f_{i-1,j}-f_{i-1,j-1} \le 2k0≤fi−1,j−fi−1,j−1≤2k ,所以 xxx 一定可以可以取到这个值。
所以 fi,j=fi−1,j−1+fi−1,j2f_{i,j}=\frac{f_{i-1,j-1}+f_{i-1,j}}{2}fi,j=2fi−1,j−1+fi−1,j ,边界为 fi,0=0(0≤i≤n)f_{i,0}=0 (0 \le i \le n)fi,0=0(0≤i≤n) ,fi,i=i×k(0≤i≤m)f_{i,i}=i \times k (0\le i \le m)fi,i=i×k(0≤i≤m) 。复杂度 O(n,m)O(n,m)O(n,m) 。
考虑优化。可以打表发现规律。 考虑每个 fi,if_{i,i}fi,i 对 fn,mf_{n,m}fn,m 的贡献,相当于从 (i+1,i)(i+1,i)(i+1,i) 走到 (n,m)(n,m)(n,m) ,每次可以向下或向右下走,求方案数,再除以 2n−i2^{n-i}2n−i 即可。注意由于多次询问 kkk 不一样,由于 kkk 对计算过程影响不大,考虑将 fff 中的 ×k\times k×k 提出来最后直接对答案 ×k\times k×k 。这样复杂度线性,记得特判 n=mn=mn=m 的情况。
代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e6+5,mod=1e9+7,N=3e6;ll Pow_(ll x,ll y){ll s=1;while(y){if(y&1) (s*=x)%=mod;(x*=x)%=mod;y>>=1;}return s;
}ll fac[maxn],inv_fac[maxn];
ll C(ll m,ll n){return fac[m]*inv_fac[n]%mod*inv_fac[m-n]%mod;
}ll inv_2[maxn];
int main(){freopen("number.in","r",stdin);freopen("number.out","w",stdout);fac[0]=inv_fac[0]=inv_2[0]=1;for(int i=1;i<=N;i++){inv_2[i]=inv_2[i-1]*Pow_(2,mod-2)%mod;fac[i]=fac[i-1]*i%mod;inv_fac[i]=Pow_(fac[i],mod-2);}int t; cin>>t;while(t--){int n,m,k; cin>>n>>m>>k;if(n==m){cout<<1ll*n*k%mod<<"\n";continue;}if(!m){cout<<"0\n";continue;}ll ans=0;for(int i=1;i<=m;i++)(ans+=i*C(n-i-1,m-i)%mod*inv_2[n-i]%mod)%=mod; cout<<ans*k%mod<<"\n";}return 0;
}
T3 子序列
link
T4 新生
link