本文搬运自本人初中博客园博客,若图片加载不出来,可到原文查看:https://www.cnblogs.com/zhangtingxi/p/15600134.html

我太弱了,改不出T4,就把T1-3题解码了。

T1 报数

题目链接

想着T2,T3的题解都写了,就补一下T1的吧。

典型的筛法。

假如一个数含有7,则把它的倍数全筛走。

这里可以加一个小优化,假如这个数已经被筛过,就不需要再筛它的倍数了。

最后再倒着预处理每个数的下一个没被筛的是什么。

如果不预处理,不断6999999就可以卡死你。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();
}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();}return x*f;}
//#define M
//#define mo
#define N 10000050
int n, m, i, j, k;
int f[N], s[N], t, x;

int pan(int x)
{
while(x)
{
if(x%10==7) return 1;
x/=10;
}
return 0;
}

signed main()
{
// freopen("number.in", "r", stdin);
// freopen("number.out", "w", stdout);
for(i=1; i<=10000005; ++i)
{
if(pan(i))
{
if(!f[i])
for(j=i; j<=10000005; j+=i) f[j]=1;
}
// if(!f[i]) printf("%d\n", i);
}
for(i=10000005; i>=1; --i)
{
if(!f[i]) s[i]=i;
else s[i]=s[i+1];
// printf("s[%d]=%d\n", i, s[i]);
}
t=read();
while(t--)
{
x=read();
if(f[x]) printf("-1\n");
else printf("%d\n", s[x+1]);
}
return 0;
}

T2 数列

题目链接

首先dp得从低位向高位枚举,因为高位无论如果使用 2ai2^{a_i} 都对低位二进制1的个数无影响,满足dp的无后效性。

dp(k,i,x,y)dp(k, i, x, y)SS 从低的高二进制的前 kk 位中,用了数列 aa 的前 ii 项,且此时 SS 中共有 xx 个二进制位为1,第 i+1i+1 位进了 yy 过去。

则:

dp(k,i,x,y)=j=0nidp(k+1,i+j,x+(y+j&1),y+j>>1)×Ci+jj×vkjdp(k, i, x, y)=\sum_{j=0}^{n-i}dp(k+1, i+j, x+(y+j\&1),y+j>>1)\times C_{i+j}^j\times v_k^j

假设这一位有 jjaja_j 满足 aj=ka_j=k,则下一位就有 i+ji+jaa 的元素确定,如果这一位是1,则 x+1x+1,在转移中的 x+(y+j&1)x+(y+j\&1) 体现。而进位到下一位的就是 y+j>>1y+j>>1 了。

对于每种方案,其对于答案的贡献为 vkjv_k^j,而方案相当于在 i+ji+j 个数中插 jj 块板,即 Ci+jjC_{i+j}^j

建议用记忆化搜索实现。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Problem: P7961 [NOIP2021] 数列【民间数据】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7961
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define mo 998244353
#define N 40
#define M 110
int n, m, i, j, k;
int c[M][M], s[M][M];
int f[M][N][N][N], v[M], K;

int count(int x)
{
int ans=0;
while(x) x-=x&-x, ++ans;
return ans;
}

int dfs(int k, int u, int x, int y)
{
if(u==n)
{
// printf("> f[%lld][%lld][%lld][%lld]=%lld\n", k, u, x, y, x+count(y)<=K);
if(x+count(y)>K) return 0;
// printf("----------");
return 1;
}
if(k>m) return 0;
if(f[k][u][x][y]!=-1) return f[k][u][x][y];
int ans=0;
for(int i=0; i<=n-u; ++i)
{
ans=(ans+dfs(k+1, u+i, x+((y+i)&1), (y+i)>>1)
*s[k][i]%mo*c[u+i][i]%mo)%mo;
// printf("%lld %lld\n", s[k][i], c[n-u][i]);
}
f[k][u][x][y]=ans;
// printf("f[%lld][%lld][%lld][%lld]=%lld\n", k, u, x, y, f[k][u][x][y]);
return f[k][u][x][y];
}


signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
memset(f, -1, sizeof(f));
n=read(); m=read(); K=read();
for(i=0; i<=m; ++i) v[i]=read();
c[0][0]=1;
for(i=1; i<=n; ++i)
for(j=c[i][0]=1; j<=i; ++j)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mo;
for(i=0; i<=m; ++i)
{
s[i][0]=1;
for(j=1; j<=n; ++j) s[i][j]=(s[i][j-1]*v[i])%mo;
}
printf("%lld\n", dfs(0, 0, 0, 0));
return 0;
}


T3 方差

题目链接

Part A 式子化简

首先题目要求的式子就是 n2n^2 乘上 1ni=1n(aiaˉ)2\frac{1}{n}\sum_{i=1}^n(a_i-\bar a)^2,其中 aˉ=1ni=1nai\bar a=\frac{1}{n}\sum_{i=1}^n a_i

我们把这三合在一起也就是:

n2×1ni=1n(ai1nj=1naj)2n^2\times\frac{1}{n}\sum_{i=1}^n(a_i-\frac{1}{n}\sum_{j=1}^n a_j)^2

前面化简一下,后面用完全平方公式展开:

n×i=1n(ai22×(1nj=1naj)×ai+1n2(j=1naj)2)n\times\sum_{i=1}^n(a_i^2-2\times(\frac{1}{n}\sum_{j=1}^n a_j)\times a_i+\frac{1}{n^2}(\sum_{j=1}^n a_j)^2)

拆开:

n×i=1nai2n×i=1n(2×1n(j=1naj)×ai)+n×i=1n(1n2(j=1naj)2)n\times\sum_{i=1}^n a_i^2-n\times\sum_{i=1}^n(2\times \frac{1}{n}(\sum_{j=1}^n a_j)\times a_i)+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2)

对于第二坨,把与 aia_i 无关的抽出来:

n×i=1nai2n×2×1n(j=1naj)×i=1nai+n×i=1n(1n2(j=1naj)2)n\times\sum_{i=1}^n a_i^2-n\times 2\times \frac{1}{n}(\sum_{j=1}^n a_j)\times\sum_{i=1}^n a_i+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2)

把第二坨化简一下:

n×i=1nai22×i=1nai×i=1nai+n×i=1n(1n2(j=1naj)2)n\times\sum_{i=1}^n a_i^2-2\times\sum_{i=1}^n a_i\times\sum_{i=1}^n a_i+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2)

把第二坨的后两个写成平方的形式:

n×i=1nai22×(i=1nai)2+n×i=1n(1n2(j=1naj)2)n\times\sum_{i=1}^n a_i^2-2\times(\sum_{i=1}^n a_i)^2+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2)

看第三坨,发现没有和 ii 有关的项,之间把 i=1n\sum_{i=1}^n 变成乘 nn

n×i=1nai22×(i=1nai)2+n×n×1n2(j=1naj)2n\times\sum_{i=1}^n a_i^2-2\times(\sum_{i=1}^n a_i)^2+n\times n\times\frac{1}{n^2}(\sum_{j=1}^n a_j)^2

化简一下第三坨:

n×i=1nai22×(i=1nai)2+(i=1nai)2n\times\sum_{i=1}^n a_i^2-2\times(\sum_{i=1}^n a_i)^2+(\sum_{i=1}^n a_i)^2

然后我们发现二、三坨可以合并:

n×(i=1nai2)(i=1nai)2n\times(\sum_{i=1}^n a_i^2)-(\sum_{i=1}^n a_i)^2

于是对于每种 aa 我们都有直接算的方法了。

Part B 差分交换

现在让我们考虑令一个问题。

对于相邻的三个数 ai1a_{i-1}aia_iai+1a_{i+1},我们计算相邻两数的差分别为 aiai1a_i-a_{i-1}ai+1aia_{i+1}-a_i

先在我们把 aia_i 变成 ai1+ai+1aia_{i-1}+a_{i+1}-a_i,现在就是 ai1a_{i-1}ai1+ai+1aia_{i-1}+a_{i+1}-a_iai+1a_{i+1}

然后我们再对相邻两数作差分别为:ai+1aia_{i+1}-a_iaiai1a_i-a_{i-1}

发现了什么?

没错,我们对每个数进行交换,只不过是把相邻两项的差交换

于是我们对 aa 作差分序列 dd,这样无论我们对 aa 序列进行怎样的变换,dd 序列的元素始终不变,只是顺序改变

基于这个结论,我们可以枚举 dd 的顺序,时间复杂度 O(n!)O(n!)

Part C 差分单谷性

这里我们可以引出一个结论:dd 的排列必然是先从大到小,再从小到大。

这里可以感性理解一下,因为对于每个 aa 只有不断靠近平均数,方差才最小。

我们也可以大致运用反证法证明。(有误请指出)

看不到?戳这里查看图片

图中 d1>d2d_1>d_2,我们发现 a1a_1a3a_3 的值不变,图2相比图1中只有 a2a_2 变大了。

两个 a2a_2 相比,显然图1中的 a2a_2 更靠近谷底,也就是 aˉ\bar a。按照开始的式子 1ni=1n(aiaˉ)2\frac{1}{n}\sum_{i=1}^n(a_i-\bar a)^2 我们发现 a2a_2 取图1的情况更优。

有了这个结论,我们可以把 dd 按从大到小排序,对于 did_i,要么放左边,要么放右边,通过此方法,我们就得到了一种 O(2n)O(2^n) 的暴力。

48分Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Problem: P7962 [NOIP2021] 方差【民间数据】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
// #define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 410
//#define mo
int n, m, i, j, k;
int a[N], d[N], b[N];
int ans=0x7fffffff;
int sum, num;

bool cmp(int x, int y)
{
return x>y;
}

void dfs(int x, int l, int r, int s, int t)
{
if(l==r-1)
{
ans=min(ans, n*t-s*s);
return ;
}
int k;
k=a[l+1]=a[l]+d[x]; dfs(x+1, l+1, r, s+k, t+k*k);
k=a[r-1]=a[r]-d[x]; dfs(x+1, l, r-1, s+k, t+k*k);
}

signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
n=read();
for(i=1; i<=n; ++i) a[i]=read();
for(i=2; i<=n; ++i) d[i-1]=a[i]-a[i-1];
sort(d+1, d+n, cmp);
dfs(1, 1, n, a[1]+a[n], a[1]*a[1]+a[n]*a[n]);
printf("%d", ans);
return 0;
}

Part D 相对位置代替绝对位置

可以发现,我们在上面的方法中每次都把 aa 的绝对位置求出来。但其实我们可以只求出其相对位置。

我们先对 dd 从小到达排序,再一条一条放进去。此时 aia_i 的位置是在这种放进去的方法下的 j=1idj\sum_{j=1}^i d_j

首先让我们把之前的式子搬回出来:

n×(i=1nai2(i=1nai)2n\times(\sum_{i=1}^n a_i^2)-(\sum_{i=1}^n a_i)^2

我们要让结果最小,就是要让 i=1nai\sum_{i=1}^n a_i 最大,i=1nai2\sum_{i=1}^n a_i^2 最小。

让我们设 f(i,j)f(i, j) 表示已经决定的 iiaa 里面,和为 jj 的最小平方和。设 SS 为当前的 iij=1idi\sum_{j=1}^i d_i

jj 可以理解为 i=1nai\sum_{i=1}^n a_if(i,j)f(i, j) 可以理解为 i=1nai2\sum_{i=1}^n a_i^2

现在对于 di+1d_{i+1},要么放在左边,要么放在右边。

先说放在右边,jj 则会加上当前点的坐标,答案加上当前的平方,而当前点为 S+diS+d_i

f(i+1,j+(S+di))=f(i,j)+S×Sf(i+1, j+(S+d_i))=f(i, j)+S\times S

放在左边的话,就是整体 aa 右移 di+1d_{i+1}jj 就明显是加上 (i+1)×di+1(i+1)\times d_{i+1},至于 ff 的话,这里要手推一下。

首先至少要加上这个点 di2d_i^2,然后后面的和变成 k=1i(ak+di)2\sum_{k=1}^i(a_k+d_i)^2,而原先是 k=1iak2\sum_{k=1}^i a_k^2,所以加上的是:

k=1i(ak+di)2k=1iak2\sum_{k=1}^i(a_k+d_i)^2-\sum_{k=1}^i a_k^2

前面用完全平方公式拆一下:

k=1iak2+k=1i(2×ak×di)+k=1idi2k=1iak2\sum_{k=1}^i a_k^2+\sum_{k=1}^i(2\times a_k\times d_i)+\sum_{k=1}^i d_i^2-\sum_{k=1}^i a_k^2

最前面与最后面两坨消去:

k=1i(2×ak×di)+k=1idi2\sum_{k=1}^i(2\times a_k\times d_i)+\sum_{k=1}^i d_i^2

前面那坨把无关紧要的提出了:

2×di×k=1ak+k=1idi22\times d_i\times\sum_{k=1} a_k+\sum_{k=1}^i d_i^2

jj 套进去:

2×di×j+k=1idi22\times d_i\times j+\sum_{k=1}^i d_i^2

后面那坨直接把 k=1i\sum_{k=1}^i 变成乘 ii

2×di×j+i×di22\times d_i\times j+i\times d_i^2

根据这个,我们可以把放左边的dp推出了:

f(i+1,j+di+1×(i+1))=f(i,j)+2×di×j+i×di2+di2f(i+1, j+d_{i+1}\times (i+1))=f(i, j)+2\times d_i\times j+i\times d_i^2+d_i^2

后面两个合一下:

f(i+1,j+di+1×(i+1))=f(i,j)+2×di×j+(i+1)×di2f(i+1, j+d_{i+1}\times (i+1))=f(i, j)+2\times d_i\times j+(i+1)\times d_i^2

然后就行了。

转移方程与最终代码可能有一些细节的东西,改一改就行了。

可是按照这个方法打只有72分。

72分code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Problem: P7962 [NOIP2021] 方差【民间数据】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 410
#define M 610
//#define mo
int n, m, i, j, k;
int a[N], d[N];
int ans=0x7fffffffff;
int sum, num;
int f[N][N*M];

signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
n=read();
for(i=1; i<=n; ++i) a[i]=read();
for(i=2; i<=n; ++i) d[i-1]=a[i]-a[i-1];
sort(d+1, d+n);
for(i=0; i<=n; ++i) for(j=0; j<=240000; ++j) f[i][j]=ans;
f[0][0]=0;
for(i=0; i<n-1; ++i)
{
sum+=d[i+1];
for(j=0; j<=240000; ++j)
{
f[i+1][j+sum]=min(f[i+1][j+sum], f[i][j]+sum*sum);
f[i+1][j+d[i+1]*(i+1)]=min(f[i+1][j+d[i+1]*(i+1)], f[i][j]+2*j*d[i+1]+(i+1)*d[i+1]*d[i+1]);
// if(f[i][j]!=ans)
// printf("f[%lld][%lld]=%lld\n", i, j, f[i][j]);
}
}

for(i=0; i<=24000; ++i)
{
// if(f[n-1][i]!=0x7fffffffff) printf("f[%lld][%lld]=%lld\n", n-1, i, f[n-1][i]);
// ans=min(ans, n*(f[n-1][i]+2*i*a[1]+(n-1)*a[1]*a[1]+a[1]*a[1])-(a[1]*n+i)*(a[1]*n+i));
ans=min(ans, n*f[n-1][i]-i*i);
}

printf("%lld", ans);
return 0;
}

Part E 小优化

我们发现主要是集中在MLE和TLE。

MLE方面,我们可以使用滚动数组。

TLE的话,对于 jj 的循环范围,我们可以动态分配。

提一句,最后加不加上 a1a_1 都行(实测可以)。

最后贴上AC code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Problem: P7962 [NOIP2021] 方差【民间数据】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 10010
#define M 610
//#define mo
int n, m, i, j, k;
int a[N], d[N];
int ans=0x7ffffffffff;
int sum, num, p, q;
int f[2][500010];

signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
n=read();
for(i=1; i<=n; ++i) a[i]=read();
for(i=2; i<=n; ++i) d[i-1]=a[i]-a[i-1];
sort(d+1, d+n);
for(j=0; j<=500000; ++j) f[1][j]=f[0][j]=ans;
f[0][0]=0;
for(i=0; i<n-1; ++i)
{
sum+=d[i+1];
p=(i+1)%2; q=i%2; num+=d[i+1]*(i+1);
for(j=0; j<=num; ++j) f[p][j]=ans;
for(j=0; j<=num; ++j)
{
if(j+sum<=num)
f[p][j+sum]=min(f[p][j+sum], f[q][j]+sum*sum);
if(j+d[i+1]*(i+1)<=num)
f[p][j+d[i+1]*(i+1)]=min(f[p][j+d[i+1]*(i+1)], f[q][j]+2*j*d[i+1]+(i+1)*d[i+1]*d[i+1]);
}
}

for(i=0; i<=500000; ++i)
{
//ans=min(ans, n*(f[(n-1)%2][i]+2*i*a[1]+(n-1)*a[1]*a[1]+a[1]*a[1])-(a[1]*n+i)*(a[1]*n+i));
//可写可不写,是是否加上a1的情况
ans=min(ans, n*f[n-1][i]-i*i);
}

printf("%lld", ans);
return 0;
}