首页 > 其他分享 >【题解】NOIP2021

【题解】NOIP2021

时间:2023-09-02 19:55:20浏览次数:41  
标签:int 题解 sum times leq dp NOIP2021 sim

咕咕咕的东西总是要补的。

A.报数

题目描述:

报数游戏是一个广为流传的休闲小游戏。参加游戏的每个人要按一定顺序轮流报数,但如果下一个报的数是 \(7\) 的倍数,或十进制表示中含有数字 \(7\),就必须跳过这个数,否则就输掉了游戏。

在一个风和日丽的下午,刚刚结束 SPC20nn 比赛的小 r 和小 z 闲得无聊玩起了这个报数游戏。但在只有两个人玩的情况下计算起来还是比较容易的,因此他们玩了很久也没分出胜负。此时小 z 灵光一闪,决定把这个游戏加强:任何一个十进制中含有数字 \(7\) 的数,它的所有倍数都不能报出来!

形式化地,设 \(p(x)\) 表示 \(x\) 的十进制表示中是否含有数字 \(7\),若含有则 \(p(x) = 1\),否则 \(p(x) = 0\)。则一个正整数 \(x\) 不能被报出,当且仅当存在正整数 \(y\) 和 \(z\) ,使得 \(x = yz\) 且 \(p(y) = 1\)。

例如,如果小 r 报出了 \(6\) ,由于 \(7\) 不能报,所以小 z 下一个需要报 \(8\);如果小 r 报出了 \(33\),则由于 \(34 = 17 \times 2\),\(35 = 7 \times 5\) 都不能报,小 z 下一个需要报出 \(36\) ;如果小 r 报出了 \(69\),由于 \(70 \sim 79\) 的数都含有 \(7\),小 z 下一个需要报出 \(80\) 才行。

现在小 r 的上一个数报出了 \(x\),小 z 想快速算出他下一个数要报多少,不过他很快就发现这个游戏可比原版的游戏难算多了,于是他需要你的帮助。当然,如果小 r 报出的 x 本身是不能报出的,你也要快速反应过来小 r 输了才行。

由于小 r 和小 z 玩了很长时间游戏,你也需要回答小 z 的很多个问题。

对于 \(100\%\) 的数据,\(1 \le T \leq 2 \times {10}^5\),\(1 \le x \leq {10}^7\)。

题目分析:

一个数的所有倍数显然想到类似埃氏筛的东西,所以其实我们可以类似埃氏筛只不过每一次是将十进制中含有数字 \(7\) 的筛掉,然后再筛它的倍数即可。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e7 + 1e5;
int a[MAXN + 5];
bool is[MAXN + 5];
int nxt[MAXN + 5],cnt; 
int maxn = 0;
bool check(int k){
	while(k){
		if(k % 10 == 7){
			return true;
		}
		k/=10;
	}
	return false;
}
void pre(){
	for(int i=1; i<= maxn; i++){
		if(check(i))
			is[i] = true;
		if(is[i]){
			for(int j=1; j * i <= maxn; j++){
				is[i * j] = true;
			}
		}
	}
}
int main(){
//	freopen("number4.in","r",stdin);
//	freopen("out.txt","w",stdout);
	memset(is,false,sizeof(is));
	int t;
	scanf("%d",&t);
	if(t <= 1000){
		maxn = 11000;
	}
	else if(t <= 10000)
		maxn = 201000;
	else
		maxn = 10001000;
	pre();
	int last = 0;
	bool flag = false;
	for(int i=1; i<=maxn; i++){
		if(!is[i]){
			nxt[last] = i;
			last = i;
		}
	}
	while(t--){
		int x;
		scanf("%d",&x);
		if(is[x]){
			printf("%d\n",-1);
		}
		else{
			printf("%d\n",nxt[x]);
		}
	}
	return 0;
} 

B.数列

题目描述:

给定整数 \(n, m, k\),和一个长度为 \(m + 1\) 的正整数数组 \(v_0, v_1, \ldots, v_m\)。

对于一个长度为 \(n\),下标从 \(1\) 开始且每个元素均不超过 \(m\) 的非负整数序列 \(\{a_i\}\),我们定义它的权值为 \(v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}\)。

当这样的序列 \(\{a_i\}\) 满足整数 \(S = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n}\) 的二进制表示中 \(1\) 的个数不超过 \(k\) 时,我们认为 \(\{a_i\}\) 是一个合法序列。

计算所有合法序列 \(\{a_i\}\) 的权值和对 \(998244353\) 取模的结果。

对所有测试点保证 \(1 \leq k \leq n \leq 30\),\(0 \leq m \leq 100\),\(1 \leq v_i < 998244353\)。

题目分析:

显然是需要 \(dp\) 解决这个问题的,因为限制里有二进制下 \(1\) 的个数不超过 \(k\) 所以考虑直接把这个东西记到状态里。
可以发现我们相当于将每一个数确定它是二进制下的哪一位的 \(1\),因为我们需要考虑二进制下的进位问题,所以为了方便我们肯定是从低到高考虑每一位。
所以这样的话 \(dp\) 的状态就很显然了,\(dp[i][j][k][p]\) 表示考虑到了二进制下从低到高前 \(i\) 位,现在已经确定了 \(j\) 个 \(a\) 的值,二进制下 \(1\) 的个数为 \(k\),向第 \(i\) 位的进位数量为 \(p\)。
转移的话就是枚举第 \(i+1\) 位的 \(a\) 有多少个:

\[dp[i][j][k][p] \times v_i^t \times \binom{n-j}{t} \to dp[i+1][j+t][(p+1)\&1][\lfloor \frac{t+p}{2} \rfloor] \]

统计答案的时候就是要考虑 \(p\) 的进位会产生多少个位数超过 \(m\) 的 \(1\)。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 998244353;
int f[105][35][35][35];
int pw[105][105];
int v[105];
int fac[105],inv[105];
int mod(int x){
	return x % MOD;
}
int power(int a,int b){
	int res = 1;
	while(b){
		if(b & 1)	res = mod(res * a);
		a = mod(a * a);
		b >>= 1;
	}
	return res;
}
int binom(int n,int m){
	if(n < m || n < 0 || m < 0)	return 0;
	return mod(fac[n] * mod(inv[m] * inv[n-m]));
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,m,K;scanf("%lld%lld%lld",&n,&m,&K);
	for(int i=0; i<=m; i++)	scanf("%lld",&v[i]);
	fac[0] = 1;
	for(int i=1; i<=n; i++)	fac[i] = mod(fac[i-1] * i);
	inv[n] = power(fac[n],MOD-2);
	for(int i=n-1; i>=0; i--)	inv[i] = mod(inv[i+1] * (i + 1));
	for(int i=0; i<=m; i++){
		pw[i][0] = 1;
		for(int j=1; j<=n; j++)	pw[i][j] = pw[i][j-1] * v[i] % MOD;
	}
	f[0][0][0][0] = 1;
	for(int i=0; i<=m; i++){
		for(int j=0; j<=n; j++){
			for(int k=0; k<=K; k++){
				for(int p=0; p<=n; p++){
					for(int t=0; t<=n-j; t++){
						f[i+1][j+t][k+((t+p)&1)][(t+p)/2] = (f[i+1][j+t][k+((t+p)&1)][(t+p)/2] + f[i][j][k][p] * binom(n-j,t)%MOD * pw[i][t])%MOD;
					}
				}
			}
		}
	}
	int ans = 0;
	for(int k=0; k<=K; k++){
		for(int p=0; p<=n; p++){
			if(k + __builtin_popcount(p) <= K)	ans = (ans + f[m+1][n][k][p])%MOD;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

C.方差

题目描述:

给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 < i < n\),将 \(a_i\) 变为 \(a_{i - 1} + a_{i + 1} - a_i\)。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 \(n^2\) 的结果。

其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 \(D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}^2\),其中 \(\bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i\)。

测试点编号 \(n \le\) \(a_i \le\)
\(1 \sim 3\) \(4\) \(10\)
\(4 \sim 5\) \(10\) \(40\)
\(6 \sim 8\) \(15\) \(20\)
\(9 \sim 12\) \(20\) \(300\)
\(13 \sim 15\) \(50\) \(70\)
\(16 \sim 18\) \(100\) \(40\)
\(19 \sim 22\) \(400\) \(600\)
\(23 \sim 25\) \({10}^4\) \(50\)

题目分析:

第一步显然就是把 \(n^2\) 乘到贡献里:(不会真的有人是算出最小值再乘 \(n^2\) 吧)

\[\begin{aligned} ans &= n \times \sum_{i=1}^n (a_i - \bar a)^2 \\ &= n \times \sum_{i=1}^n a_i^2 - 2 \times n \times \sum_{i=1}^n (a_i \times \bar a) + n \times \sum_{i=1}^n \bar a^2 \\ &= n \times \sum_{i=1}^n a_i^2 - (\sum_{i=1}^n a_i)^2 \\ \end{aligned} \]

然后就是一次操作可以干什么,如果我们将原序列差分的话会发现这个操作本质上是交换相邻的两个差分数组的数,那么自然想到最小值是不是和差分数组有什么关系。
然后手动模拟一下样例或者搜搜就会发现,在最优情况下差分数组是单谷的。
这个有什么用呢,就是假设我们将差分数组 \(d\) 排序之后,从小到大插入每一个数,那么我们每次都是在序列的首或者尾插入数的。
这个就意味着我们可以直接 \(dp\) 这个过程了,如果我们直接设 \(dp[i]\) 表示插入了 \(d\) 中前 \(i\) 个数的最优答案的话,我们会发现根本没办法转移,因为我们好像啥也不知道。
所以可以尝试是不是可以把某个东西放到状态里呢,就是可以将差分数组还原后形成的原数组的和记下来,也就是设 \(dp[i][j]\) 表示插入了 \(d\) 中前 \(i\) 个数,原数组的和为 \(j\) 时平方和的最小值。
这里需要注意的一点就是:我们无需在意到底是怎么操作得到的,我们已经 \(dp\) 得到了差分数组,所以直接做一次前缀和就可以得到原数组。
转移就是讨论插在首还是尾:
如果插在首:

\[f[i][j] + 2 \times j \times d_{i+1} + (i+1) \times d_{i+1}^2 \to f[i+1][j + (i+1) \times d_{i+1}] \]

如果插在尾:

\[f[i][j] + (\sum_{k=1}^{i+1} d_k)^2 \to f[i+1][j + \sum_{k=1}^{i+1} d_k] \]

虽然看似一切完美但是有一个十分严重的问题就是:\(a_1\) 与前一个数形成的这个差分值我们该怎么处理,因为在前面的分析中我们是直接将这个忽略才得到了这一系列的结论,而加入这个值显然会让我们直接求得的一切都不成立。
因为我们的操作不会对这个值产生任何影响,考虑加上这个值就是会多一个数 \(a_1\) 并且将所有数加 \(a_1\),推推可以发现完全能直接完全忽略这个值,因为把 \(a_1\) 的贡献带入上面的式子之后会直接被消掉。
但是消掉是消掉,我们最后带入算式的 \(n\) 依旧是 \(n\) 而不是 \(n-1\) 这个自己带带就能发现。

这样的话时空复杂度就是 \(O(n \times n \times a)\)。
空间复杂度很好说,直接滚掉第一维就好了。
观察数据范围会发现 \(a\) 特别小相比来说 \(n\) 比较大,也就是说 \(d_i = 0\) 的位置很多,而 \(d_i = 0\) 意味着转移不会产生任何影响,所以可以直接忽略掉。
这样的话时间复杂度就变成了 \(O(\min(n,a) \times n \times a)\),这样就可以过了。

代码:

点击查看代码
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <assert.h>
using namespace std;

#define re register
#define sf scanf
#define pf printf
#define nl() putchar('\n')
#define ll long long
#define _for(i, a, b) for(re int (i) = (a); (i) < (b); ++(i))
#define _rfor(i, a, b) for(re int (i) = (a); (i) <= (b); ++(i))
#define inf 0x7ffffffffffffffll
#define maxn 10005
#define maxx 500005

int rdnt(){
   re int x = 0, sign = 1;
   re char c = getchar();
   while (c < '0' || c > '9') { if (c == '-') sign = -1; c = getchar(); }
   while (c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (c ^ 48), c = getchar();
   return x * sign;
}

ll  a[maxn], d[maxn], s[maxn], f[maxx];

inline void ud(re ll &x, re ll y){ if (y < x) x = y; }

int main(){
   #ifndef ONLINE_JUDGE
   freopen("variance.in", "r", stdin);
   freopen("variance.out", "w", stdout);
   #endif

   re int n = rdnt(), rg = 0; re ll mx = 0, ma = a[1] = rdnt();
   _rfor(i, 2, n) d[i-1] = (a[i] = rdnt())-a[i-1], ma = max(ma, a[i]);
   _rfor(x, 1, ma*n) f[x] = inf; f[0] = s[0]= 0;
   sort(d+1, d+n);
   _for(i, 1, n){
   	s[i] = s[i-1] + d[i];
   	if (d[i] == 0) continue;
   	for(re int x = mx; x >= 0; --x){
   		if (f[x] == inf) continue;
   		ud(f[x+i*d[i]], f[x] + 2*x*d[i] + i*d[i]*d[i]);
   		ud(f[x+s[i]], f[x] + s[i]*s[i]);
   		mx = max(mx, max(x+i*d[i], x+s[i]));
   		f[x] = inf;
   	}
   }
   re ll ans = inf;
   _rfor(x, 0, mx) if (f[x] < inf) ud(ans, n*f[x] - (ll)x*x);
   pf("%lld\n", ans);

   return 0;
}

标签:int,题解,sum,times,leq,dp,NOIP2021,sim
From: https://www.cnblogs.com/linyihdfj/p/17673140.html

相关文章

  • 【题解】Luogu[P7706] 「Wdsr-2.7」文文的摄影布置
    Link一道很有意思的线段树题。第一步分析,我们要求最大的\(a_i+a_k-\min{(b_j)}\),事实上我们可以直接省去这个\(\min\)因为要最大化这个东西,选出来的\(b_j\)必然是最小的,所以原题转化为给定\(l,r\)求\(\max{(a_i-b_j+a_k)}\)其中\(i<j<k\)。第二步分析,我们发现这是一......
  • 【题解】Educational Codeforces Round 153(CF1860)
    每次打都想感叹一句,Educational名不虚传。A.NotaSubstring题目描述:有\(t\)组数据,对于每一组数据,你需要判断能否构造一个只由左右括号组成且长度为已经给定字符串的\(2\)倍且已经给定的字符串不是子串的合法字符串。注:合法的字符串是左右括号能完全匹配的字符串。如果能,......
  • 【题解】Luogu-P2482 SDOI2010 猪国杀
    写了\(358\)行,\(11.94\mathrm{KB}\),有这么几个地方写挂了:反猪决斗一定选主猪。游戏结束判定是主猪死亡或全部反猪死亡。决斗可能被反杀,之后不能再出牌。点击查看代码#include<bits/stdc++.h>usingnamespacestd;intn,m;charCh[3];queue<char>Deck;in......
  • CF1863C 题解
    CF1863CMEXRepetition题解Links洛谷CodeforcesDescription给你一个长度为\(n\)的序列\(a\),满足\(\foralli\in[1,n]\),\(0\leqa_{i}\leqn\)且序列中的数互不相同。定义一次操作为:按照\(i\)从\(1\)到\(n\)的顺序,\(a_{i}\gets\operatorname{MEX}(a_{1}......
  • CF1863B 题解
    CF1863BSplitSort题解Links洛谷CodeforcesDescription给定一个\(1\simn\)的排列\(q\),你可以多次进行以下操作:新建一个初始为空的序列\(q\);选择一个整数\(x\)(\(2\leqx\leqn\));按照在\(p\)中出现的顺序将所有小于\(x\)的数添加到序列\(q\)末尾。按照在......
  • P1463 [POI2001] [HAOI2007] 反素数 题解
    P1463[POI2001][HAOI2007]反素数题解可以发现,最大的不超过\(n\)的反素数就是\(1\simn\)中因数最多的数字。证明:设\(x,x\in[1,n]\)为\(1\simn\)中因数最多的数字,则\(x<y\len\)都不会成为答案,因为存在\(x<y\)因数比\(y\)多,同时也不会存在\(y'<x\)......
  • vmware中的疑难问题解决方法
    converter在windows中使用的agent服务端口9089,vcenter使用443端口。converter再windows中不能启动服务的解决方法:1.开启TLS1.01.11.2。在programdata中更改SSLOPTIONS的值为:56313856。可以尝试官方方法2.禁用网络连接:在注册表中localmachine-system-currentcontrolset-contro......
  • CF 1863D 题解
    CF1863DTwo-ColoredDominoes题解Links洛谷CodeforcesDescription有一个\(n\timesm\)的棋盘,上面铺着一些\(1\times2\)的多米诺骨牌(横竖均有可能),骨牌之间没有重叠。你需要找到一种染色方案满足以下条件:每个多米诺骨牌一端被染白,另一端被染黑。其他没有骨牌的格子......
  • 题解 [AGC004D] Teleporter
    题目链接躺在床上想到重要性质的题目。。。首先,由于每个城市只有一个可以直接到达的城市,所以\(n\)个城市就有\(n\)条边,容易发现这是一棵基环树,那么我们先从普通树的角度考虑,若要求每个点走\(k\)条边恰好到\(1\)号节点,这个环必须加在哪里。若\(k=1\),没有环显然也是可行......
  • GCD Counting题解
    题意有一棵有\(n\)个节点的树,第\(i\)个节点有点权\(a_i\)。定义\(g(x,y)\)为\(x\)到\(y\)的树上路径所经过的点的点权\(\gcd\)。对于每一个正整数\(k\in[1,2\times10^5]\)求出满足以下条件的\(x,y\)的对数:\(1\lex\ley\len\)\(g(x,y)=k\)\(1\len......