Preface
今天这场代码量好小啊……
T4原来是经典的除法分块,但我都忘光了,远程求助陈指导才会
A. 「NOIP2022模拟赛一 By QY A」签到题
Pro
- 构造一个长度为\(n\)的排列\(p\),使得其\(\max(\operatorname{lis(p),\operatorname{lds(p)}})\)是所有排列中最小的一个(相同则输出任意一种)
- \(\operatorname{lis(p)},\operatorname{lds(p)}\)分别表示\(p\)的最长上升子序列长度和最长下降子序列长度
- \(n\le 100000\)
Sol
刚开始有点迷,打了个暴力找了下规律就会了
不难证明\(\max(\operatorname{lis(p),\operatorname{lds(p)}})\)的最小值就是\(\lceil\sqrt n\rceil\),考虑以下的构造方法:
n=9
3 2 1 6 5 4 9 8 7
n=11
3 2 1 7 6 5 4 11 10 9 8
普遍地,把序列分为一些大小为\(\sqrt n\)的块,块的内部保证连续降序,然后块的总数也是\(\sqrt n\)级别的,升序子序列只能存在与不同的块之间
#include<cstdio>
#include<cmath>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],m;
int main()
{
RI i,j,k; scanf("%d",&n); m=(int)sqrt(n); if (m*m<n) ++m;
for (i=n-m+1,k=n;i>=1;i-=m) for (j=0;j<m;++j) a[i+j]=k--;
for (i=1;i<=n%m;++i) a[i]=k--;
for (i=1;i<=n;++i) printf("%d ",a[i]);
return 0;
}
B. 「NOIP2022模拟赛一 By QY B」简单题
Pro
- 在一个圆中随机分布着\(n\)个点,求存在一条直径使得所有点均在直线一侧的概率
- \(n\le 100000\),答案对\(998244353\)取模
Sol
首先在圆内和在圆周上是等价的,然后考虑先选定一个点为起点
以这个起点为端点,规定一个方向(假定为逆时针)做得一段半圆周,我们强制接下来的点都要落在这个半圆周上
剩下\(n-1\)个点都落在上面的概率是\(\frac{1}{2^{n-1}}\),再乘上起点选法\(n\)就是答案了
#include<cstdio>
#include<cmath>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int mod=998244353;
int n;
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
return scanf("%d",&n),printf("%d",1LL*n*quick_pow(quick_pow(2,n-1))%mod),0;
}
C. 「NOIP2022模拟赛一 By QY C」数列题
Pro
- 有一个长度为\(n\)的数列\(a\),\(a_i\)在初始时是整数,后续过程中可以变成实数
- 你可以对一个数\(a_i\)进行一次修改,将其值变为\(a_i'\),这要花费\((a_i-a_i')^2\)的代价
- 求让最后的数列变为等差数列的最小代价总和
- \(n\le 1000\),数据组数T\le 1000$
Sol
考虑等差数列的一般表达形式,\(d_n=A\cdot n+B\),把\(n\)看做自变量,\(d_n\)看做因变量,这其实就是一条直线的方程
而且我们发现原来给出的值\(a_i\)可以看做在平面内的点\((i,a_i)\),而代价的定义也和SSE(残差平方和)一模一样
因此我们直接使用线性回归的相关知识,用最小二乘法得到最优的拟合直线\(y=kx+b\),有:
\[k=\frac{\sum_{i=1}^n (x_i-\overline x)(y_i-\overline y)}{\sum_{i=1}^n (x_i-\overline x)^2}\\
b=\overline y-k\overline x
\]
就可以轻松算出答案了,复杂度\(O(Tn)\)
#include<cstdio>
#include<cctype>
#include<algorithm>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1005;
int t,n,a[N]; double _x,_y,f1,f2,k,b,ans;
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; bool flag=0; while (!isdigit(ch=tc())) if (ch=='-') flag=1;
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc())); if (flag) x=-x;
}
#undef tc
}F;
inline double sqr(const double& x) { return x*x; }
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (F.read(t);t;--t)
{
RI i; for (F.read(n),_x=_y=f1=f2=ans=0,i=1;i<=n;++i)
F.read(a[i]),_x+=i,_y+=a[i]; _x/=n; _y/=n;
for (i=1;i<=n;++i) f1+=(i-_x)*(a[i]-_y),f2+=sqr(i-_x);
for (k=f1/f2,b=_y-k*_x,i=1;i<=n;++i) ans+=sqr(a[i]-(k*i+b));
printf("%.15lf\n",ans);
}
return 0;
}
D. 「NOIP2022模拟赛一 By QY D」化学题
Pro
- 有一个数初值为\(1\),每秒钟它就会等概率随机变为原来的\(1,2,3,\cdots,n\)倍
- 求该数第一次大于\(m\)的期望时间
- \(2\le n\le 9\times 10^8,m\le 10^9\)
Sol
暴力DP人人都会,就是要注意不变的情况,单独提出来后把转移方程移项后即可:
\[f_i=(\sum_{j|i\and \frac{i}{j}\in[1,n]} \frac{f_j}{n}) +1\\
\Leftrightarrow f_i=(\sum_{j|i\and \frac{i}{j}\in(2,n]} \frac{f_j}{n}) +\frac{f_i}{n}+1\\
\Leftrightarrow f_i=(\sum_{j|i\and \frac{i}{j}\in(2,n]} \frac{f_j}{n-1}) +\frac{n}{n-1}
\]
考虑从\(j|i\)入手,用整除分块后状态变成了\(\sqrt m\)级别,同时在转移的时候也可以再套一个整除分块来把相同的值一起转移
总体复杂度\(O(m^{\frac{3}{4}})\)(和杜教筛的复杂度类似,两个除法分块套在一起就是这个复杂度)
#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int S=100000,mod=998244353;
int n,m,sm,st[S<<1],cnt,inv,id1[S],id2[S],f[S<<1];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
#define ID(x) ((x)<=sm?id1[x]:id2[m/(x)])
int main()
{
RI i,l,r; scanf("%d%d",&n,&m); sm=sqrt(m);
for (l=1;l<=m;l=r+1) r=m/(m/l),st[ID(m/l)=++cnt]=m/l;
for (inv=quick_pow(n-1),i=cnt;i;--i)
{
for (l=2;l<=min(n,st[i]);l=r+1)
r=min(n,st[i]/(st[i]/l)),inc(f[i],1LL*(r-l+1)*f[ID(st[i]/l)]%mod);
f[i]=1LL*(f[i]+n)*inv%mod;
}
return printf("%d",f[ID(m)]),0;
}
Postscript
咕噜咕噜咕噜……