2021牛客OI赛前集训营-提高组(第一场)
前言:#
pts:25 + 20 + 0 + 30 = 75
最惨淡的一场。
出现的主要问题是没分配好时间,导致 的暴力都没来急写。
出现的最大问题是脑子掉线。。。。
今后考试策略:
拿到题先读完所有的题,然后把所有能写的暴力全敲完之后再来想正解。
考试一定要带脑子 !!!
T1 牛表#
题目描述
给出三个整数 , 为素数,可以重复对 执行如下操作:
选择一个整数 ,花费 的牛币,使得 。
最小需要花费多少牛币才能使得 ?
设 为当 时的答案,为了减少输出,你需要输出
数据范围
考试的时候真没忘建图那方面想,然后就草草打了个暴力走人了。
solution
对于每一个操作,相当于从 向 建一条边权为 的边,然后到 跑一个最短路就好了。
30pts
当 直接 就好了。
但由于数据水或者牛客神机太快了,这玩意赛时能过 ,现在加强了数据,但是还能过 分,就挺反人类行为的 = =
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define rg register
#define int long long
using namespace std;
const int mod = 998244353;
const int MAXN = 2020, MAXX = 4e6 + 5;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int dis[MAXN][MAXN], Pow[MAXX], p, t, ans;
signed main(){
p = read(), t = read();
memset(dis, 0x3f, sizeof dis);
for (int i = 1; i < p; i++) {
for (int j = 1; j < p; j++) {
int v = i * j % p;
dis[i][v] = min(dis[i][v], abs(i - j));
}
}
for (int i = 0; i < p; i++) dis[i][i] = 0;
for (int k = 0; k < p; k++){
for (int i = 0; i < p; i++) {
for (int j = 0; j < p; j++) {
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
Pow[0] = 1;
for (int i = 1; i <= p * p; i++) Pow[i] = Pow[i - 1] * t % mod;
for (int i =1; i < p; i++) {
for (int j = 1; j < p; j++) {
ans = ans + dis[i][j] * Pow[(i - 1) * (p - 1) + j - 1] % mod;
ans %= mod;
}
}
cout<<ans;
return 0;
}
T2 牛牛和数组操作#
题目描述
有 个整数
你需要做确切地 次操作,每次操作以下形式:
选择一个整数 满足 , 使得 , 令
此次操作的操作为 牛币
有多少不同的操作方式使得操作花费的牛币最少,两种操作不同当且仅当两种操作的操作序列不同。
答案对 取模。
数据范围
, 每个测试点 的和
solution
这道题我的 n! 过掉了 的数据就挺反人类的 = =
这道题 能过也挺反人类的 = =。
正解是个卡常。。。。
10pts
枚举全排列就好了。
20pts
状态压缩,计算代价,对比代价,得出答案。
70pts(实测 90)
设第一个操作的人的编号为 , 在 进行操作 和 的操作就独立了,这两段区间进行操作不会对另一段区间的价值产生影响,因此可以进行区间 。
设 表示对 区间进行操作的最小代价。
显然
设 为 区间内最小操作的序列数量,枚举断点 。
显然只有当 才可以转移。
因为两个序列之间的操作顺序先后都可,所以就有 种方案。
复杂度:
code
/*
work by:Ariel_
Sorce:
Knowledge:Dp
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int MAXN = 2010, INF = 1e18;
const int mod = 998244353;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int n, C[MAXN][MAXN], a[MAXN], Max[MAXN][MAXN], f[MAXN][MAXN], g[MAXN][MAXN];
void Pre() {
for (int i = 1; i <= n; i++){
Max[i][i] = a[i];
for (int j = i + 1; j <= n; j++)
Max[i][j] = max(Max[i][j - 1], a[j]);
}
C[0][0] = 1;
for (int i = 1; i <= n; i++) {
C[i][0] = 1, C[i][i] = 1;
for (int j = 1; j < i; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
}
}
void Dp() {
for (int len = 0; len <= n; len++) {
for (int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
if(l >= r) f[l][r] = 0, g[l][r] = 1;
else {
f[l][r] = INF, g[l][r] = 0;
for (int k = l; k <= r; k++)
f[l][r] = min(f[l][r], f[l][k - 1] + f[k + 1][r] + Max[l][k - 1] + Max[k + 1][r]);
for (int k = l; k <= r; k++) {
if(f[l][r] == f[l][k - 1] + f[k + 1][r] + Max[l][k - 1] + Max[k + 1][r])
g[l][r] = (g[l][r] + g[l][k - 1] * g[k + 1][r] % mod * C[r - l][k - l] % mod) % mod;
}
}
}
}
}
signed main(){
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
Pre();
Dp();
cout<<g[1][n];
puts("");
return 0;
}
100pts
实际上每次先操作区间最大值是最优的,因此没有必要对区间的所有数都进行枚举,而只枚举区间最大值,时间复杂度
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int mod = 998244353, INF = 1e18;
const int MAXN = 1e3 + 50;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int inv[MAXN], fac[MAXN], g[MAXN][MAXN], f[MAXN][MAXN], Max[MAXN][MAXN];
int pos[MAXN][MAXN], a[MAXN], n;
int nxt[MAXN], pre[MAXN];
bool vis[MAXN][MAXN];
void Pre() {
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i <= 1000; i++) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
for (int i = 2; i <= 1000; i++)
inv[i] = inv[i] * inv[i - 1] % mod;
}
int C(int n, int m) {
if(n < m) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
void dfs(int l, int r) {
if(l >= r) {
f[l][r] = 0, g[l][r] = 1;
return ;
}
if(vis[l][r]) return ;
vis[l][r] = true;
f[l][r] = INF, g[l][r] = 0;
for (int i = pos[l][r]; i <= r; i = nxt[i]) {
dfs(l, i - 1), dfs(i + 1, r);
g[l][r] = (g[l][r] + g[l][i - 1] * g[i + 1][r] % mod * C(r - l, i - l) % mod) % mod;
}
}
signed main(){
Pre();
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= n + 1; i++) pre[i] = n + 1;
for (int i = n; i >= 1; i--) {
nxt[i] = pre[a[i]];
pre[a[i]] = i;
}
for (int i = 1; i <= n; i++) {
pos[i][i] = i;
for (int j = i + 1; j <= n; j++) {
if(a[pos[i][j - 1]] < a[j]) pos[i][j] = j;
else pos[i][j] = pos[i][j - 1];
}
}
dfs(1, n);
printf("%lld\n", g[1][n]);
puts("");
return 0;
}
100pts
对于一段区间 ,如果存在
此时 谁先选择没有关系,因此有
因此当碰到两个最大值连续出现时,直接将整个区间划分为两段,最大值不连续则仍然枚举所有最大值。
时间复杂度
T3 与巨#
定义无穷序列
定义函数
其中 为真值函数,当 为真返回 ,否则返回 。
求
数据范围
每个测试点 的和
solution
20pts
直接模拟题意就好。
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int mod = 998244353;
const int MAXN = 1e7 + 8;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int T, n, c, f[MAXN];
char s[MAXN];
int G(int x) {
int ret = 0;
while(x) {ret++, x >>= 1;}
return (1 << ret) - 1;
}
signed main(){
T = read();
while(T--) {
scanf("%s%lld", s + 1, &c);
n = 0;
for (int i = 1; i <= s[i]; i++) n = n * 2 + s[i] - '0';
int Ans = 0;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1];
if(((i * c) & G(i)) == i) f[i] = i;
}
for (int i = 1; i <= n; i++)Ans = (Ans + f[i]) % mod;
cout<<Ans<<"\n";
}
puts("");
return 0;
}
100pts
设 (必然是这样的形式,其中 为 二进制的最高位),则 相当于 ,则:
设 ,则 需要含有因子 。
设 ,我们可以枚举 , 如果 则 内的所有数最高位都为 ,设 , 含有因子 即 的低于 位的全为 ,这样的数为 , 这是个 项的等差数列,很容易求和,同时每个数对答案贡献了 次。
当 时,由于 , 我们需要满足,,对这个 项的等差数列计算贡献后,由于最后一项被计数了 次(计数范围为),此时多计数了 次,把这个贡献减掉就好了。
时间复杂度
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 1e7 + 5;
const int mod = 998244353;
ll read(){
ll x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
ll pow[MAXN], c, T, n, p;
char s[MAXN];
ll calc(ll s, ll d, ll n) {
return (s * n % mod + d * n % mod * (n + mod - 1) % mod * (mod - mod / 2) % mod) % mod;
}
int main(){
pow[0] = 1;
for (int i = 1; i <= MAXN - 1; i++) pow[i] = (pow[i - 1] << 1) % mod;
T = read();
while(T--){
scanf("%s%lld", s + 1, &c);
n = strlen(s + 1);
c -= 1;
ll Ans = 0;
if(c == 0){
for (int i = 1; i <= n; i++) Ans = ((Ans << 1) + s[i] - '0') % mod;
Ans = Ans * (Ans + 1) % mod * (mod - mod / 2) % mod;
printf("%lld\n", Ans);
continue;
}
if(c & 1) {puts("0"); continue;}
p = 0;
while(c % 2 == 0) p++, c /= 2;
for (int t = 0; t < n; t++) {
int g = max(0ll, t + 1 - p);
if(t < n - 1) //正好的时候
Ans = (Ans + pow[g] * calc(pow[t], pow[g], (pow[t + 1 - g] + mod - pow[t - g])) % mod);
else {//计算剩余部分
ll tot = 0;
for (int i = 1; i <= n - g; i++) tot = ((tot << 1) + s[i] - '0') % mod;
tot = (tot + 1 + mod - pow[t - g]) % mod;
Ans = (Ans + pow[g] * calc(pow[t], pow[g], tot)) % mod;
ll res = (pow[t] + (tot + mod - 1) * pow[g] % mod) % mod;
ll l = 0;
for (int i = 1; i <= n; i++) l = ((l << 1) + s[i] - '0') % mod;
int r = (res + pow[g] + mod - 1) % mod;
Ans = (Ans + mod - (r + mod - l) % mod * res % mod) % mod;
}
}
printf("%lld\n", Ans);
}
puts("");
return 0;
}
100pts
T4 矩阵学说#
题目描述
给出 行 列的矩阵,第 行第 列的元素为 ,找出满足以下条件的三元组{(i,j,x)}(i,j,x)的数量:
- 矩阵的左上角 到右下角 恰好含有 个不同的整数。
数据范围
solution
用 记录颜色种类。
在边长扩展的过程中,不同数量的数只会增加不会减少,具有单调性。于是可以二分找到不同数量 的最大边长 和不同数量 的最大边长 , 即为固定一个左上角符合条件的正方形的数量。
这样需要处理 个查询,每个查询查询一个正方形内不同颜色的数量,由于是正方形,故可以采用二维 表来做。
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <bitset>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 1510;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int n, m, k, lg[MAXN];
bitset<100>f[14][MAXN][MAXN];
void ST(){
for (int k = 1; (1 << k) <= max(n, m); k++)
for (int i = 1; i + (1 << k) - 1 <= n; i++)
for (int j = 1; j + (1 << k) - 1 <= m; j++)
f[k][i][j] = f[k - 1][i][j] | f[k - 1][i + (1 << k - 1)][j] | f[k - 1][i + (1 << k - 1)][j + (1 << k - 1)] | f[k - 1][i][j + (1 << k - 1)];
}
int Query(int x_1, int y_1, int x_2, int y_2) {
int k = lg[x_2 - x_1 + 1];
return (f[k][x_1][y_1] | f[k][x_2 - (1 << k) + 1][y_2 - (1 << k) + 1] | f[k][x_1][y_2 - (1 << k) + 1] | f[k][x_2 - (1 << k) + 1][y_1]).count();
}
ll work(int k) {
if(k == 0) return 0;
ll Ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int l = 1, r = min(n - i + 1, m - j + 1), ret;
while(l <= r) {
int mid = (l + r + 1) >> 1;
if(Query(i, j, i + mid - 1, j + mid - 1) <= k) ret = mid, l = mid + 1;
else r = mid - 1;
}
Ans += ret;
}
}
return Ans;
}
int main(){
for(int i = 1; i < MAXN; i++) lg[i] = lg[i - 1] + (1 << lg[i - 1] == i), lg[i - 1]--;
n = read(), m = read(), k = read();
for (int i = 1; i <= n; i++) {
for (int j = 1, x; j <= m; j++) {
x = read();
f[0][i][j][x - 1] = 1;
}
}
ST();
printf("%lld", work(k) - work(k - 1));
puts("");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话