nowcoder Week Contest
52
小红有 个数字 和一个空字符串 。现在她需要执行 次操作:第 次操作需要将 按照数位上的相对顺序、从左到右的取出并依次插入 (在 中不需要连续,但需要保持原有相对顺序)。 小红想要构造一个这样的字符串 ,使得它的字典序是所有合法构造方案中最大的。
2
92 86
9862
小学哥完美的优先队列 直接给搞成模拟了
#include <bits/stdc++.h>
typedef long long ll;
std::priority_queue< std::pair<char,std::string> > q;
void solve() {
int n;
std::cin >> n;
for(int i = 1; i <= n; i++) {
std::string s;
std::cin >> s;
q.push({s[0],s});
}
std::string ans;
while(!q.empty()) {
auto [c,s] = q.top();
q.pop();
ans += c;
s.erase(0,1);
if(s != "") {
q.push({s[0],s});
}
}
std::cout << ans <<'\n';
return;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _;
while(_ --) {
solve();
}
return 0;
}
51
给定一个 n×n
的矩阵,矩阵中的每个元素都是正整数,小红当前位于左上角 (1, 1)
,每次可以从 (x, y)
走到 (x+1, y)
、(x, y+1)
、(x-1, y)
、(x, y-1)
,但不能走出矩阵。小红希望找到一条到右下角 (n, n)
的路径,定义路径权值为路径上经过的每个点的最大值,求所有路径中的最小路径权值。
以为是dp 问题 却没发现是 二分最大点值 判断能否从起点到终点即可
#include <bits/stdc++.h>
typedef long long ll;
int a[1000][1000];
std::vector< std::pair<int,int> > d = {{0,1},{0,-1},{1,0},{-1,0}};
bool vis[600][600];
int n;
void dfs(int x,int y,int w) {
if(a[x][y] > w || vis[x][y] || vis[n][n]) return;
vis[x][y] = 1;
for(auto [i,j] : d) {
i += x; j += y;
if(vis[i][j] || i < 1 || i > n || j < 1 || j > n || a[x][y] > w) continue;
dfs(i,j,w);
}
}
void solve() {
std::cin >> n;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
std::cin >> a[i][j];
}
}
if(n == 1) {
std::cout << a[1][1] << '\n';
return;
}
int l = 1, r = 1e9, x = 0;
while(l <= r) {
int mid = (l + r) >> 1;
std::memset(vis,0,sizeof(vis));
dfs(1,1,mid);
if(vis[n][n]) {
r = mid - 1;
x = mid;
}
else l = mid + 1;
}
std::cout << x << '\n';
return;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _;
while(_ --) {
solve();
}
return 0;
}
学习到了 遍历四个方向
std::vector< std::pair<int,int> > d = {{0,1},{0,-1},{1,0},{-1,0}};
for(auto [i,j] : d) {
i += x; j += y;
if(vis[i][j] || i < 1 || i > n || j < 1 || j > n || a[x][y] > w) continue;
dfs(i,j,w);
}
**50 **
小红有一棵 n
个点的树,根节点为 1
,有一个物块在根节点上,每次它会等概率随机移动到当前节点的其中一个子节点,而后等概率随机传送到一个同深度节点,若此时它位于叶子节点,则停止移动。 求其移动到子节点的次数的期望值,答案对 998244353
取模。
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
const int mod = 998244353;
ll ksm(ll a,ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
std::vector<int> e[N];
int a[N],b[N],dep[N];
ll p[N];
ll ans = 0;
int maxdep = -1;
void dfs(int u,int fa) {
dep[u] = dep[fa] + 1;
a[dep[u]] += 1;
maxdep = std::max(maxdep,dep[u]);
b[dep[u]] += (u != 1 && e[u].size() == 1);// 仅存在一条回边 说明子叶节点
for(int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(v == fa) continue;
dfs(v,u);
}
}
void solve() {
int n;
std::cin >> n;
if(n == 1) {
std::cout << 0 << '\n';
return;
}
for(int i = 1,u,v; i <= n - 1; i++) {
std::cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1,0);
p[1] = 1;
for(int i = 2; i <= maxdep; i++) {
p[i] = p[i-1] * (a[i] - b[i]) % mod * ksm(a[i],mod - 2) % mod;
}
for(int i = 1; i <= maxdep; i ++) {
ans += p[i];
ans %= mod;
}
std::cout << ans <<'\n';
return;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _;
while(_ --) {
solve();
}
return 0;
}
一般除法取模转化为乘法逆元就用费马小定理就好
49
区间所有整数的异或和
异或有结合律,且 异或和为0
#include <bits/stdc++.h>
typedef long long ll;
ll ntum(ll x) {
// 0 1 2 3 ^ -> 0
ll res = 0; // 5 ->(0 ^ 5 ^ 4)
ll k = (x + 1) % 4; // 次数
while(k--) {
res ^= x;
x --;
}
return res;
}
void solve() {
ll l,r;
std::cin >> l >> r;
std::cout << (ntum(r)^ntum(l-1)) << '\n';
return;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
std::cin >> _;
while(_ --) {
solve();
}
return 0;
}
希望找到一个最小的正整数, 使得对于数组中的某些索引, 满足
#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int p = 13331;
const int mod = 1e9 + 7;
const int N = 2e6 + 50;
ull P[N];
ull h[N];
ull _hash(int l,int r) {
if(l < 0 || r < 0) return 0;
return (h[r] - h[l - 1] * P[r - l + 1] % mod + mod) % mod;
}
void solve() {
int n;
std::cin >> n;
std::vector<ull> a(n+1);
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
}
P[0] = 1;
for(int i = 1; i <= n; i++) {
P[i] = P[i-1] * p; P[i] %= mod;
h[i] = h[i-1] * p + a[i]; h[i] %= mod;
}
for(int k = 1; k <= n; k++) {
if( (_hash(1,n-2*k) + _hash(2*k+1,n)) % mod == (2 * _hash(k+1,n-k)) % mod ) {
std::cout << k <<'\n';
break;
}
}
return;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _;
while(_ --) {
solve();
}
return 0;
}
46
Taki买了 n
种猫粮,第 i
种猫粮的营养值为 a[i]
、数量为 b[i]
。猫猫的饭量是无穷的,每一天她可以吃任意数量的猫粮,但同一种猫粮她一天只会吃一次。Taki想知道在 k
天内,猫猫可以获得的最大营养值之和是多少。
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
const int p = 1e9 + 7;
struct node{
int a,b;
};
void solve()
{
int n;
std::cin >> n;
std::vector<node> t(n+1);
for(int i = 1; i <= n; i++) {
std::cin >> t[i].a;
}
for(int i = 1; i <= n; i++) {
std::cin >> t[i].b;
}
std::sort(t.begin()+1,t.end(),[](node A,node B){
return A.b < B.b;
});
int q,k;
std::vector<ll> sum(n+1),pre(n+2);
for(int i = 1; i <= n; i++) {
sum[i] = sum[i-1] + 1ll * t[i].a * t[i].b;
}
for(int i = n; i >= 1; i--) {
pre[i] = pre[i+1] + t[i].a;
}
std::cin >> q;
while(q --) {
std::cin >> k;
int l = 1,r = n,x = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(t[mid].b <= k) {
l = mid + 1;
x = mid;
}
else r = mid - 1;
}
std::cout << sum[x] + 1ll * pre[x+1] * k << '\n';
}
return;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
多组数据, 有两个数字 x
, y
,她想知道,有多少种方式可以将 x
拆成 y
个正整数的乘积。 例如 x=6
, y=2
时,有 6×1=6
, 3×2=6
, 2×3=6
, 1×6=6
这 4 种方法。 由于这个答案可能很大,因此你需要输出答案对 10^9 + 7
取模后的结果。
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
const int p = 1e9 + 7;
ll fac[1001];
ll ksm(ll a,ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
void init() {
fac[0] = 1;
for(int i = 1; i < 1000; i ++) {
fac[i] = fac[i - 1] * i % p;
}
return;
}
void solve()
{
int A,B;
std::cin >> A >> B;
// A 拆成 B 个 数 乘积 方案数 [12,1] 与 [1,12] 不同
std::vector<int> d;
for(int i = 2; i * i <= A; i ++) {
if(A % i == 0) {
int cnt = 0;
while(1) {
A /= i;
cnt ++;
if(A % i != 0) break;
}
d.push_back(cnt);
}
}
if(A > 1) d.push_back(1);
ll ans = 1;
for(auto n : d) {
ll temp = 1;
for(ll i = B; i <= B - 1 + n; i++) temp = temp * i % p;
ans = ans * temp % p * ksm(fac[n],p-2);
ans %= p;
}
std::cout << ans << "\n";
return;
}
int main()
{
init();
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
牛客周赛 Round 45
A 签到题
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)
void solve()
{
int a,b,c,d,e;
std::cin >> a >> b >> c >> d >> e;
std::cout << (( (a + b + c + d + e) > 100 ) ? "YES" : "NO") << '\n';
return;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
B
需要转两个弯的签到题
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)
void solve()
{
int n,m;
std::cin >> n >> m;
if(m == 1) { std::cout << "YES"; return; }
if(n % 2 == 0) {
std::cout <<"YES";
return;
}
else if(m & 1) {
std::cout <<"YES";
return;
}
std::cout << "NO";
return;
}
// 3 * 3
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
C 题目很容易理解 枚举 递归 记忆化
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)
int f[N];
bool check(int x) {
if(f[x] == 1) return true;
if(f[x] == -1) return false;
int p = x,cur = 0;
while(p > 0) {
cur += p % 10;
p /= 10;
}
if(cur & 1) {
f[x] = -1;
return false;
}
if(cur == x) {
f[x] = 1;
return true;
}
if(cur < x && check(cur) == true) {
f[x] = 1;
return true;
}
return false;
}
void solve()
{
int n,m;
std::cin >> n;
int ans = 0;
for(int i = 1; i <= n; i++) {
if(check(i)) ans++;
}
std::cout << ans << '\n';
return;
}
// 3 * 3
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
D 思维题 算是 核心代码很短但却越容易写错
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)
int pos[N];
void solve()
{
int n,k;
std::cin >> n >> k;
std::vector<int> a(n+1);
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
}
int j = 0;
ll ans = 0;
for(int i = 1; i <= n; i++) {
if(pos[a[i]] && i - pos[a[i]] > k) {
j = std::max(j,pos[a[i]]);
}
ans += i - j;
pos[a[i]] = i;
}
std::cout << ans << "\n";
return;
}
// 3 * 3
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
注意更换 的位置时 不要用上一次出现的位置简单替换 一定注意 j 是递增的
最关键一行 十八行
E 算是思维题
简单描述一下题意
树 问有多少个点到其余所有点的距离都小于等于 2
一个点 开始 所有与它直接相连的点(u) + 1
所有与u 直接相连的点 加上
看看sum 是否为 n - 1
不会有多算的 因为树上无环
#include <bits/stdc++.h>
typedef long long ll;
const int N = 3e5 + 50;
#define popcount(x) __builtin_popcount(x)
void solve()
{
int n;
std::cin >> n;
std::vector<int> v[N];
for(int i = 1,x,y; i < n; i++) {
std::cin >> x >> y;
v[x].push_back(y);
v[y].push_back(x);
}
ll ans = 0;
for(int i = 1; i <= n; i++) {
ll sum = 0;
for(auto u : v[i]) {
sum += 1;
sum += v[u].size() - 1;
}
if(sum == n - 1) ans++;
}
std::cout << ans << '\n';
return;
}
// 3 * 3
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
可笑的是我还分析性质 什么深度大于5一定没有解了
满足性质的答案一定在 某一层或者某两层 然后枚举这两层 出题人会不知道重点构图这两层吗 唉
F
纯粹的补体力
小橙拥有了一张随机生成的竞赛图,他喜欢图上的三元环,即由三个点组成环,请你帮她求出图上有多少个三元环。
竞赛图是一个有向图,任意不同的两点间都恰好有一条单向边。也就是一共有 条有向边。
伪代码
def rnd():
ret = seed
seed = (seed * 7 + 13) mod 1000000007
return ret mod 2
for i = 1 to n - 1:
for j = i + 1 to n:
if rnd() == 0:
add_edge(i, j) # 从i到j添加一条有向边
else:
add_edge(j, i) # 从j到i添加一条有向边
思维题
一个符合要求的三元环 其中每个点都符合出度 = 入度 = 1
一个竞赛图有多少个三元环: 个
什么条件不符合要求
从一个点 引出两条边 这样三个点一定不符合要求的三元组 数量
相减就好
#include <bits/stdc++.h>
typedef long long ll;
const int N = 3e5 + 50;
const int mod = 1e9 + 7;
#define popcount(x) __builtin_popcount(x)
ll n,seed;
int deg[N];
ll rnd() {
ll ret = seed;
seed = (seed * 7 + 13) % mod;
return ret % 2;
}
void solve()
{
std::cin >> n >> seed;
if(n <= 2) {std::cout << 0 <<'\n'; return; }
for(int i = 1; i < n; i++) {
for(int j = i + 1; j <= n; j++) {
if(rnd() == 0) deg[i]++;
else deg[j]++;
}
}
ll num = 1ll * (n - 1) * 1ll * (n - 2) * 1ll * n / 6;
for(int i = 1; i <= n; i++) {
num -= 1ll * deg[i] * 1ll * (deg[i] - 1) / 2;
}
std::cout << num << "\n";
return;
}
// 3 * 3
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int _ = 1;
//std::cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
竞赛图(tournament graph)是一个有向图,其中每一对顶点之间恰有一条有向边。这意味着对于任意两个不同的顶点 ( u ) 和 ( v ),要么存在从 ( u ) 指向 ( v ) 的有向边 ( (u, v) ),要么存在从 ( v ) 指向 ( u ) 的有向边 ( (v, u) ),但不会同时存在。
竞赛图具有以下一些重要性质:
-
Hamilton路径与Hamilton回路:
- Hamilton路径:一个竞赛图总是存在一个Hamilton路径,即经过所有顶点且每个顶点恰经过一次的有向路径。
- Hamilton回路:一个竞赛图存在Hamilton回路的充要条件是图是强连通的(strongly connected)。这意味着图中的任意一对顶点之间存在一条有向路径。
-
完全竞争图:
- 竞赛图的每一对顶点之间都存在唯一的一条有向边,因此一个n阶的竞赛图(即有n个顶点的竞赛图)总共有 ( \frac{n(n-1)}{2} ) 条边。
-
王氏不等式:
- 在任意一个n阶竞赛图中,存在一个顶点,其出度至少为 ( \lceil \frac{n-1}{2} \rceil )。
-
主顶点(King):
- 在一个n阶竞赛图中,总是存在一个顶点可以通过至多两步到达其他所有顶点。这种顶点称为主顶点(King)。
-
反馈弧集(Feedback Arc Set):
- 竞赛图中的反馈弧集是一个能够将图中的所有环破坏的最小有向边集。对于竞赛图,可以通过某种排序(即线性排序)来找到一个最小的反馈弧集。
-
局部性和全局性:
- 竞赛图的许多全局性质可以通过局部性质来推导。例如,如果在一个竞赛图的某个子集中存在Hamilton路径,那么在整个竞赛图中也可能存在Hamilton路径。
-
强连通性:
- 如果一个竞赛图是强连通的,则从任意一个顶点出发都可以到达其他任意顶点。这是竞赛图存在Hamilton回路的一个必要条件。
竞赛图在理论计算机科学、组合优化和图论中有着广泛的应用。例如,它们可以用于表示锦标赛的赛程安排、对比较复杂系统的优劣关系建模等。 by chatgpt
牛客周赛 41
A: 签到
#include <iostream>
using namespace std;
int a,b,c;
int main() {
cin >> a >> b >> c;
int ans = 0;
int pot = min(a-b,c-b);
cout << ((pot < 0) ? ans : pot) << "\n";
return 0;
}
B: 签到构造题 将第k个变成第一个 顺延前面的即可
注意 k = 1 输出 -1
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 2e6 + 55;
int n,k;
int a[N];
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n >> k;
if(n < k || k == 1) {
cout << -1 << "\n";
return 0;
}
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
cout << a[k] << " ";
for(int i = 1; i < k; i++) {
cout << a[i] <<" ";
}
for(int i = k + 1; i <= n; i++)
cout << a[i] << " ";
cout << "\n";
//system("pause");
return 0;
}
C: 循环移位 看第多少次是4的倍数
打表4的倍数 发现只需看 最后两位的十进制是不是4的倍数 因为可以把大数拆分成100 的倍数 和 小于100 的数的和
需要对 小于10的串特判即可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 50;
const int M = 2007;
const int P = 1e9 + 7,MOD = 998244353;
const long long INF = 1e18;
typedef long long ll;
int n,m,k;
void solve() {
string s;
cin >> s;
int len = s.length() ;
int ans = -1;
char s1[N];
if(len == 1) {
int x = s[0] - '0';
if(x % 4 == 0) cout << 0 << endl;
else cout << -1 << endl;
return;
}
for(int i = 0; i < len; i ++) {
s1[i] = s[i];
}
for(int i = 0; i < len; i ++) {
int x = (s1[len - 2 + i] - '0') * 10 + (s1[len - 1 + i] - '0');
if(x % 4 == 0) {
ans = i;
break;
}
s1[len + i] = s1[i];
}
cout << ans << "\n";
return ;
}
int main() {
int _ = 1;
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//cin >> _;
while(_ --) {
solve();
}
return 0;
}
//
牛客周赛 Round 39
F: 01串 A,B 每次选择某个串 的一个区间 [l,r] 全部 变成1
求 两个字符串 有多少个位置的值都是 1
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
const int P = 1e8;
typedef long long ll;
const int M = 2007;
int n,m,k;
int a[N],b[N];
struct node{
int l,r,c1,c2,c;
// c1: A [l,r] 1 的 个数
// c2: B [l,r] 1 的 个数
// c: Ai & Bi 的串 [l,r] 的 1 的 个数
int lz1,lz2;// 懒标记 A B
}tr[4040404];
void push_up(int u) { // 用儿子更新父亲
tr[u].c = tr[u << 1].c + tr[u << 1 | 1].c;
tr[u].c1 = tr[u << 1].c1 + tr[u << 1 | 1].c1;
tr[u].c2 = tr[u << 1].c2 + tr[u << 1 | 1].c2;
return;
}
void build(int u,int l,int r) {
if(l == r) {
tr[u] = {l,r,a[l],b[l],a[l] & b[l]};
return;
}
else{
tr[u] = {l,r,0,0,0};
int mid = (l + r) >> 1;
build(u << 1,l,mid);
build(u << 1 | 1,mid + 1,r);
push_up(u);
return;
}
}
void push_down(int u) { // 父亲更新儿子
if(tr[u].lz1 ) {
tr[u << 1].c1 = (tr[u << 1].r - tr[u << 1].l + 1);
tr[u << 1].c = tr[u << 1].c2;
tr[u << 1 | 1].c1 = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
tr[u << 1 | 1].c = tr[u << 1 | 1].c2;
tr[u << 1].lz1 = 1;
tr[u << 1 | 1].lz1 = 1;
tr[u].lz1 = 0;
}
if(tr[u].lz2) {
tr[u << 1].c2 = (tr[u << 1].r - tr[u << 1].l + 1);
tr[u << 1].c = tr[u << 1].c1;
tr[u << 1 | 1].c2 = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
tr[u << 1 | 1].c = tr[u << 1 | 1].c1;
tr[u << 1].lz2 = 1;
tr[u << 1 | 1].lz2 = 1;
tr[u].lz2 = 0;
}
}
void modify(int u,int l,int r,int op) { // 区间修改
if(l <= tr[u].l && tr[u].r <= r){
if(op == 1) {
tr[u].c1 = (tr[u].r - tr[u].l + 1);
tr[u].c = tr[u].c2;
tr[u].lz1 = 1;
}
else{
tr[u].c2 = (tr[u].r - tr[u].l + 1);
tr[u].c = tr[u].c1;
tr[u].lz2 = 1;
}
return;
}
push_down(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) modify(u << 1,l,r,op);
if(r > mid) modify(u << 1 | 1,l,r,op);
push_up(u);
}
void solve() {
cin >> n;
string A,B;
cin >> A >> B;
for(int i = 0;i < n;i ++) {
a[i + 1] = A[i] - '0';
b[i + 1] = B[i] - '0';
}
build(1,1,n);// u l r
cin >> m;
char p;
for(int i = 0,l,r;i < m;i ++) {
cin >> p;
int op = (p - 'A' + 1);
cin >> l >> r;
modify(1,l,r,op);
cout << tr[1].c << endl;
}
return;
}
int main() {
int _ = 1;
//cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
牛客周赛 Round 38
A : 签到题
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
int n;
int main() {
cin >> n;
cout << (10 - (n % 10)) % 10;
return 0;
}
B : 需要一个知识点: 若 一个数是 9 的 倍数 ,则 各数位数之和 也是 9 的 倍数
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 40;
typedef long long ll;
string s;
ll sum,ans;
int main() {
cin >> s;
int len = s.length();
for(int i = 0; i < len ; i++) {
sum += (s[i] - '0');
if(sum % 9 == 0) ans++;
}
cout << ans ;
return 0;
}
C : 构造一个长为 ,均为小写字母,恰好有 个 回文子串 的 字符串
被样例骗里,以为只用 OO类字符串不够,开始计算长度 x 的 "OOOOOO" 的贡献 其实没有必要
最后一串shit代码 108.33 / 125 pts
其实只要 枚举 k 个 两个长度的 ,余下的 用一个长度的 填充即可
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
int n,k,pos;
int main() {
cin >> n >> k;
string s = "";
string a = "abc", b = "def";
for(int i = 0,j = 0; i < k; i++) {
s += a[j];
s += a[j];
j = (j + 1) % 3;
}
int j = 0;
while(s.size() < n) {
s += b[j];
j = (j + 1) % 3;
}
cout << s;
return 0;
}
D :每次操作在相邻数组间插入一个数 ,平滑值: 相邻两数差值的绝对值的最大值
让 平滑值 为 K 最少需要 操作几次
142.50 / 150
错误原因 ; 忽略了当 差值远小于 K 时 当且仅当需要一次操作 使得平滑值 为 K
我代码默认了 小于K时 操作次数不添加
举例 : 1 3 5 7 12
1/ 13 /3/ 5/ 7/ 即可
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
ll a[N],c[N];
ll n,k,ans;
int main() {
cin >> n;
cin >> k;
for(ll i = 1; i <= n; i++) {
cin >> a[i];
c[i] = abs(a[i] - a[i-1]);
}
c[1] = 0;
bool flag = false;
for(ll i = 1; i <= n; i++) {
//cout << c[i] << " ";
if(c[i] >= k) flag = true;
ll x = c[i] % k;
ans += c[i] / k;
if(c[i] != 0 && x == 0) ans--;
}
if(flag == false) ans++;
cout << ans;
return 0;
}
E : 长度 为 的数组 , 选择最多的 数 使得能够 构成 等比数列 ,公比 为 正整数 输出 最多的数量
5
3 4 2 1 4
3
最初的设想是 先排序 利用dp ,状态量 是 作为 等比某一项的 最大 长度
但是我 dp 很烂 ,不好写
真的没有想到 能够 枚举 公比
听讲解 : 公比 q 多× 几次 就能超 数据范围 就算 q = 2 ,也仅需 17 次 完全是 常数级的复杂度
注意 q = 1 的 情况;
利用 map 标记 数出现次数 每个 a[i] 枚举 × q 看看 是够存在 但是 必须连续
还有一处 特别巧妙的地方 :::
pow(q,ans) <= 2e5
很多时候 我们 利用 q = 1 或 2 得 到了一个 ans ,已经比较大了 (假设是10),而且已知 。。
这时我们枚举 公比 10 , 那我们根本不用 看10了 ,就算 10为公比 的数在数组中很多 ,但绝对不会多于
对我们要进行的 ans 更新没有任何作用 ,大大降低了时间复杂度
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
const int M = 2007;
int n;
int a[N];
map<int,int> mp;
void solve() {
cin >> n;
int ans = 0;
for(int i = 1; i <= n; i++) {
cin >> a[i];
mp[a[i]]++;
ans = max(ans,mp[a[i]]);
}
for(int q = 2; pow(q,ans) <= 2e5 ; q ++) {
for(int i = 1; i <= n; i++) {
int now = a[i],cnt = 0;
while(now <= 2e5 && mp[now] > 0) {
cnt++;
now *= q;
}
ans = max(ans,cnt);
}
}
cout << ans << endl;
}
int main() {
int _ = 1;
// cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
更新:数据有加强 现在这个方法只能过 96.88%
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 50;
typedef long long ll;
const int M = 2007;
int n;
void solve() {
cin >> n;
int ans = 0;
vector<int> a(N + 1),cnt(N + 1);
for(int i = 1; i <= n; i++) {
cin >> a[i];
cnt[a[i]]++;
}
for(int i = 1; i <= 2e5; i++) {
ans = max(ans,cnt[i]);
if(cnt[i] == 0) continue;
for(int j = i + i; j <= 2e5; j += i) {
if(cnt[j] == 0) continue;
int c = 2;
int now = j;
int q = j / i;
while(now <= 2e5 && cnt[now] > 0) {
ans = max(ans,c);
now *= q;
c ++;
}
}
}
cout << ans << endl;
}
int main() {
int _ = 1;
// cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
新更改的代码时间复杂度还是不会算 ,主要区别在于枚举 q 改为 枚举 a[i] 的 倍数 ,如果有倍数 就可以算出 q 根据连续的原则 等比地去找
**F: ** 进行 次询问,每次问一个区间 是否存在 回文子串 (长度 严格大于 2) (注意子序列 可以不连续 )
当时看到题目时就想到 线段树,莫队 树状数组 ,来找 区间有没有 回文串 ,但是没有做过类似的题目 ,不知道 怎么写 就放弃了
讲解注意: 1 1 2 1 发现 一个区间 只要存在有 不相邻的 两个数相等 就一定能满足条件
la[i] 保存的是 上次出现的坐标 ,其中
只要区间内有 且 值 在 内 即可 区间 最大值 即可
问题回到区间查询最大值 。。 这样 线段树 , RMQ 可以做了
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 50;
typedef long long ll;
const int M = 2007;
int n,q;
int f[M][30];
void solve() {
cin >> n >> q;
vector<int> Log(N),a(N),la(N);
map<int,int> mp;
for(int i = 1; i <= n; i++) {
cin >> a[i];
if(mp.count(a[i])) {
la[i] = mp[a[i]];
}
mp[a[i]] = i;
}
for(int i = n; i ; i--) {
if(la[i] == i - 1)
la[i] = la[la[i]];
}
//for(int i = 1; i <= n; i++) {
// cout << la[i] << " ";
//} cout << endl;
for(int i = 1; i <= n; i++) f[i][0] = la[i];
Log[1] = 0, Log[2] = 1;
for(int i = 3; i <= n; i++) Log[i] = Log[i/2] + 1;
for(int j = 1; j <= Log[n]; j ++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
f[i][j] = max(f[i][j-1],f[i + (1 <<(j-1))][j-1]);
}
}
for(int i = 1; i <= q; i++) {
int l,r;
cin >> l >> r;
int p = Log[r - l + 1];
int pos = max(f[l][p],f[r - (1 << p) + 1][p]);
if(pos >= l) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
int main() {
int _ = 1;
// cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
这里用的是 RMQ ST 倍增 求解 区间最大值
什么是RMQ问题
问题指的是 (区间最大/最小值询问),给定一个区间左端点 和右端点 ,询问 中最值,一般朴素的做法是利用for循环在 中不断判断更新答案,但这样做的时间复杂度是 ,再乘上查询次数后时间复杂度更是非常高,这对我们来说非常不理想,因此有三种快速解决RMQ问题的算法
下面以求区间最大值,一共有 个数据, 次询问为例。
ST表(动态规划+倍增思想)
算法原理:
由于 问题一个可重复贡献问题,而倍增能够快速的覆盖所有元素,所以能够快速地求解出任意区间的最大值,ST表本身是一个数据结构,一般用二维数组来存储。
实现过程(下标从 开始):
令 的含义为以 为起始点(左端点),区间长度为 的最大值。区间表示就是 ,如果将区间一分为而,则得到 和 ,这两个子区间的各自的最大值再取一次最大值就是大区间的最大值
由 明白了某个区间可以划分中点,这个区间最大值可以由左右区间得来,那我们用状态转移方程的语言去表达就是 及 整理就可以得到
对于初始化:我们根据定义 代表以下标 开头并有 个元素的区间最大值,此时不难发现这个区间只有 自身,那么最值肯定也是其本身,所以 ,另外,我们发现 是 的幂, 的取值不应该太大,其极限取值应该是 ,以及我们后续查询也需要将区间长度取对数,为了快速得到 的值,需要对数表数组 ,初始化为 ,往后递推 。
循环范围:发现 由 得来,所以可以外层循环 从 ( 的情况已经在初始化得出),内层循环 从 ,但右端点(即区间结束点)的取值不可以超过 ,由 得到
查询:对于一个区间 ,其长度对数 ,我们可以划分成两个区间(注意这里不能再从中间一分为二了,因为长度不一定是 的幂,奇数长度有可能会决策到额外的数),从左开始为 ,从右开始为 ,这分别代表了「从 开始的 个数」、「从 结尾的 个数」。这两个区间一定能够覆盖 (这里不作证明),故查询结果应该是
最终代码:
注意在刚刚的描述中 只是为了方便描述,实际编码应该用 来写入
G: 给定 长度为 的 数组 ,从 中 删除 一个区间 ,使得剩余拼接起来的区间的 逆序对数量 不少于 , 求删除区间 方案数
常见的求 逆序对有两种方法,归并排序和树状数组。 这道题是明显的有修改趋势,要通过树状数组来求逆序对 。
3 1 2 5 1 2 3 5
树状数组求逆序对的原理是: 类似桶排序 放的位置, 将放的数 看已经有多少 比它大的数已经放过了 ,或者倒着放,看有多少值比它小 。。。 针对值域较小的情况,值域较大的话,需要用到离散化。。。
我们来看 少了一个数 ,逆序对发生了什么变化: 逆序对减少了 左边比它大的,右边比它小的
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
const int M = 2007;
ll n,m,k,cnt;
ll a[N];
vector<ll> tr1(1000001),tr2(1000001);
ll lowbit(ll i) {
return i & (-i);
}
void add(vector<ll> & tr,ll x,ll k) {
for(ll i = x; i <= 1e6; i += lowbit(i)) {
tr[i] += k;
}
}
ll getsum(vector<ll> & tr,ll x) {
ll res = 0;
for(ll i = x; i ; i -= lowbit(i)) {
res += tr[i];
}
return res;
}
void solve() {
cin >> n >> m;
ll sum = 0;
//vector<int> a(n + 1);
for(ll i = 1; i <= n; i++) {
cin >> a[i];
}
for(ll i = n; i ; i --) {
add(tr2,a[i],1);
sum += getsum(tr2,a[i] - 1);
}
// 当前 sum 为 给定原序列的逆序对数
ll ans = (sum >= m);
// 维护 双指针 (滑动窗口) 必需 删除的区间
for(ll i = 1, j = 1; i <= n; i ++) {
add(tr2,a[i],-1); // 删除 a[i]
sum -= getsum(tr2,a[i] - 1);// 左侧的
sum -= getsum(tr1,1e6) - getsum(tr1,a[i]); // 右侧
while(j <= i && sum < m) { // 把 已经删除的a[j] 加上
add(tr1,a[j],1);
sum += getsum(tr2,a[j] - 1);
sum += getsum(tr1,1e6) - getsum(tr1,a[j]);
j ++;
}
ans += (i - j + 1);
}
cout << ans << endl;
}
int main() {
int _ = 1;
//cin >> _ ;
while(_ --) {
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】