考试差不多考完了,过几天还有一场英语
停止训练挺久了,现在暑假开始,可以全身心投入acm了
这一周从周四开始,猛练一波
现在欠了一堆题目没补,先补题吧
周四
An Easy Problem(思维+堆)
这道题有我的思路是对于n * m
(n - 1) * m 和 n * (m - 1) 显然是更小的
所以可以用堆来维护,这一对取出之后就压入比其更小的两个
注意要去重。同时结构体的重载注意一些细节
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
struct node
{
ll x, y;
bool operator < (const node& rhs) const
{
if(x * y == rhs.x * rhs.y) return x < rhs.x; //细节 不然(2, 3) 和(3, 2) 会看作相同
return x * y < rhs.x * rhs.y;
}
};
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
priority_queue<node> q;
q.push(node{n, m});
set<node> s;
while(1)
{
node u = q.top(); q.pop();
if(--k == 0)
{
printf("%lld\n", u.x * u.y);
break;
}
if(s.find(node{u.x - 1, u.y}) == s.end())
{
q.push(node{u.x - 1, u.y});
s.insert(node{u.x - 1, u.y});
}
if(s.find(node{u.x, u.y - 1}) == s.end())
{
q.push(node{u.x, u.y - 1});
s.insert(node{u.x, u.y - 1});
}
}
return 0;
}
写完看了AC代码,发现有写得非常简洁的
这下面这个和我上面的思路是一样的
但是其一开始就把m 2m 3m全部加入堆中
相当于把横坐标都确定了。然后每次新加入的都是改变纵坐标
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
priority_queue<pair<ll, ll>> q;
_for(i, 1, n) q.push(make_pair(i * m, i));
while(--k)
{
pair<ll, ll> u = q.top(); q.pop();
q.push(make_pair(u.first - u.second, u.second));
}
printf("%lld\n", q.top().first);
return 0;
}
周五
An Easy Problem(二分答案)
还有一个思路是二分答案,这倒挺骚的
因为如果固定了面积,很容易算出小于等于这个面积的有多少种可能
知道了小于等于这个面积的个数,那么用总数减去这个,就是大于当前面积的个数
如果第k大,那么大于它的就有k-1个
写的时候有个细节
拿样例来说,1 2 2 3 3 4 6 6 9
这时4 和 5 都是有3个大于它的数,但是显然4是答案而5不是
这个时候这么处理
其实就是取等号的时候要往左移,而取等号是满足条件的
那二分答案
那就设为0 0 0 1 1 1 1的形式
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
int n, m, k;
bool check(ll key)
{
ll cnt = 0;
_for(i, 1, n)
cnt += min(key / i, 1LL * m);
return 1LL * n * m - cnt <= k - 1;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
ll l = 0, r = 1e13;
while(l + 1 < r)
{
ll m = l + r >> 1;
if(check(m)) r = m;
else l = m;
}
printf("%lld\n", r);
return 0;
}
Double(时间复杂度)
这题很容易想到一个暴力的做法,就是枚举每一个点看它能不能留到最后
但是当时觉得这样暴力显然会超时
比赛的时候队友告诉我一直乘2很快就会乘到很大的数,我才醒悟过来
一直乘2乘30次左右就最大了,所以可以很快就结束
因此每次如果乘到比最大数还大的时候就结束了,并不需要遍历完整个区间
甜甜圈(树状数组)
当时我想这道题的时候卡住了,模拟肯定超时。是队友过的
这题可以发现甜甜圈的相对位置是不变的,可以看作转化成一个区间
那么每次就求区间和就行了,注意去掉的区间不能算
所以就是单点修改,区间求和
树状数组就行了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int f[N];
int lowbit(int x) { return x & (-x); }
void add(int x, int p)
{
for(; x < N; x += lowbit(x)) f[x] += p;
}
int s(int x)
{
int res = 0;
for(; x; x -= lowbit(x))
res += f[x];
return res;
}
int sum(int l, int r)
{
if(r < l) return 0;
return s(r) - s(l - 1);
}
int main()
{
int n1, n2;
scanf("%d%d", &n1, &n2);
int mx = 0, t;
vector<pair<int, int>> ve;
_for(i, 1, n1)
{
int x; scanf("%d", &x);
ve.push_back(make_pair(x, n1 - i + 1));
if(mx < x) { mx = x; t = i - 1; }
}
_for(i, 1, n2)
{
int x; scanf("%d", &x);
ve.push_back(make_pair(x, n1 + i));
if(mx < x) { mx = x; t = i - 1; }
}
sort(ve.begin(), ve.end(), greater<pair<int, int>>());
ll ans = t;
_for(i, 1, n1 + n2) add(i, 1);
rep(i, 0, ve.size() - 1)
{
int p1 = ve[i].second, p2 = ve[i + 1].second;
ans += sum(min(p1, p2) + 1, max(p1, p2) - 1);
add(p1, -1);
}
printf("%lld\n", ans);
return 0;
}
Don't Really Like How The Story Ends (dfs + 栈)
这道题依然是队友做的
这道题要理解dfs的栈的过程
https://blog.csdn.net/weixin_45717583/article/details/117512217
这哥们写的很好
分几种情况
如果v直接与v+1相连就直接访问
如果v存在未访问的点,且不是v+1,这个时候必须连一条边
如果v的点全部都是访问过的,说明到底了,这个时候可以退栈,退到存在有未访问过的点的节点,然后就转化为前面两条
全部遍历过一遍之后,如果还有未访问过的点,说明不连通,这时就直接从第1个点连一条边到其他连通分量
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int n, m, ans, now;
void dfs(int u)
{
now++; //每次now++到下一个点
rep(i, 0, g[u].size())
{
int v = g[u][i];
if(v < now) continue; //肯定是访问过的点
else if(v == now) dfs(v);
else
{
ans++;
dfs(now);
i--; //这个点相当于没访问
}
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
_for(i, 1, n) g[i].clear();
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
_for(i, 1, n) sort(g[i].begin(), g[i].end()); //边排序
now = 1;
ans = 0;
dfs(1);
while(now <= n) //最后还剩下点没遍历,说明不连通
{
dfs(now);
ans++;
}
printf("%d\n", ans);
}
return 0;
}
二维树状数组模板
比赛时遇到了树状数组套树状数组,练一练
#include <bits/stdc++.h>
#define rep(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e3;
int f[MAXN][MAXN], n, m, q;
int lowbit(int x) { return x & (-x); }
void add(int x, int y, int p)
{
for(; x <= n; x += lowbit(x))
for(int i = y; i <= m; i += lowbit(i))
f[x][i] += p;
}
int s(int x, int y)
{
int res = 0;
for(; x; x -= lowbit(x))
for(int i = y; i; i -= lowbit(i))
res += f[x][i];
return res;
}
int sum(int x1, int y1, int x2, int y2)
{
return s(x2, y2) - s(x2, y1 - 1) - s(x1 - 1, y2) + s(x1 - 1, y1 - 1);
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
_for(i, 1, n)
_for(j, 1, m)
{
int x; scanf("%d", &x);
add(i, j, x);
}
while(q--)
{
int op, x1, y1, x2, y2, p;
scanf("%d", &op);
if(op == 1) scanf("%d%d%d", &x1, &y1, &p), add(x1, y1, p);
else
{
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", sum(x1, y1, x2, y2));
}
}
return 0;
}
P4054 [JSOI2009]计数问题(二维树状数组)
二维树状数组练一下
这道题c很小只有100,直接暴力开100个二维树状数组就行了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 310;
const int M = 110;
int f[M][N][N], a[N][N], n, m, q;
int lowbit(int x) { return x & -x; }
void add(int c, int x, int y, int p)
{
for(int i = x; i <= n; i += lowbit(i))
for(int j = y; j <= m; j += lowbit(j))
f[c][i][j] += p;
}
int s(int c, int x, int y)
{
int res = 0;
for(int i = x; i; i -= lowbit(i))
for(int j = y; j; j -= lowbit(j))
res += f[c][i][j];
return res;
}
int sum(int c, int x1, int y1, int x2, int y2)
{
return s(c, x2, y2) + s(c, x1 - 1, y1 - 1) - s(c, x2, y1 - 1) - s(c, x1 - 1, y2);
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
_for(j, 1, m)
{
scanf("%d", &a[i][j]);
add(a[i][j], i, j, 1);
}
scanf("%d", &q);
while(q--)
{
int op; scanf("%d", &op);
if(op == 1)
{
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
add(a[x][y], x, y, -1);
add(c, x, y, 1);
a[x][y] = c;
}
else
{
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d", &x1, &x2, &y1, &y2, &c);
printf("%d\n", sum(c, x1, y1, x2, y2));
}
}
return 0;
}
poj 2155(二维差分 + 二维树状数组)
首先要发现一个性质,0操作了奇数次是1,偶数次是0
所以其实问题就转化为,每次让一个矩阵加1,然后每次询问一个点加了多少次
二维树状数组可以解决二维上的单点修改,每次查询一个矩阵的和
用二分维差分,就可以转化为每次一个矩阵的每个数加上一个值,每次询问一个点的值是多少
二维差分就模仿一维差分的写法就好
一维差分是d[i] = a[i] - a[i - 1]
求值是前缀和a[i] = d[1]~d[i]
二维差分就是 d[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1]
求值是前缀和 a[i][j] = d[1][1] ~ d[i][j]
一维差分每次修改会改两个地方
二维差分每次修改会改四个地方,画个图按照d[i][j]定义就知道了
#include <cstdio>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e3 + 10;
int f[N][N], n, q;
int lowbit(int x) { return x & -x; }
void add(int x, int y, int p)
{
for(int i = x; i <= n; i += lowbit(i))
for(int j = y; j <= n; j += lowbit(j))
f[i][j] += p;
}
int sum(int x, int y)
{
int res = 0;
for(int i = x; i; i -= lowbit(i))
for(int j = y; j; j -= lowbit(j))
res += f[i][j];
return res;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(f, 0, sizeof f);
scanf("%d%d", &n, &q);
while(q--)
{
char op[5];
scanf("%s", op);
if(op[0] == 'C')
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
add(x1, y1, 1);
add(x2 + 1, y2 + 1, 1);
add(x1, y2 + 1, -1);
add(x2 + 1, y1, -1);
}
else
{
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", sum(x, y) % 2);
}
}
puts("");
}
return 0;
}
Kera's line segment(二维树状数组 + 转化为二维平面)
这道题就是省赛遇到的题
这道题的关键在于L <= l r <= R
这个区间完全包含的等式,转化到二维平面上
区间(l, r)转化为点(l, r)
这样每次就是询问右下区域的点
那么可以用二维树状数组。
首先可以翻转一下转化为左下区间的点
二维树状数组原来是不能求区间最值的 因为不像求和那样可以直接s(r) - s(l - 1)
除非每次的最值都是前缀最值,即(1, r)的最值
这道题刚好满足,每次求的最值都是(1, 1) 到(x,y)的最值
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 3000 + 10;
int f1[N][N], f2[N][N], n, q;
int lowbit(int x) { return x & -x; }
void add(int x, int y, int p)
{
for(int i = x; i <= 3000; i += lowbit(i))
for(int j = y; j <= 3000; j += lowbit(j))
{
f1[i][j] = max(f1[i][j], p);
f2[i][j] = min(f2[i][j], p);
}
}
int query(int x, int y)
{
int mx = 0, mi = 1e9;
for(int i = x; i; i -= lowbit(i))
for(int j = y; j; j -= lowbit(j))
{
mx = max(mx, f1[i][j]);
mi = min(mi, f2[i][j]);
}
return mx - mi;
}
int main()
{
memset(f2, 0x3f, sizeof f2);
scanf("%d%d", &n, &q);
_for(i, 1, n)
{
int l, r, v;
scanf("%d%d%d", &l, &r, &v);
l = 3000 - l + 1;
add(l, r, v);
}
int last = 0;
while(q--)
{
int op, l, r, v;
scanf("%d%d%d", &op, &l, &r);
l ^= last; r ^= last;
l = 3000 - l + 1;
if(op == 1) scanf("%d", &v), add(l, r, v);
else printf("%d\n", last = query(l, r));
}
return 0;
}
Byfibonacci(01背包拓展)
比赛的时候我想到了dp
也就是dp[i] = dp[i - k] * k
但是这样有两个问题,一个是会超时,一个是会重复选一个数
然后就卡住了
赛后看题解,dp是正解之一
怎么解决上面这两个问题呢
一个数只能选一次的话,用到了01背包的思路
把数看作容量
价值的更更新方式就是上面那个dp方程
模仿01背包,容量逆序,这样就可以保证一个数只选了一次
然后这个01背包是要刚好填满的,也就是说有很多不合法的状态
因为转移的时候是乘法,所以不合法为0就可以了
因此也有了一个优化,就是第二维一开始从前几个容量的和开始,而不是从1e7,也就是总容量开始。这就解决了超时的问题
这道题是一个数拆成几个数,利用了01背包的思路
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e7 + 10;
const int mod = 998244353;
int dp[N], f[40], s[40];
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
int main()
{
f[0] = f[1] = 1; s[0] = 1; s[1] = 2;
_for(i, 2, 40)
{
f[i] = add(f[i - 1], f[i - 2]);
s[i] = add(s[i - 1], f[i]);
}
dp[0] = 1;
_for(j, 0, 34)
for(int i = min((int)1e7, s[j]); i >= f[j]; i--)
dp[i] = add(dp[i], mul(dp[i - f[j]], f[j]));
int T; scanf("%d", &T);
while(T--)
{
int x; scanf("%d", &x);
printf("%d\n", dp[x]);
}
return 0;
}
周六
History(分层图最短路)
感觉比赛时我认真思考是可以做出来的。这题在比赛时主要是队友在想,我连题意都没有太弄清楚
这个边长度的变换,猜它有循环。打表发现每4个就是一个循环
所以就分层图最短路就好了,分4层
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
struct node
{
int v; ll w;
bool operator < (const node& rhs) const
{
return w > rhs.w;
}
};
vector<node> g[N << 2];
int n, m, p;
ll d[N << 2];
int add(int a, int b) { return (a + b) % p; }
int mul(int a, int b) { return 1LL * a * b % p; }
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int inv(int x)
{
if (x < 0) x += p;
return binpow(x, p - 2);
}
ll solve()
{
priority_queue<node> q;
_for(i, 1, 4 * n) d[i] = 1e18;
d[1] = 0;
q.push(node{1, d[1]});
while(!q.empty())
{
node x = q.top(); q.pop();
int u = x.v;
if(x.w != d[u]) continue;
for(auto t: g[u])
{
int v = t.v; ll w = t.w;
if(d[v] > d[u] + w)
{
d[v] = d[u] + w;
q.push(node{v, d[v]});
}
}
}
ll ans = 1e18;
_for(i, 1, 4)
ans = min(ans, d[i * n]);
return ans;
}
int main()
{
scanf("%d%d%d", &n, &m, &p);
while(m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
_for(i, 0, 3)
{
g[i * n + u].push_back(node{((i + 1) % 4) * n + v, w});
w = mul(1 + w, inv(1 - w));
}
}
printf("%lld\n", solve());
return 0;
}
Birthday Cake(双哈希)
一直没学哈希,今天搞一搞哈希
这道题是比赛的一道题
思路其实蛮简单的,但是我把string作为map的键值,超时了
因为其实string比较的时候要一个一个比,就很慢
那么这个时候就可以用哈希,把string转化成整数,整数比较就很快
处理过程类似131进制数
哈希有三种方法,自然溢出,单哈希,双哈希
自然溢出就是利用ull的自动取模,单哈希就是模一个质数如1e9 + 7
双哈希就是模两个质数如1e9 + 7 1e9 + 9
自然溢出和单哈希都可能被出题人卡,但是双哈希就很安全
这道题一开始用自然溢出写
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
const int base = 131;
map<ull, int> mp;
ull Hash[N], p[N];
string s[N];
int n;
ull get_hash(string s)
{
ull res = 0;
int len = s.size();
rep(i, 0, len)
res = res * base + s[i];
return res;
}
ull cut(int l, int r) //计算某一个子串的哈希值,记住公式
{
if(l - 1 < 0) return Hash[r]; //注意负数的情况
return Hash[r] - Hash[l - 1] * p[r - l + 1];
}
int main()
{
p[0] = 1;
_for(i, 1, 4e5) p[i] = p[i - 1] * base; //初始化p数组 表示base的p次方 cut用到
ll ans = 0;
scanf("%d", &n);
_for(i, 1, n)
{
cin >> s[i];
ll t = get_hash(s[i]);
if(mp[t]) ans += mp[t];
mp[t]++;
}
_for(i, 1, n)
{
string t = s[i];
int len = t.size();
Hash[0] = t[0];
rep(i, 1, len) Hash[i] = Hash[i - 1] * base + t[i];
_for(k, 1, len / 2)
if(cut(0, k - 1) == cut(len - k, len - 1))
ans += mp[cut(k, len - k - 1)];
}
printf("%lld\n", ans);
return 0;
}
过了97%的点,被卡了
之后用双哈希写,过了。
双哈希就是两种模数,得出的两个答案用pair存
哈希还可以用来O(1)时间判断两个子串是否相等
O(n)预处理
#include <bits/stdc++.h>
#define pa pair<ull, ull>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
const int base = 131;
const int mod1 = 1e9 + 7;
const int mod2 = 998244353;
map<pa, int> mp;
ull Hash[N][2], p[N][2];
string s[N];
int n;
pa get_hash(string s)
{
ull res1 = 0, res2 = 0;
int len = s.size();
rep(i, 0, len)
{
res1 = (res1 * base % mod1 + s[i]) % mod1;
res2 = (res2 * base % mod2 + s[i]) % mod2;
}
return make_pair(res1, res2);
}
pa cut(int l, int r) //计算某一个子串的哈希值,记住公式
{
if(l - 1 < 0) return make_pair(Hash[r][0], Hash[r][1]);
ull res1 = (Hash[r][0] - Hash[l - 1][0] * p[r - l + 1][0] % mod1 + mod1) % mod1;
ull res2 = (Hash[r][1] - Hash[l - 1][1] * p[r - l + 1][1] % mod2 + mod2) % mod2;
return make_pair(res1, res2);
}
int main()
{
p[0][0] = p[0][1] = 1;
_for(i, 1, 4e5)
{
p[i][0] = p[i - 1][0] * base % mod1;
p[i][1] = p[i - 1][1] * base % mod2;
}
ll ans = 0;
scanf("%d", &n);
_for(i, 1, n)
{
cin >> s[i];
pa t = get_hash(s[i]);
if(mp[t]) ans += mp[t];
mp[t]++;
}
_for(i, 1, n)
{
string t = s[i];
int len = t.size();
Hash[0][0] = Hash[0][1] = t[0];
rep(i, 1, len)
{
Hash[i][0] = (Hash[i - 1][0] * base % mod1 + t[i]) % mod1;
Hash[i][1] = (Hash[i - 1][1] * base % mod2 + t[i]) % mod2;
}
_for(k, 1, len / 2)
if(cut(0, k - 1) == cut(len - k, len - 1))
ans += mp[cut(k, len - k - 1)];
}
printf("%lld\n", ans);
return 0;
}
白兔的字符串(hash)
找几道哈希的题做一做
这道题首先要解决一个循环的问题
倍长取子串就好了
然后把值都加入一个unordered_map中
开始加入set中T了,map也T
然后就暴力枚举每个串的子串就行了
用自然溢出,写起来方便
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef unsigned long long ull;
const int base = 131;
const int N = 2e6 + 10;
ull Hash[N], p[N];
unordered_map<ull, bool> vis;
string s;
int n;
ull cut(int l, int r)
{
if(l - 1 < 0) return Hash[r];
return Hash[r] - Hash[l - 1] * p[r - l + 1];
}
int main()
{
p[0] = 1;
rep(i, 1, N) p[i] = p[i - 1] * base;
cin >> s;
int len = s.size();
s = s + s;
Hash[0] = s[0];
rep(i, 1, len * 2) Hash[i] = Hash[i - 1] * base + s[i];
rep(i, 0, len) vis[cut(i, i + len - 1)] = 1;
cin >> n;
while(n--)
{
cin >> s;
int t = s.size(), ans = 0;
Hash[0] = s[0];
rep(i, 1, t) Hash[i] = Hash[i - 1] * base + s[i];
rep(i, 0, t)
{
if(i + len - 1 >= t) break;
ans += vis[cut(i, i + len - 1)];
}
cout << ans << endl;
}
return 0;
}
K串 (莫队 + 哈希 + 前缀和)
这题非常精彩,涉及到了挺多知识点
莫队和哈希我都会,但是我没有想到用前缀和的思想,导致没做出来
区间[l, r]中每个字母的个数是k的倍数
那么首先化为前缀和,处理出前缀和后就是
[1, r] - [1, l-1]中每个字母的个数是k的倍数
这样还不够,注意差是k的倍数,意味着mod k是同余的
所以如果我们把每个字母的个数都模k
那么就转化为[1, r] 和[1, l-1]是相等的
也就是26元组 a[l - 1] 和 a[r] 是相等的
那么就转化为每次询问区间[l, r]中有多少对相同的26元组
这不就是莫队模板题吗,求一个区间内相同数的个数
当然,这里的26元组要哈希一下扔到unorderer_map中
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef unsigned long long ull;
const int N = 3e4 + 10;
const int base = 131;
unordered_map<ull, int> mp;
int cnt[N][30], ans[N], sum, m, k;
ull Hash[N];
string s;
struct query
{
int l, r, bl, id;
}q[N];
bool cmp(query a, query b)
{
if(a.bl != b.bl) return a.bl < b.bl; //分块
if(a.bl & 1) return a.r < b.r; //同一块内按照右端点
return a.r > b.r; //奇偶块优化
}
void add(int x) { sum += mp[Hash[x]]; mp[Hash[x]]++; }
void del(int x) { mp[Hash[x]]--; sum -= mp[Hash[x]]; }
int main()
{
cin >> k >> s;
int len = s.size();
rep(i, 0, len)
{
if(i > 0) rep(j, 0, 26) cnt[i][j] = cnt[i - 1][j];
cnt[i][s[i] - 'a'] = (cnt[i][s[i] - 'a'] + 1) % k;
ull t = 0;
rep(j, 0, 26) t = t * base + cnt[i][j];
Hash[i + 1] = t; //i + 1和题目标号一致
}
cin >> m;
int block = sqrt(len);
_for(i, 1, m)
{
int l, r;
cin >> l >> r;
q[i] = query{l, r, l / block, i};
}
sort(q + 1, q + m + 1, cmp);
int l = 1, r = 0;
_for(i, 1, m)
{
int ll = q[i].l - 1, rr = q[i].r; // -1关键 因为这里WA了好久
while(l < ll) del(l++); //del先删掉再移动
while(l > ll) add(--l); //add反过来,先移动再操作
while(r < rr) add(++r); //l一个add一个del r同样
while(r > rr) del(r--);
ans[q[i].id] = sum;
}
_for(i, 1, m) cout << ans[i] << endl;
return 0;
}
Fake Math Problem(组合数学)
那个式子大概就是
每次给一个ai
1
n
n * (n - 1)
n……(n - ai + 1)
上面求和
然后对于每个ai得到的数再求和
计算这个东西
关键在于发现这个模数不是个质数,当时我们队就被坑了
既然这样,相乘的过程只要含有了这个模数的所有因子就为0了
所以暴力就好了,如果为0就直接break
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int mod = 998241383;
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n, ans = 0;
scanf("%d", &n);
_for(i, 0, n)
{
ans = add(ans, 1);
int k, t = 1, cnt = 0;
scanf("%d", &k);
for(int j = i; j >= i - k + 1 && t; j--)
{
t = mul(t, j);
ans = add(ans, t);
cnt = add(cnt, t);
}
}
printf("%d\n", ans);
}
return 0;
}
周日
今天看了科比的演讲,太励志了
感觉我的努力程度远远不及科比。
他的那种上进,自律,专注真的令我非常震撼
暑假是个很好的时间,努力提升自己
Good Game, GG(博弈论)
这题就分类讨论
x = 1 Alice多一次
x = 2 如果Bob操作1次,Alice可以操作两次,还不如不操作
对于奇数x > 1 因为2 Bob不可能去操作,所以分割成2 + 一个奇数
这样可以多操作x / 2 + 1次
对于偶数x > 2 肯定是分成两个偶数
这样可以多操作x / 2 - 1次
比赛的时候我纠结在具体拆的过程中了,搞得很乱
其实和拆的顺序没有关系,要宏观的观察
还有要开long long 好坑
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n;
long long a = 0, b = 0;
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
if(x == 1) a++;
else if(x % 2 == 1) a += x / 2 + 1;
else b += x / 2 - 1;
}
puts(a > b ? "Alice" : "Bob");
}
return 0;
}
手动计算(计算几何)
比赛时队友手算积分过的
官方的题解很骚
题目给的精度要求很小,只要求输出1位小数
所以可以直接切成很多个以0.01为边长的正方形
然后看每个正方形在不在椭圆内部,在的话加上这个正方形的面积
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
double a, b, c, d;
scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
double ans = 0;
for(double x = -8; x <= 8; x += 0.01)
for(double y = -8; y <= 8; y += 0.01)
if(x * x / (a * a) + y * y / (b * b) <= 1 || x * x / (c * c) + y * y / (d * d) <= 1)
ans += 0.01 * 0.01;
printf("%.1f\n", ans);
}
return 0;
}
Dance with a stick(计算几何)
这道题我当时猜了一个结论,就是一条直接左右两边点相同就可以了
当时具体实现想的有点复杂,队友也在写其他题,就没写了
最后证明这个结论是对的。同时实现可以写的很简单
直接点排序,排成一条类似y = x的斜线
取中间点,方向是-1 1e9就可以了
这样就非常巧妙
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
int n;
vector<pair<int, int>> ve;
scanf("%d", &n);
_for(i, 1, n)
{
int x, y;
scanf("%d%d", &x, &y);
ve.push_back(make_pair(x, y));
}
sort(ve.begin(), ve.end());
if(n % 2 == 0)
{
puts("No");
return 0;
}
puts("Yes");
printf("%d %d -1 1000000000\n", ve[n / 2].first, ve[n / 2].second);
return 0;
}
Honeycomb(思维)
这道题关键是点的坐标不好确立
可以从里到外一圈一圈来看,判断当前点在第几圈
如何判断呢,我们可以发现每一圈最大的点数是3 *k*(k-1)
于是可以二分,用vector实现,用lower_bound
然后定位之后,再沿着那一圈绕,找到当前点的坐标。
有了点的坐标就可以算出x和y方向上的差值
之后就很简单了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int dir[6][2] = {1, 1, -1, 1, -2, 0, -1, -1, 1, -1, 2, 0};
struct node { int x, y; };
vector<int> ve;
node get(int now)
{
int p = lower_bound(ve.begin(), ve.end(), now) - ve.begin();
int x = p, y = -p, t = 3 * p * (p + 1) + 1 - now;
rep(i, 0, 6)
{
int k = min(t, p); t -= k;
x += dir[i][0] * k;
y += dir[i][1] * k;
if(!t) break;
}
return node{x, y};
}
int main()
{
for(int i = 1; 3 * i * (i - 1) + 1 <= 1e9; i++)
ve.push_back(3 * i * (i - 1) + 1);
int T; scanf("%d", &T);
while(T--)
{
int a, b;
scanf("%d%d", &a, &b);
node A = get(a);
node B = get(b);
int dx = abs(A.x - B.x), dy = abs(A.y - B.y);
if(dy > dx) printf("%d\n", dy + 1);
else printf("%d\n", dy + (dx - dy) / 2 + 1);
}
return 0;
}