Virtual NOIP II Day 3 总结

Sweetlemon

2018-11-01 15:52:01

Personal

### T1 平均数 $40$分:直接枚举所有连续子段,计算其平均值并加入数组中,对数组进行排序,再输出第$k$小元素即可。$\text{O}(n^2\log n)$。 $100$分:直接计算第$k$小元素是很困难的,我们考虑二分,即二分一个第$k$小元素的大小,二分可行的条件是存在至少$k$个连续子段的平均值大于等于$x$,则输出可行的最小解即可。 把上述模型数学化,即:有数列$a_1,a_2,a_3,\cdots,a_n$,设它的前缀和为$S_1,S_2,S_3,\cdots,S_n$。请验证是否存在$k$组$(i,j)(0\le i< j\le n)$,使$\frac{S_j-S_i}{j-i}\le x$。 在数学上,我们一般是如何求解$f(x)\le x_0$这类不等式的呢?通常,我们会进行移项,转化为$g(x)=f(x)-x_{0}\le 0$。 这里我们不妨也进行这样的转化。原不等式等价于$\frac{S_j-S_i}{j-i}-x\le 0$,即$\frac{(S_j-jx)-(S_i-ix)}{j-i}\le 0$。 接下来我们要进一步进行变换。令$a'_{i}=a_{i}-x$,设其前缀和为$S'_{i}=S_{i}-ix$,则上面的不等式又可以化为$\frac{S'_{j}-S'_{i}}{j-i}\le 0$。即我们通过把整个数列的所有数减去我们二分的平均值,把问题转化为,是否存在至少$k$个连续子段的均值(或和)非正。 认真观察上面的分式不等式,根据我们熟悉的技巧,这个不等式可以转化为$(S'_{j}-S'_{i})(j-i)\le 0$,这不就是我们熟悉的单调性表述式(单调不增)吗?于是,问题转化为,在新数列$\left\{ S' \right\}$中,是否有至少$k$个非正序对。用归并排序或树状数组即可简单解决(这里用归并排序更好,因为数列元素是浮点数,用树状数组需要额外做离散化)。 附上代码: ```cpp #include <cstdio> #include <cctype> #include <algorithm> #define MAXIOLOG 25 #define MAXN 100005 #define INF 1019260817 #define EPS 0.00003 //EPS是经过反复试验得出的值(逃 using namespace std; int n,k; int a[MAXN]; //原数组 double b[MAXN]; //调整后的数组 double tarr[MAXN]; //归并排序的临时数组 long long poss; //记得这里一定要用long long! int check(void); void merge_sort(int l,int r); //半开区间[l,r) int main(void){ int maxx=0,minx=INF; n=uread(),k=uread(); for (int i=1;i<=n;i++) a[i]=uread(); for (int i=1;i<=n;i++){ maxx=max(maxx,a[i]); minx=min(minx,a[i]); } double ll,rr; ll=minx-0.1; rr=maxx; //(ll,rr] while (rr-ll>EPS){ double mid=(ll+rr)/2.0; for (int i=1;i<=n;i++){ b[i]=double(a[i])-mid; } for (int i=1;i<=n;i++) b[i]+=b[i-1]; if (check()) rr=mid; else ll=mid; } printf("%.4lf\n",ll); return 0; } int check(void){ poss=0; merge_sort(0,n+1); return poss>=k; } void merge_sort(int l,int r){ if (r==l+1) return; int mid=(l+r+1)>>1; //[l,mid),[mid,r) merge_sort(l,mid); merge_sort(mid,r); int i=l; int j=mid; int tpos=0; while (i<mid && j<r){ if (b[j]<=b[i]){ tarr[tpos]=b[j]; j++,tpos++; poss+=(mid-i); } else { tarr[tpos]=b[i]; i++,tpos++; } } while (i<mid){ tarr[tpos]=b[i]; tpos++,i++; //poss+=(r+1-mid); } while (j<r){ tarr[tpos]=b[j]; tpos++,j++; } for (i=0,j=l;i<tpos;i++,j++) b[j]=tarr[i]; } ``` ### T2 涂色游戏 这题看数据范围就觉得像数学题,果然如此。 $20$分:直接暴力搜索即可。 $70$分:考虑按列递推。我们发现某一列的方案数只和上一列所选的颜色数有关,因此我们设状态$f[i][j]$表述当前考虑到第$i$列,且第$i$列所选颜色数为$j$的方案数。 这个方案可以由$f[i-1][x]$转移而来,我们枚举$x$,再求和。 第$i$列和第$i-1$列合起来要出现$q$种颜色,因此第$i$列一定至少要出现第$i-1$列没有出现过的$q-x$种颜色。我们把枚举填色方案分为选色和涂色两步,再相乘即得方案数。 首先考虑较为简单的涂色。假设我们已经选出了$j$种要涂的颜色,现在考虑把这一列(含$n$个格子)涂成$j$种颜色的方案数。设这个方案数为$g[j]$,则$$g[j]=\text{C}^{j}_{j}j^{n}-\text{C}^{j-1}_{j}(j-1)^{n}+\text{C}^{j-2}_{j}(j-2)^n-\cdots+(-1)^{j-i}\text{C}^{i}_{j}i^n+\cdots+(-1)^{j-1}\text{C}^{1}_{j}1^{n}$$ 上式可结合容斥原理理解,基本上的意思是,把最多$j$种颜色的方案数减去最多$j-1$种颜色的方案数,再加上减重复的最多$j-2$种颜色的方案数,再减去…… 接着考虑更为复杂的选色。现在要选出$j$种颜色,其中必须有$q-x$种颜色选自上一列未选的$p-x$种中,剩下的颜色可以任选。我们设上一列未选的$p-x$种颜色构成的集合为$A$,上一列选了的$x$种颜色构成的集合为$B$,则可以枚举选择$A$中和$B$中元素的个数。设$a$为选择的$A$中元素个数$(a\ge q-x)$,$b=j-a$为选择的$B$中元素的个数,那么这种情况下的方案数就是$\text{C}^{a}_{p-x}\text{C}^{b}_{x}$。对每个可能的$a$求和即可。 于是,我们在计算时,对于$f[i][j]$,枚举所有$f[i-1][x]$,把$f[i-1][x]$乘上涂色方案数和选色方案数,累加到$f[i][j]$中去。 $100$分:观察上面的方案,$f[i][j]$是不是$f[i-1][x]$的固定线性组合呢?即在转移时,涂色方案数和选色方案数并不随着列的改变而改变(影响它的只是上一列的颜色数和这一列的颜色数)。而观察数据范围,$m$很大,可能要简化这一重复过程。于是我们采用矩阵Kasumi加速计算。 附注:这道题当时我在考场上写的时候没有搞清楚容斥原理,算错了$g$的值。后来订正时发现了问题,却又发现了一个诡异的问题:得分随着数组大小的增大而增大,数据范围只有$100$,但数组要开到$171$才$\text{AC}$。经过`assert`的仔细检查,终于发现是当$x>q$时$q-x<0$导致下标负向越界。因此数组下标一定要考虑清楚,否则很难查错! ```cpp #include <cstdio> #include <cctype> #include <algorithm> #define MAXIOLOG 25 #define MAXN 105 #define MOD 998244353 using namespace std; typedef long long ll; ll io_temp[MAXIOLOG]; typedef ll io_t; io_t uread(void); void uwrite(io_t x,char spliter='\n'); ll maxx; ll c[MAXN][MAXN]; ll tpow[MAXN]; ll color_methods[MAXN]; ll choose_methods[MAXN][MAXN]; ll tmatrix[MAXN][MAXN]; ll tpowm[MAXN][MAXN]; ll f0[MAXN]; ll qpow(ll x,ll p); void matrix_pow(ll p); void t_times_t(void); void t_times_ans(void); int main(void){ ll n,m,p,q; n=uread(),m=uread(),p=uread(),q=uread(); c[0][0]=1; for (ll i=1;i<=100;i++){ c[i][0]=1; for (ll j=1;j<=i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD; } tpow[0]=0; for (ll i=1;i<=100;i++) tpow[i]=qpow(i,n); //tpow[x]=x^n for (ll i=1;i<=100;i++){ for (ll j=1;j<=i;j++){ ll tres; tres=(ll)(c[i][j])*tpow[j]%MOD; if ((i^j)&1) tres=-tres; color_methods[i]=((color_methods[i]+tres+MOD)%MOD); } } maxx=min(n,p); //Max colors in a column //Choose methods for (ll i=1;i<=maxx;i++){ ll lbound=q-i; //The last column has at least lbound colors //Important! (lbound<0)?(lbound=0):(0); for (ll j=lbound;j<=maxx;j++){ //j->i //j colors in last column, i colors in this column ll part_a_tot=p-j; //Part A:not chosen ll part_b_tot=j; //Part B:chosen ll llbound=max(i-part_b_tot,q-j); (llbound<0)?(llbound=0):(0); //Choose at least llbound colors in part A ll uubound=min(i,part_a_tot); //Choose at most uubound colors in part A ll tmethods=0; for (ll pa_choose=llbound;pa_choose<=uubound;pa_choose++){ ll pb_choose=i-pa_choose; //Choose .. from part a,choose .. from part b tmethods=(tmethods+c[part_a_tot][pa_choose]*c[part_b_tot][pb_choose]%MOD)%MOD; } choose_methods[i][j]=tmethods*color_methods[i]%MOD; } } //i=1(f_0) for (ll j=1;j<=maxx;j++){ f0[j]=(ll)(c[p][j])*(color_methods[j])%MOD; } //f=matrix_pow(choose_methods,m-1)*f0 matrix_pow(m-1); ll aans=0; for (ll i=1;i<=maxx;i++){ ll tans=0; for (ll j=1;j<=maxx;j++) //matrix times vector tans=(tans+f0[j]*choose_methods[i][j]%MOD)%MOD; aans=(aans+tans)%MOD; } uwrite(aans); //aans=f[0]+f[1]+...+f[maxx] return 0; } ll qpow(ll x,ll p){ //x^p % mod; ll ans=1; ll tx=x; while (p){ if (p&1) ans=(ans*tx)%MOD; tx=(tx*tx)%MOD; p>>=1; } return ll(ans); } void matrix_pow(ll p){ //ans=ans^p for (ll i=1;i<=maxx;i++){ for (ll j=1;j<=maxx;j++){ tpowm[i][j]=choose_methods[i][j]; choose_methods[i][j]=(i==j); //Make the ans matrix e } } while (p){ if (p&1) t_times_ans(); //ans*=t; t_times_t(); //t*=t p>>=1; } } void t_times_t(void){ //t*=t //Ans stored in tmatrix for (ll i=1;i<=maxx;i++){ for (ll j=1;j<=maxx;j++){ //Calc row i column j ll tans=0; for (ll k=1;k<=maxx;k++) tans=(tans+ll(tpowm[i][k])*tpowm[k][j]%MOD)%MOD; tmatrix[i][j]=tans; } } //Move for (ll i=1;i<=maxx;i++) for (ll j=1;j<=maxx;j++) tpowm[i][j]=tmatrix[i][j]; } void t_times_ans(void){ //ans*=t //Ans stored in tmatrix for (ll i=1;i<=maxx;i++){ for (ll j=1;j<=maxx;j++){ //Calc row i column j ll tans=0; for (ll k=1;k<=maxx;k++) tans=(tans+ll(choose_methods[i][k])*tpowm[k][j]%MOD)%MOD; tmatrix[i][j]=tans; } } //Move for (ll i=1;i<=maxx;i++) for (ll j=1;j<=maxx;j++) choose_methods[i][j]=tmatrix[i][j]; } ``` ### T3 序列 $40$分:先计算出所有询问的答案,再对每次修改维护它对答案的变化,即可得到答案。 这里有个巨大的锅,就是我在写这题程序时忘记了把修改应用到数组中,居然能过样例,结果当然是全部$\text{WA}$。因此所有的程序在提交前一定要仔细检查一遍程序行为! $100$分:据说是什么“可持久化线段树”,可惜我不会这玩意儿。