注:第三第四场大概还没看一题多解的题解实现和解法
-
L 牛牛学走路
签到
-
E 炸鸡块君的高中回忆
数学
-
H 牛牛看云
组合数学 桶排序
-
J 小朋友做游戏
排序
-
A 九小时九个人九扇门
背包DP
-
F 中位数切分
贪心
-
D 牛牛做数论
欧拉函数 素数
-
C Baby's first attempt on CPU
模拟 贪心
-
I B站与各唱各的
组合数学 逆元 数学 轮换对称性/拉格朗日乘数法
-
B 炸鸡块君与FIFA22
ST表
-
K 冒险公社
DP
-
G ACM is all you need
差分 离散化 数学
-
A 小沙的步伐
模拟
-
D 小沙的杀球
贪心
-
H 小沙的数数
位运算 组合数学
-
F 小沙的算数
分块 二分
-
I 小沙的构造
构造 模拟
-
G 小沙的身法
树上前缀和
-
E 小沙的长路
图论 欧拉回路
-
A 小沙的炉石
二分 / 数学
-
L 小沙的remake(普通版)
线段树/树状数组 组合数学 DP
-
M 小沙的remake(竞速版)
基数排序
-
B 小沙的魔法
并查集 离散化
-
D 小沙的涂色
构造
-
J 小沙的Dota
线段树 DP
-
A 智乃的Hello XXXX
签到
-
D 智乃的01串打乱
签到
-
B 智乃买瓜
背包DP
-
L 智乃的数据库
set 哈希表
-
E 智乃的数字积木(easy version)
模拟 排序
-
G 智乃的树旋转(easy version)
树
-
C 智乃买瓜(another version)
逆向 构造
-
J 智乃的C语言模除方程
前缀和 数学
-
H 智乃的树旋转(hard version)
splay单旋 构造
-
K 智乃的C语言模除方程(another version)
整除分块 数列
-
F 智乃的数字积木(hard version)
启发式合并 并查集
-
E 真假签到题
签到 数学
-
H 真真真真真签到题
签到 数学
-
K 小红的真真假假签到题题
进制 构造
-
A R
滑动窗口
-
C 蓝彗星
差分
-
F 小红的记谱法
签到 小模拟
-
J 区间合数的最小公倍数
LCM 唯一分解定理
-
I 爆炸的符卡洋洋洒洒
DP
-
D 雪色光晕
计算几何
-
B 进制
线段树 / 树状数组
-
G 子序列权值乘积
拓展欧拉定理 组合数学
-
L 在这冷漠的世界里光光哭哭
二分 前缀和
-
J 三国小孩
签到
-
G 163小孩
枚举 / DFS / 组合数学
-
I 兔崽小孩
二分 前缀和
-
D 数位小孩
数位DP / DFS
-
A 疫苗小孩
二分
-
C 战棋小孩
记忆化DFS + DP / 贪心 + 二进制枚举 + 排序
-
E 复苏小孩
线段树 矩阵乘法
-
F 飞车小孩
结构体排序 组合数学
-
K 造梦小孩
整除分块 前缀和 线段树/树状数组
-
B 乒乓小孩
思维
-
H 一六三小孩
构造 随机 枚举
-
I A+B问题
高精度 进制
-
F +-串
思维 贪心
-
E 骑士
排序 / 前缀和
-
D 删除子序列
思维 字符串(子序列匹配) 贪心
-
J 牛妹的数学难题
组合数学 生成函数 线性逆元
-
H 寒冬信使2
博弈论 记忆化DFS 二进制枚举
-
B 价值序列
思维 组合数学
-
G 迷宫2
BFS
-
A 回文大师
字符串哈希+二分+差分 / KMP树前缀和
-
C 数组划分
单调栈 二分 / 并查集
比赛日志:
开场看第一题,一眼没啥思路,然后瞬间切最后一题看,一眼签到有思路,马上写,刷新看到有人交了,证明确实是签到题,然后很容易过掉了,L题结束,语法题,三分钟过题排名大约18。然后看榜做题,看到E题,是个简单的数学规律题,花了一会儿很快推导出了规律,一交一个准,切掉了,截止目前八分钟,大概还是二十多。然后看榜,有好像多选了,这时候我先去看感觉比较能做的一道求和题,很快发现可以桶,然后发现很尴尬不会计数,一交一发WA,然后分奇偶,急了,没有仔细验证,又WA。然后又WA了一次,终于找对了规律,花了不少时间,终于切掉了这道H题,截止目前半小时。
然后去看J题,以为是简单的排序,然后WA掉了,仔细思考,发现会滑窗,不是均分的,然后把滑窗用数组合并优化掉,然后过掉了这道题,总用时45分钟。然后看A题去了,仔细思考几分钟,发现不过是个dp签到,然后很快debug一下签了,总用时60分钟。此时闲下功夫看榜,发现排27。
然后去搞过题很多的F题,我一直有在想一直没想出来,第一眼以为是两个堆维护中位数,然后二分长度,结果发现二分不出来,推过去直接得到结论的,然后过题人数越来越多,但是我发现自己一开始的结论就错了,我连样例都原来过不了,我的做法有问题,不能贪心顺推,然后仔细观察,猛然按与m的大小关系分为两类,然后很快发现直接计数即可。然后去猜结论,以为C很简单直接暴力猜一发,然后喜提炸掉了。然后分散精力去看数论D题,打表找规律,找了好一会儿,找出来了,然后写出来切掉了。然后C,发现比较麻烦,想了很久推了很多,发现有连带关系,在原图矩阵里,可以连带一个三角。我在想一开始看到这么小的数据就以为是可以DFS,记忆化乃至模拟退火,然后后来发现好像可以先把i-1的全部切掉,就这样个贪心法,对i-1,i-2,i-3都贪一下,然后喜提挂掉了。然后发现i-1可以不考虑后续,但是i-3时应该一有就搞,然后给我搞出来了切掉了。
然后去看过题人数比较少的I,仔细一看发现可以写出一个函数求最值,然后对数求导吧、一般求导吧都比较困难,然后观察发现符合轮换对称性,直接取0.5,然后验证了好一会儿,然后一交一个过,逆元裸题。F是110过的,D是157,C是202,然后紧接着I在217就过了,所以很快。
然后已经接近尾声了,看了看剩下的题目,生下三道题,压轴没思路,B怀疑是卷积或者DP,K怀疑是贪心,根据相似经验。然后K没想出来,专门去搞B,很开始一眼就怀疑前缀和,然后仔细思考了很久发现好像还是可以前缀和,虽然复杂度显然不敢恭维,只能期望数据弱,然后一交因为实现和各种问题一直在WA,反正就是无了。剩下这么久时间基本上啥都没过。终榜排名一路掉,从50多掉到84,一共三千多人。
直接模拟即可
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
ll t, n;
#define sc(x) scanf("%lld", &x)
signed main()
{
sc(t), sc(n);
while (t--)
{
sc(n);
ll x = 0, y = 0;
db d = 0;
string t;
cin >> t;
for (int i = 0, ie = t.size(); i < ie; ++i)
{
if (t[i] == 'L')
{
--x;
}
else if (t[i] == 'R')
{
++x;
}
else if (t[i] == 'U')
{
++y;
}
else if (t[i] == 'D')
{
--y;
}
d = max(d, sqrt(x * x + y * y));
}
printf("%.12lf\n", d);
}
return 0;
}
若只有一个人带不走人。若卡比人多,不用带人。否则,第一次进
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll t, n, m;
signed main()
{
sc(t);
while (t--)
{
sc(n), sc(m);
if (m == 1 && n > 1)
{
printf("-1\n");
}
else if (m >= n)
{
printf("1\n");
}
else
{
printf("%lld\n", 1LL + 2 * (ll)ceil(1.0 * (n - m) / (m - 1)));
}
}
return 0;
}
原式即求两两组合,含自身重复。先桶排一下,然后对不自身重复部分,直接相乘就是数目。对相同的而言,有自己跟自己得
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, bin[1024], ans, m = 1000;
signed main()
{
sc(n);
for (ll i = 1; i <= n; ++i)
{
ll x;
sc(x);
bin[x]++;
}
for (ll i = 0; i <= m; ++i)
{
for (ll j = i + 1; j <= m; ++j)
{
ans += abs(i + j - m) * (bin[i] * bin[j]);
}
}
// printf("%lld\n", ans);
for (ll i = 0; i <= m; ++i)
{
ans += abs(i + i - m) * ((bin[i] + 1) * bin[i] / 2);
}
printf("%lld", ans);
return 0;
}
对总人数
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 20010
ll t, a, b, n, va[mn], vb[mn];
signed main()
{
sc(t);
while (t--)
{
sc(a), sc(b), sc(n);
for (ll i = 0; i < a; ++i)
{
sc(va[i]);
}
sort(va, va + a);
reverse(va, va + a);
for (ll i = 0; i < b; ++i)
{
sc(vb[i]);
}
sort(vb, vb + b);
reverse(vb, vb + b);
if ((n + 1) / 2 > a || a + b < n)
{
printf("-1\n");
continue;
}
ll nb = n / 2;
if (nb > b)
{
nb = b;
}
ll na = n - nb;
if (nb < 0 || na < 0)
{
printf("-1\n");
continue;
}
for (ll i = 0; i < nb; ++i)
{
va[a + i] = vb[i];
}
sort(va, va + a + nb);
reverse(va, va + a + nb);
ll ans = 0;
for (ll i = 0; i < n; ++i)
{
ans += va[i];
}
// printf("%lld %lld\n", na, nb);
printf("%lld\n", ans);
}
return 0;
}
直接对其推一个背包 DP 即可,比较板子的题目。
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mod 998244353
ll n, a, dp[2][13];
ll root(ll x)
{
if (x < 10)
{
return x;
}
ll r = 0;
while (x > 0)
{
r += x % 10;
x /= 10;
}
return root(r);
}
signed main()
{
sc(n);
dp[0][0] = 1;
for (ll i = 1, now = 1, prev = 0; i <= n; ++i)
{
sc(a);
a = root(a);
memset(dp[now], 0, sizeof dp[now]);
for (ll j = 0; j <= 9; ++j)
{
ll k = root(j + a);
// printf("(%lld)", dp[prev][j]);
dp[now][k] += dp[prev][j];
}
for (ll j = 0; j <= 9; ++j)
{
dp[now][j] = (dp[now][j] + dp[prev][j]) % mod;
// printf("%lld ", dp[now][j]);
}
// printf("\n");
now ^= 1, prev ^= 1;
}
for (ll i = 1; i <= 9; ++i)
{
printf("%lld ", dp[n & 1][i]);
}
return 0;
}
对于大于等于中位数的,每个自身可以划一段。对于小于的,为了让整一段凑够中位数,必须再找大于等于中位数的其他段借两个数,借的过程等于删掉一段,并和另一端组成一段。
可以直接贪心这个过程,即发现大于等于中位数的,若当前没有需要借的就自成一段,有就给借;对小于中位数的,一直找前面已有的借,找不到就记着先。最后等效于看有多少个大于等于
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll t, n, m, a[mn], k;
signed main()
{
sc(t);
while (t--)
{
sc(n), sc(m), k = 0;
for (ll i = 1; i <= n; ++i)
{
sc(a[i]);
k += a[i] >= m ? 1 : -1;
}
printf("%lld\n", k > 0 ? k : -1);
}
return 0;
}
打表可知,最大的是素数,最小的是素数乘积。
证明:$H(x)=\prod(1-\dfrac1{p_i})$ ,所以素数越多值越少。而对素数本身,只有一项目前已知最大的乘数,所以显然是最大的。
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll t, n;
vector<ll> hmin;
bool isprime(ll p)
{
if (p == 2 || p == 3)
return true;
if (p < 2 || (p % 6 != 1 && p % 6 != 5))
return false;
for (ll i = 5; i * i <= p; i += 6)
if (p % i == 0 || p % (i + 2) == 0)
return false;
return true;
}
signed main()
{
for (ll i = 0, lt = 2, p = 3; i < 12; ++i)
{
hmin.emplace_back(lt);
lt *= p;
++p;
while (!isprime(p))
{
++p;
}
}
sc(t);
while (t--)
{
sc(n);
if (n == 1)
{
printf("-1\n");
continue;
}
ll lfi = lower_bound(hmin.begin(), hmin.end(), n) - hmin.begin();
ll lf = hmin[lfi] > n ? hmin[lfi - 1] : hmin[lfi];
ll rf = n;
while (!isprime(rf))
--rf;
printf("%lld %lld\n", lf, rf);
}
return 0;
}
可以看作是模拟题。对于输入的矩阵,构造一个矩阵,设
如果在第
问题转化为对
可以发现第一列值有多少就一定要减多少,其他地方减的矩阵不可能帮到它,所以先扫第一列,有就全减了,然后得到新的矩阵
第二列,上一行减的可以帮助这一行,所以如果有相邻的两个值,优先减上面的必然最优。
在考虑自己列的时候是不需要考虑它右边的列的,因为必然
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, a[110][5], ans;
void f(ll &x, ll v)
{
x = max(x - v, 0LL);
}
void cls(ll i, ll v)
{
f(a[i][1], v);
f(a[i][2], v);
f(a[i][3], v);
f(a[i + 1][2], v);
f(a[i + 1][3], v);
f(a[i + 2][3], v);
}
signed main()
{
sc(n);
for (ll i = 1; i <= n; ++i)
{
for (ll j = 1; j <= 3; ++j)
{
sc(a[i][j]);
if (a[i][j])
{
a[i][j] = 4 - j;
}
}
}
for (ll i = 1; i <= n; ++i)
{
for (ll j = 1; j <= 3; ++j)
{
if (a[i][j] > 0)
{
ans += a[i][j];
cls(i, a[i][j]);
}
}
}
printf("%lld", ans);
return 0;
}
因为相互独立,所以设第
拉格朗日乘数法数学证明:
设
求偏导数,联立以
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, m, mod = 1e9 + 7, t;
ll qpow(ll a, ll b)
{
ll r = 1;
for (; b > 0; b >>= 1)
{
if (b & 1)
{
r = r * a % mod;
}
a = a * a % mod;
}
return r;
}
ll inv(ll x)
{
return qpow(x, mod - 2);
}
ll e(ll a, ll b)
{
return m * (1 - qpow(a, n) * inv(qpow(b, n)) % mod - qpow(b - a, n) * inv(qpow(b, n)) % mod + 2 * mod) % mod;
}
signed main()
{
sc(t);
while (t--)
{
sc(n), sc(m);
printf("%lld\n", e(1, 2));
}
return 0;
}
定义
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 200010
#define lg 19
ll n, q, l, r, s, st[3][mn][lg], nx[mn][lg];
char t[mn];
signed main()
{
sc(n), sc(q), scanf("%s", t + 1);
for (ll i = 0; i < lg; ++i)
{
nx[n + 1][i] = n + 1;
}
for (ll i = 1; i <= n; ++i)
{
nx[i][0] = i + 1;
for (ll k = 0; k < 3; ++k)
{
st[k][i][0] = (t[i] == 'W') + (-1) * (t[i] == 'L') * (k % 3 != 0);
}
}
for (ll j = 1; j < lg; ++j)
{
for (ll i = 1; i <= n; ++i)
{
nx[i][j] = nx[nx[i][j - 1]][j - 1];
for (ll k = 0; k < 3; ++k)
{
st[k][i][j] = st[k][i][j - 1] + st[(k + st[k][i][j - 1]) % 3][nx[i][j - 1]][j - 1]; //not [][1 << (j - 1)][]
}
}
}
while (q--)
{
sc(l), sc(r), sc(s);
// for (ll d = r - l + 1, p = 63 - __builtin_clzll(d); d > 0; d >>= 1, --p)
// {
// if (d & 1)
// {
// s += st[(s + 3) % 3][l][p];
// l += 1 << p;
// }
// }
for (ll i = lg - 1; i >= 0; --i)
{
if (nx[l][i] <= r + 1)
{
s += st[(s + 3) % 3][l][i];
l = nx[l][i];
}
}
printf("%lld\n", s);
}
return 0;
}
设
递推
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll n, dp[mn][3][3], r = 0, g = 1, b = 2, ans = -1;
char s[mn];
signed main()
{
sc(n), scanf("%s", s + 1);
memset(dp, -0x3f, sizeof dp);
for (ll x = 0; x < 3; ++x)
{
for (ll y = 0; y < 3; ++y)
{
dp[1][x][y] = (x == g); //无关紧要
dp[2][x][y] = (x == g) + (y == g);
}
}
for (ll i = 3; i <= n; ++i)
{
for (ll x = 0; x < 3; ++x)
{
for (ll y = 0; y < 3; ++y)
{
for (ll z = 0; z < 3; ++z)
{
ll gs = (x == g) + (y == g) + (z == g);
ll rs = (x == r) + (y == r) + (z == r);
if ((s[i] == 'R' && rs > gs) || (s[i] == 'G' && gs > rs) || (s[i] == 'B' && rs == gs))
{
dp[i][x][y] = max(dp[i][x][y], (x == g) + dp[i - 1][y][z]);
}
}
}
}
}
for (ll x = 0; x < 3; ++x)
{
for (ll y = 0; y < 3; ++y)
{
ans = max(ans, dp[n][x][y]);
}
}
printf("%lld", ans);
return 0;
}
可以发现,固定
具体而言,设
-
如果
$f_i < x$ ,则需要满足:$|f_i-b|+b < |x-b|+b$-
对
$b \le f_i$ ,两边直接去绝对值,得不等式恒成立 -
对
$f_i < b\le x$ ,去绝对值得到$b-f_i < x-b\Rightarrow b < \dfrac{x+f_i}2$ ,因为$b$ 是整数, 若右边本就是整数,即$(x+f_i)\bmod 2=0$ ,则$b$ 可取$\le \dfrac{x+f_i}2-1$ ;否则,可取$\le\lfloor\dfrac{x+f_i}2\rfloor$ ,合并为:$x\le \lfloor\dfrac{x+f_i}2\rfloor-(x+f_i\bmod 2\equiv 0)$ -
对
$b\ge x$ ,得到恒不成立的不等式 -
只要能满足 $|f_i-b|+b < |x-b|+b $ ,恒满足
$|f_i-b| +b<|y-b|+b$ ,因为代入上述步骤同理计算得需要满足的是$x\le \lfloor\dfrac{y+f_i}2\rfloor-(y+f_i\bmod 2\equiv 0)$ 稍加思索,发现函数
$\lfloor\dfrac p2\rfloor-(p\bmod 2\equiv0)=1-(p\bmod 2)+\lfloor\dfrac p2\rfloor$ ,同奇偶是单调增,从奇到奇$+1$ 增了$1$ ,所以是单调递增函数
-
-
如果
$f_i > y$ ,同理:-
$b\le y$ 恒不成立 -
$y < b\le f_i$ ,得$f_i-b < b-y\Rightarrow b >\dfrac{f_i+y}2$ ,本就是整数要$+1$ ,本不是也要加,即$b\ge \lfloor\dfrac{f_i+y}2\rfloor+1$ -
$b\ge f_i$ 恒成立 - 同理,只要能满足这个不等式,另一个关于
$x$ 的恒满足
-
-
如果
$f_i=x$ 或$f_i=y$ 至少其一成立,永远不可能成为最小值 -
否则,只能是夹在两者中间,
$x < f_i < y$ ,需要同时满足: $$ \begin{cases} |f_i-b|+b < |x-b|+b\ |f_i-b|+b < |y-b|+b \end{cases} $$-
$b\le x$ ,则不等式一恒不成立,不等式二恒成立,无解;同理,$b\ge y$ 也无解 -
$x < b\le f_i$ ,$f_i-b < b-x\Rightarrow b\ge\lfloor\dfrac{f_i+x}2\rfloor+1$ ,不等式二恒成立 -
$f_i < x\le y$ ,$b-f_i < y-b\Rightarrow b\le \lfloor\dfrac{y+f_i}2\rfloor-(y+f_i\bmod 2\equiv 0)$,不等式一恒成立
-
综上所述,可以写成代码。
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
#define big 0x7fffffff
ll n, a[mn], t, cnt;
map<ll, ll> m;
signed main()
{
sc(t);
while (t--)
{
sc(n), cnt = 0, m.clear();
for (ll i = 1; i <= n; ++i)
{
sc(a[i]);
}
for (ll i = 2; i < n; ++i)
{
ll lt = min(a[i + 1], a[i - 1]), gt = max(a[i + 1], a[i - 1]);
if (a[i] < lt)
{
++cnt;
m[(lt + a[i]) / 2 - ((lt + a[i]) % 2 == 0) + 1]--;
}
else if (a[i] > gt)
{ // removed plus infinity
m[(gt + a[i]) / 2 + 1]++;
}
else if (!(a[i] == gt || a[i] == lt))
{
m[(lt + a[i]) / 2 + 1]++;
m[(gt + a[i]) / 2 - ((gt + a[i]) % 2 == 0) + 1]--;
}
}
ll ans = cnt;
for (auto i : m)
{
cnt += i.second;
ans = min(ans, cnt);
}
printf("%lld\n", ans);
}
return 0;
}
比赛日志:
一开始我随机看了两道题,都没有一眼的思路,然后再退回去,发现榜单有一道题已经有不少人通过了,然后点进去一看一个签到题,大致锁定了题意然后马上开始做,一边做一边思考细节,把二维改成了一维,然后重新理解了纠正的题意,然后本地测试一下,对了,然后直接赶时间一交。一惊,竟然CE了。然后一看是本地给我自己加了cstring头的strlen,然后牛客没有(因为手机跑不了万能头,所以没开)。然后编译器写了一个,猛然发现牛客编辑器无法全选覆盖,然后只得再在网页再写一次,然后跑网页本地测试,一发一个AC。手机发出通告的音效和AC的音效。这时候已经10分钟了(手机写代码速度不敢恭维),已经有九百多号人过了这道题了
然后到家了,手忙脚乱地打开好电脑的环境,然后仔细看过了签到K之后的C题,然后思考片刻,大概觉得是个0-1背包,然后写着写着发现是可以贪心的,即当前能杀球一定杀,不然留着也是等效的,然后平堆,然后本地对了一交一个段错误,好家伙我把1e6写成了1e5,天大的罪过,然后纠正过后切掉了C题,这时总用时是23分种(指正:不是总罚时,是过这道题时距开赛的时间差,上同)。排名自然一点都不好看。
然后按照榜单,去看H题。想了好一会的,虽然很快发现了异或肯定不会拆高位,然后这样往下想,发现好像不过是一个组合数学桶和球的问题,并且发现了答案。然后稳妥测试了几下,一交一个WA。然后思考片刻,发现是1e18乘法取模爆long long,然后改了一下,然后发现自己测试的时候前后结果一致,然后也不敢贸然交,然后猛然发现不能写(a%p*b%p)%p,后者内部还得括号多一次,然后果然不一样了,一交一发AC,此时总用时33,罚时上天。
然后看F题,一眼以为线段树,然后仔细思考,很快发现加号分割即可,乘法的连一块,然后编号上可以二分找,然后再用点逆元。实现了蛮久,debug了好一阵子,然后一交一发AC,总用时58。
然后看E题,没啥思路,是完全图的结论题。想写暴力验证答案,在位运算穷举下,先写了个floyd,然后求了半天发现都不会,不会改成最长路……负吧,发现带负环了,max吧,又答案不对。然后又用dijkstra重写,发现答案都差不多,并且发现自己建图一堆错误。然后还是不行,看了看johnson,没写,然后想了想干脆DFS吧。然后看了一下通告,发现最长路的定义完全不一样啊,然后写DFS,又发现了建图的一堆错误,然后跑了个暴力,但是结果跟自己推了半天的纸面结果的最小值不一样,然后我怀疑程序错了,debug了半天,但是6以上完全跑不出来。然后纸面也搞不出什么新的东西。试着猜了两次结论,都WA了。已经被过了这道题的反超了。
然后姑且放弃了这道题,看了看别的题目,看到一道比较少人做的I题,一看构造题,思考了片刻感觉有思路啊,然后实现了……有点久,大概是半小时切了这道题。然后一看总用时都205分钟了。不过我的排名还是没有上来。
然后E又在思考,想不出什么东西。A我也想不出来,一开始以为是斯特林数什么的,然后不行,又想了想,纠正了读题错误,然后发现是前n+m项和里挑走若干个数,没有思路。然后又发现了理解的错误,然后发现更难了,想不出来。
然后再往更深去看题目,看到一道G题,思索片刻,觉得可能是树剖+主席树,然后还惊呼怎么能这么难。然后想了想线段树不需要,然后再想了想,树上前缀和LCA也能做,然后综合考量,我还是选择了后者。然后从自己的洛谷AC记录里搞了一份LCA板子上手魔改,然后调试了半天,现推了树上前缀和,然后得到了代码。然后一发一个MLE,我惊了,计算后发现long long会炸空间,改成int毫不犹豫又交了一次,结果WA了。仔细思考,发现是改的时候有会爆long long的地方,还是得换回long long,然后通篇检查了一次,又交了一发,终于AC了。此时已经还剩十分钟不到就结束了。
想着最后再随便碰碰E题,但是还是没这么做。在看I之前自己还病急乱投医又猜了四次E题,都以失败告终。
要么在
赛时 AC 代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
char t[1000010];
ll cnt[12];
ll n, now=5;
signed main()
{
scanf("%s", t);
n=strlen(t);
for(ll i=0;i<n;++i)
{
ll c=t[i]-'0';
if (c==now)
{
continue;
}
else
{
cnt[5]++;
}
if (5!=c)
{
cnt[c]++;
}
now=5;
}
for (ll i=1;i<=9;++i)
{
printf("%lld ",cnt[i]);
}
return 0;
}
可以证明,只要能杀就杀的贪心是最优的。如果想攒体力蹲下一个球再杀,不会比现在就杀更优。因为如果还有球杀,就是等效的(只是把
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
ll n, a, b, dp[mn], hp[mn], x;
char t[mn];
signed main()
{
scanf("%lld%lld%lld%s", &x, &a, &b, t + 1);
dp[0] = 0;
hp[0] = x;
n = strlen(t + 1);
for (ll i = 1; i <= n; ++i)
{
if (t[i] == '1')
{
if (hp[i - 1] >= a)
{
dp[i] = dp[i - 1] + 1;
hp[i] = hp[i - 1] - a;
}
else
{
dp[i] = dp[i - 1];
hp[i] = hp[i - 1] + b;
}
}
else
{
dp[i] = dp[i - 1];
hp[i] = hp[i - 1] + b;
}
// printf("%lld\n", dp[i]);
}
printf("%lld", dp[n]);
return 0;
}
优化代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define sc(x) scanf("%lld", &x)
ll x, a, b, n, ans;
char s[1000010];
signed main()
{
sc(x), sc(a), sc(b), scanf("%s", s + 1), n = strlen(s + 1);
for (ll i = 1; i <= n; ++i)
{
if (s[i] == '1' && x >= a)
{
++ans, x -= a;
}
else
{
x += b;
}
}
printf("%lld", ans);
return 0;
}
可以把
设
特别注意
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, m, mod = 1e9 + 7, k = 0, r = 1;
signed main()
{
scanf("%lld%lld", &n, &m);
for (; m > 0; m /= 2)
{
if (m & 1)
{
++k;
}
}
for (ll i = 0; i < k; ++i)
{
r = (r % mod * (n % mod)) % mod;
}
printf("%lld", r);
return 0;
}
把等式按加号拆分为若干部分,那么每次修改只会影响一部分值;每次查询二分找到这部分的块,然后除一下逆元,再乘一下新值
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
ll n, q, x, y, p, s[mn], mod = 1e9 + 7, lf[mn], rf[mn], nows, a[mn];
ll ans;
char sgn[mn];
ll qpow(ll a, ll b)
{
ll r = 1;
for (; b > 0; b >>= 1)
{
if (b & 1)
{
r = r * a % mod;
}
a = a * a % mod;
}
return r;
}
ll inv(ll x)
{
return qpow(x, mod - 2);
}
signed main()
{
// printf("%lld\n", inv(2));
scanf("%lld%lld%s", &n, &q, sgn + 1);
sgn[0] = sgn[n] = '+'; //0+....+0
lf[1] = 1;
for (ll i = 1; i <= n + 1; ++i)
{
if (i <= n)
{
sc(x);
}
else
{
x = 0;
}
if (sgn[i - 1] == '+')
{
nows = (nows + x) % mod;
}
else
{
nows = (nows * x) % mod;
}
if (sgn[i] == '+')
{
++p;
rf[p] = i + 1;
lf[p + 1] = i + 1;
s[p] = nows;
ans += nows;
nows = 0;
}
a[i] = x;
// printf("^ %lld %lld\n", i, a[i]);
}
// printf("[[%lld\n", ans);
// printf("----- %lld\n", p);
// for (ll i = 1; i <= p; ++i)
// {
// printf("%lld %lld %lld\n", lf[i], rf[i], s[i]);
// }
while (q--)
{
scanf("%lld%lld", &x, &y);
ll i = upper_bound(rf + 1, rf + 1 + p, x) - rf;
// printf("-- %lld %lld %lld %lld ", i, s[i], a[x], inv(a[x]));
ans = (ans - s[i] + mod) % mod;
s[i] = s[i] * inv(a[x]) % mod;
a[x] = y;
s[i] = s[i] * a[x] % mod;
ans = (ans + s[i]) % mod;
// printf(" %lld\n", s[i]);
printf("%lld\n", ans);
}
return 0;
}
模拟+构造题。对称串除了中心位置外可以分为左右。一种构造思路是,尽可能多地凑类型,所以一开始把翻转对称的五对字符先用了,直到还剩
特别注意这种构造方法对有中心时,一开始会构造出
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
char p1[] = "\"!'*+-.08:=^_WTYUIOAHXVM|", p2[] = "<>\\/[]{}()", r[10010];
ll n, m, t, s, suc, s1, s2;
signed main()
{
scanf("%lld%lld", &n, &m);
t = (n + 1) / 2;
s = m <= 10 ? (m + 1) / 2 : m - 5;
if (m == 36 || m > n)
{
// printf("??");
printf("-1");
return 0;
}
if (m == 1)
{
for (ll i = 1; i <= n; ++i)
{
printf("%c", '0');
}
return 0;
}
for (ll i = 1, j = n; i <= n / 2; ++i, --j)
{
if (i <= 5)
{
if (suc + 2 <= m)
{
r[i] = p2[s2];
r[j] = p2[s2 + 1];
suc += 2;
s2 += 2;
}
else if (suc + 1 <= m)
{
r[i] = r[j] = p1[s1];
s1++;
suc++;
}
else //suc==m,m>=2
{
r[i] = p2[s2 - 2];
r[j] = p2[s2 - 1];
}
}
else
{
if (suc + 1 <= m)
{
r[i] = r[j] = p1[s1];
s1++;
suc++;
}
else if (s1)
{
r[i] = r[j] = p1[s1 - 1];
}
else
{
r[i] = p2[s2 - 2];
r[j] = p2[s2 - 1];
}
}
}
if ((n & 1))
{
if (suc + 1 <= m)
{
r[n / 2 + 1] = p1[s1];
s1++;
suc++;
}
else if (s1)
{
r[n / 2 + 1] = p1[s1 - 1];
}
else if (n >= 3 && m >= 2)
{
// r[n / 2] = r[n / 2 + 2] = p1[0];
for (ll i = n / 2, j = n / 2 + 2; i > 1 && r[i] == p2[s2 - 2]; --i, ++j)
{
r[i] = r[j] = p1[0];
}
r[n / 2 + 1] = p1[1];
s1 += 2;
s2 -= 2;
suc++;
}
}
if (suc < m)
{
// printf("%s %lld\n", r + 1, suc);
printf("-1");
return 0;
}
else
{
printf("%s", r + 1);
}
return 0;
}
可以发现所求即为树上一条链的和,一开始想过树剖,但是发现直接上树上前缀和即可,维护正前缀和、反前缀和即可。于是写了个树上前缀和的板子,过掉了这道题
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ln;
typedef int ll;
#define il inline
#define re register
#define rep(i, a, b) for (ll i = a; i < b; ++i)
#define repe(i, a, b) for (ll i = a; i <= b; ++i)
#define red(i, a, b) for (ll i = a; i > b; --i)
#define rede(i, a, b) for (ll i = a; i >= b; --i)
il ll read()
{
char p = 0;
ll r = 0, o = 0;
for (; p < '0' || p > '9'; o |= p == '-', p = getchar())
;
for (; p >= '0' && p <= '9'; r = (r << 1) + (r << 3) + (p ^ 48), p = getchar())
;
return o ? (~r) + 1 : r;
}
#define mn 1000010
#define mm 21
struct edge
{
ll to, nx;
} e[mn << 1];
ll fa[mn][mm], n, m, hd[mn], cnt, d[mn], u, v, lg[mn], s, a[mn];
ln cnt1[mn], cnt2[mn];
il void adde(ll &u, ll &v)
{
e[++cnt] = {v, hd[u]};
hd[u] = cnt;
}
void dfs(ll h, ll faa)
{
fa[h][0] = faa;
d[h] = d[faa] + 1;
repe(i, 1, lg[d[h]])
{
fa[h][i] = fa[fa[h][i - 1]][i - 1];
}
for (ll i = hd[h]; i; i = e[i].nx)
{
ll toi = e[i].to;
if (toi != faa)
{
cnt1[toi] = cnt1[h] + (a[h] > a[toi] ? 0 : a[toi] - a[h]);
cnt2[toi] = cnt2[h] + (a[h] < a[toi] ? 0 : a[h] - a[toi]);
dfs(toi, h);
}
}
}
ll lca()
{
if (d[u] < d[v])
swap(u, v);
while (d[u] > d[v])
u = fa[u][lg[d[u] - d[v]] - 1];
if (u == v)
return u;
rede(k, lg[d[u]] - 1, 0)
{
if (fa[u][k] != fa[v][k])
{
u = fa[u][k];
v = fa[v][k];
}
}
return fa[u][0];
}
signed main()
{
n = read();
m = read();
s = 1;
repe(i, 1, n)
{
a[i] = read();
}
rep(i, 1, n) u = read(), v = read(), adde(u, v), adde(v, u);
repe(i, 1, n) lg[i] = lg[i - 1] + (i == 1 << lg[i - 1]);
dfs(s, 0);
// repe(i, 1, n)
// {
// printf("- %lld %lld\n", cnt1[i], cnt2[i]);
// }
while (m--)
{
u = read();
v = read();
ll ui = u, vi = v;
ll f = lca();
u = ui, v = vi;
ll faa = f == 1 ? 0 : fa[f][0];
ln res = a[u];
// printf("%lld - %lld %lld ", res, f, f);
// printf("(%lld %lld)", cnt2[u], cnt2[f]);
res += cnt2[u] - cnt2[f];
// printf("- %lld", res);
// printf("(%lld %lld)\n", cnt1[v], cnt1[f]);
res += cnt1[v] - cnt1[f];
printf("%lld\n", res);
}
return 0;
}
赛时差点猜对了结论,已经推导出了一半了,赛时一直想打表枚举,但是一直没实现出来。特别注意是每条边只能走一次,而不是每个点只能走一次。赛时最后做成了素数合数分类讨论,而答案是奇偶分类讨论。
对完全图,每个点有
当
特别需要注意地,这里的环并不必然是完整的环,比如
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n;
signed main()
{
sc(n);
printf("%lld ", n - 1);
if (n % 2 == 1)
{
printf("%lld", n * (n - 1) / 2);
//(n-1)/2次环,遍历全部边
}
else
{
printf("%lld", (n - 1) / 2 * n + 1);
//少一次环遍历,最后还能抢救一次
}
return 0;
}
解法一:
假设用
不难发现,最小攻击一定是攻回交替。总和为等差数列,值为:
$$
\sum_{i=1}^a(2i-1)=a^2
$$
交换任意一个相邻的攻回,不难发现使得总攻击 YES
,低于
那么可以二分
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define sc(x) scanf("%lld", &x)
ll n, m, k, x;
signed main()
{
sc(n), sc(m), sc(k);
while (k--)
{
sc(x);
ll lf = 1, rf = min(m + 1, n), cf;
bool suc = false;
while (lf <= rf)
{
cf = (lf + rf) >> 1;
if (x < cf * cf)
{
rf = cf - 1;
}
else if (x > cf * m + cf * (cf + 1) / 2)
{
lf = cf + 1;
}
else
{
suc = true;
break;
}
}
printf(suc ? "YES\n" : "NO\n");
}
return 0;
}
解法二:
对
事实上实际发现运行时间与解法一几乎没差别
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, m, k, x;
signed main()
{
sc(n), sc(m), sc(k);
n = min(m + 1, n);
while (k--)
{
sc(x);
if (n >= 4)
{
if (x <= n * m + n * (n + 1) / 2)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
}
else
{
bool suc = false;
for (ll i = 1; i <= n; ++i)
{
if (x >= i * i && x <= i * m + i * (i + 1) / 2)
{
suc = true;
break;
}
}
if (suc)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
}
}
return 0;
}
按照权值排序后遍历,可以保证当前遍历时已经记录的方案都可以不降。可以使用树状数组维护区间和,意义是只考虑前
为了更快,可以用基数排序。基数排序要避免负数,所以先偏移。
补题 AC 代码:
#include <bits/stdc++.h>
namespace GenHelper
{
int z1, z2, z3, z4, z5, u, res;
int get()
{
z5 = ((z1 << 6) ^ z1) >> 13;
z1 = ((int)(z1 & 4294967) << 18) ^ z5;
z5 = ((z2 << 2) ^ z2) >> 27;
z2 = ((z2 & 4294968) << 2) ^ z5;
z5 = ((z3 << 13) ^ z3) >> 21;
z3 = ((z3 & 4294967) << 7) ^ z5;
z5 = ((z4 << 3) ^ z4) >> 12;
z4 = ((z4 & 4294967) << 13) ^ z5;
return (z1 ^ z2 ^ z3 ^ z4);
}
int read(int m)
{
u = get();
u >>= 1;
if (m == 0)
res = u;
else
res = (u / 2345 + 1000054321) % m;
return res;
}
void srand(int x)
{
z1 = x;
z2 = (~x) ^ (0x23333333);
z3 = x ^ (0x12345798);
z4 = (~x) + 51;
u = 0;
}
}
using namespace GenHelper;
using namespace std;
const int N = 2e6 + 7, mod = 1e9 + 7;
typedef long long ll;
struct node
{
ll id, a, b;
bool operator<(const node &x) const { return a < x.a; }
} c[N];
ll lowbit(ll x) { return x & (-x); }
int n, seed;
ll b[N], t[N];
ll a[N], id[N];
void add(ll i, ll v)
{
for (; i < N; i += lowbit(i))
{
(t[i] += v) %= mod;
}
}
ll ask(ll i)
{
ll res = 0;
for (; i; i -= lowbit(i))
{
res += t[i];
}
return res % mod;
}
// void add(int x, ll c)
// {
// for (; x < N; x += lowbit(x)) //注意不是n
// {
// t[x] += c;
// if (t[x] >= mod)
// t[x] -= mod;
// }
// }
// ll ask(ll x)
// {
// ll res = 0;
// for (; x; x -= lowbit(x))
// res += t[x];
// return res - res / mod * mod;
// }
void sorts()
{
static const int C = 16, D = 1 << C, mask = D - 1;
static int cnt[D], tmp[N];
{
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
++cnt[a[i] & mask];
for (int i = 1; i < D; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
tmp[cnt[a[i] & mask]--] = i;
}
{
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
++cnt[a[i] >> C];
for (int i = 1; i < D; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
id[cnt[a[tmp[i]] >> C]--] = tmp[i];
}
}
int main()
{
scanf("%d %d", &n, &seed);
srand(seed);
for (int i = 1; i <= n; i++)
{
// c[i].a = read(0), c[i].b = read(i), b[i] = c[i].b;
// c[i].id = i;
a[i] = read(0) + 2147483648ull, b[i] = read(i), id[i] = i;
}
// sort(c + 1, c + 1 + n);
sorts();
for (int i = 1; i <= n; i++)
{
// ll id = c[i].id;
// ll x = (ask(id) - ask(id - b[id] - 1) + 1 + mod) % mod;
// add(id, x);
int c = id[i];
ll x = (ask(c) - ask(c - b[c] - 1) + 1 + mod) % mod;
add(c, x);
}
printf("%lld", ask(n));
return 0;
}
//2000000 123456789
// 231324385
在原本的基础上,用上各种优化,如基数排序即可。直接参考上一题的代码。
首先明确这题是可以无限连并查集的,要多少有多少,因为其实等效于可以执行边数次合并。
先进行去重和离散化,升序记录每个不同的值下的节点。然后从大到小遍历每个不同的值,对于这些值的全部顶点,从每个顶点出发,如果邻点权值大于等于它的权值,那证明这些邻点可以跟它合并,然后一起继续上升。
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define range(a) a.begin(), a.end()
typedef long long ll;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const double PI = acos(-1.0);
inline ll read()
{
char ch;
ll x = 0;
bool f = true;
for (ch = getchar(); !isdigit(ch); ch = getchar())
if (ch == '-')
f ^= f;
for (; isdigit(ch); ch = getchar())
x = (x << 3) + (x << 1) + ch - 48;
return f ? x : -x;
}
const int N = 5e5 + 7;
int s[N];
vector<int> son[N], ans[N], mp;
void add(int a, int b) { son[a].pb(b), son[b].pb(a); }
int fin(int x) { return lower_bound(range(mp), x) - mp.begin(); }
int f[N];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
int main()
{
ll n = read(), m = read(), res = 0, cnt = 0;
mp.resize(n + 1, 0);
for (int i = 1; i <= n; i++)
mp[i] = s[i] = read(), f[i] = i;
sort(range(mp)), mp.erase(unique(range(mp)), mp.end());
for (int i = 1; i <= n; i++) //神奇离散化写法
ans[fin(s[i])].pb(i);
for (int i = 1; i <= m; i++)
add(read(), read());
// printf("%lld\n", mp.size());
for (int i = mp.size() - 1; i; i--)
{
for (int x : ans[i])
{
cnt++; //有多少个连通块
for (int j : son[x])
{
if (s[j] < s[x]) //找大于等于自己的邻边
continue;
int fa = find(x), fb = find(j);
if (fa != fb) //操作1
cnt--, f[fa] = fb;
}
}
res += (mp[i] - mp[i - 1]) * cnt;
// printf("%lld %lld\n", res, cnt);
}
cout << res;
return 0;
}
问题即给定正方形,问能否填小三角,使其只差一格就满。是的话给出一个构造方案。
使得还差一格,每次填
根据上述结论,可知对余
对余
枚举可知
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
#define fre(z) freopen(z ".in", "r", stdin), freopen(z ".out", "w", stdout)
#define sto \
std::ios::sync_with_stdio(false); \
std::cin.tie(nullptr); \
std::cout.tie(nullptr);
typedef long long ll;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const double PI = acos(-1.0);
inline ll read()
{
char ch;
ll x = 0;
bool f = true;
for (ch = getchar(); !isdigit(ch); ch = getchar())
if (ch == '-')
f ^= f;
for (; isdigit(ch); ch = getchar())
x = (x << 3) + (x << 1) + ch - 48;
return f ? x : -x;
}
const int N = 1e3 + 7;
int s[N][N];
int t[10][10][10];
int n;
void dfs(int l, int r, int idx)
{
int x = r - l + 1;
if (x < 5)
{
for (int i = 0; i < x; i++)
for (int j = 0; j < x; j++)
s[l + i][l + j] = t[x][i + 1][j + 1] + (t[x][i + 1][j + 1] ? idx : 0);
return;
}
else if (x % 3 == 1)
{
for (int k = 0; k <= 2; k += 2)
{
for (int i = l; i <= r - 4; i += 3)
{
s[l + k][i] = s[l + 1 + k][i] = s[l + k][i + 1] = idx++;
s[l + 1 + k][i + 1] = s[l + k][i + 2] = s[l + 1 + k][i + 2] = idx++;
s[r - 1 - k][i + 4] = s[r - k][i + 4] = s[r - 1 - k][i + 5] = idx++;
s[r - k][i + 5] = s[r - 1 - k][i + 6] = s[r - k][i + 6] = idx++;
s[i + 4][l + k] = s[i + 4][l + 1 + k] = s[i + 5][l + k] = idx++;
s[i + 5][l + 1 + k] = s[i + 6][l + k] = s[i + 6][l + 1 + k] = idx++;
s[i][r - k] = s[i][r - 1 - k] = s[i + 1][r - k] = idx++;
s[i + 1][r - 1 - k] = s[i + 2][r - k] = s[i + 2][r - 1 - k] = idx++;
}
// for (int i = 1; i <= n; i++)
// {
// for (int j = 1; j <= n; j++)
// printf("%02d ", s[i][j]);
// cout << "\n";
// }
// cout << "\n";
}
dfs(l + 4, r - 4, idx);
}
else
{
for (int i = l; i <= r - 3; i += 3)
{
s[l][i] = s[l + 1][i] = s[l][i + 1] = idx++;
s[l + 1][i + 1] = s[l][i + 2] = s[l + 1][i + 2] = idx++;
s[r - 1][i + 2] = s[r][i + 2] = s[r - 1][i + 3] = idx++;
s[r][i + 3] = s[r - 1][i + 4] = s[r][i + 4] = idx++;
s[i + 2][l] = s[i + 2][l + 1] = s[i + 3][l] = idx++;
s[i + 3][l + 1] = s[i + 4][l] = s[i + 4][l + 1] = idx++;
s[i][r] = s[i][r - 1] = s[i + 1][r] = idx++;
s[i + 1][r - 1] = s[i + 2][r] = s[i + 2][r - 1] = idx++;
}
// for (int i = 1; i <= n; i++)
// {
// for (int j = 1; j <= n; j++)
// printf("%02d ", s[i][j]);
// cout << "\n";
// }
// cout << "\n";
dfs(l + 2, r - 2, idx);
}
}
signed main()
{
n = read();
if (n % 3 == 0)
return printf("NO"), 0;
cout << "YES\n";
t[2][1][1] = t[2][1][2] = t[2][2][1] = 1; //n=2
t[4][1][1] = t[4][1][2] = t[4][2][1] = 1; //n=4
t[4][4][4] = t[4][3][4] = t[4][4][3] = 2;
t[4][1][4] = t[4][1][3] = t[4][2][4] = 3;
t[4][4][1] = t[4][4][2] = t[4][3][1] = 4;
t[4][2][2] = t[4][2][3] = t[4][3][2] = 5;
dfs(1, n, 1);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
cout << s[i][j] << " ";
cout << "\n";
}
return 0;
}
先预处理任意两个技能的合并,以及每个技能有多少种形态。然后对三个技能的合并,枚举左所有形态到中所有形态的转变,然后对中当前枚举的形态,枚举右所有形态的转变,从中找到最小的进行合并
可以用线段树维护这样的合并,那么最终变成两个相邻区间的合并,事实上可以化简为左边区间跟右边左端点进行一次三技能合并,得到一个两技能,然后再跟右边右端点进行一个三技能合并即可
预处理两技能合并复杂度是
补题 AC 代码:
#include <bits/stdc++.h>
#define fre(z) freopen(z ".in", "r", stdin), freopen(z ".out", "w", stdout)
#define lowit(x) (x & -x)
#define range(x) begin(x), end(x)
#define sz(x) (int)(x).size()
#define pb push_back
#define sto \
std::ios::sync_with_stdio(false); \
std::cin.tie(nullptr); \
std::cout.tie(nullptr);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll> PII;
typedef pair<double, int> PDL;
const int INF = 0x3f3f3f3f;
const int base = 131;
const double eps = 1e-6;
const double PI = acos(-1);
inline ll read()
{
ll x = 0;
char ch;
bool f = true;
for (ch = getchar(); !isdigit(ch); ch = getchar())
if (ch == '-')
f ^= f;
for (; isdigit(ch); ch = getchar())
x = (x << 3) + (x << 1) + (ch ^ 48);
return f ? x : -x;
}
#define space putchar(' '), void(0)
#define enter putchar('\n'), void(0)
void wr(ll x)
{
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
wr(x / 10);
putchar(x % 10 + 48);
}
void wr(char *s)
{
printf("%s", s);
}
// <------------------------------->
const int N = 1e5 + 7;
char ch[11][4] = {"", "aaa", "aac", "aab", "bbb", "bba", "bbc", "ccc", "cca", "ccb", "abc"};
int w[30][30];
int s[N];
vector<int> node[11];
bool p(int i, const char *s)
{ // i是否是s的一种排列
int cnt[3] = {0};
for (int x = 0; x < 3; x++, i /= 3) //出现次数是否抵消
cnt[i % 3]++, cnt[s[x] - 'a']--;
for (int x = 0; x < 3; x++)
if (cnt[x])
return false;
return true;
}
void init()
{
for (int i = 0; i < 27; i++)
for (int t = 1; t <= 10; t++) //每个技能有几种情况
if (p(i, ch[t]))
node[t].pb(i);
for (int i = 0; i < 27; i++) // i状态到j状态要扔几个球
for (int j = 0; j < 27; j++)
{
w[i][j] = 3;
for (int k = 0, c = 27; k < 3; k++, c /= 3)
if (i % c == j * c / 27) // i的前3-k个字符和j的后3-k个字符
{
w[i][j] = k;
break;
}
}
}
struct T
{
int w[6][6]; // A(3,3)=6
int ls, rs; //技能区间左端点右端点分别有多少种技能的排列
void init(int c)
{
memset(w, INF, sizeof w);
ls = rs = c;
for (int i = 0; i < node[c].size(); i++)
w[i][i] = 0;
}
int money()
{
int ans = INF;
for (int i = 0; i < node[ls].size(); i++)
for (int j = 0; j < node[rs].size(); j++)
ans = min(ans, w[i][j]); //从i技能切到j技能怎样最便宜
return ans + 3;
}
} tr[N << 2];
int a[6][6];
void up(T &v, const T &l, const T &r) //合并相邻技能区间
{
memset(a, INF, sizeof a);
memset(v.w, INF, sizeof v.w);
for (int i = 0; i < node[l.ls].size(); i++)
for (int j = 0; j < node[l.rs].size(); j++)
for (int k = 0; k < node[r.ls].size(); k++) //单点连接
a[i][k] = min(a[i][k], l.w[i][j] + w[node[l.rs][j]][node[r.ls][k]]);
for (int i = 0; i < node[l.ls].size(); i++) //(a,b,c,d)=(a,b,c)(d)
for (int j = 0; j < node[r.ls].size(); j++)
for (int k = 0; k < node[r.rs].size(); k++)
v.w[i][k] = min(v.w[i][k], a[i][j] + r.w[j][k]);
v.ls = l.ls, v.rs = r.rs;
}
#define lson (o << 1)
#define rson (o << 1 | 1)
#define mid (l + r >> 1)
void build(int o, int l, int r)
{
if (l == r)
return tr[o].init(s[l]);
build(lson, l, mid), build(rson, mid + 1, r);
up(tr[o], tr[lson], tr[rson]);
}
void add(int o, int l, int r, int X, int c)
{
if (l == r)
return tr[o].init(c);
if (X <= mid)
add(lson, l, mid, X, c);
else
add(rson, mid + 1, r, X, c);
up(tr[o], tr[lson], tr[rson]);
}
T ask(int o, int l, int r, int L, int R)
{
if (l == L && R == r)
return tr[o];
if (R <= mid)
return ask(lson, l, mid, L, R);
else if (L > mid)
return ask(rson, mid + 1, r, L, R);
T now;
up(now, ask(lson, l, mid, L, mid), ask(rson, mid + 1, r, mid + 1, R));
return now;
}
void solve()
{
int n = read(), m = read();
for (int i = 1; i <= n; i++)
s[i] = read();
init();
build(1, 1, n);
while (m--)
{
int op = read(), l = read(), r = read();
if (op == 1)
wr(ask(1, 1, n, l, r).money()), enter;
else
add(1, 1, n, l, r);
}
}
int main()
{
solve();
}
比赛日志:
开局真·签到题A题hello world,瞬间过题。然后一道很签的简单操作题D,第三分钟过掉了。然后是一道背包变式。我竟然捣鼓了很久才过,总用时19分过了这道题。然后这时候有两道题可以选择,我选了一道当时还比较少人,但我更有思路的L,发现直接手写哈希秒了,总用时42分钟。然后是I,一开始就觉得是双指针。然后想具体怎么实现,一边想一边写,然后做掉了这道题,总用时70分。然后到E题,思考了之后发现不难,就排序和取模。然后过掉了,总用时90分钟。然后又陷入了两难选择。我又选择了看着更有思路更少人的G。这道题要手写树旋转,我就写逐个判断遍历旋转后怎样,但是代码功夫很差,在代码上花了很长时间,第131分钟才过。然后回去再看C,是根据背包推原数据。思考了下发现是矩阵关系,然后一边想一边做,过掉了。自此共166分钟,过了8题,全部都是一发过。排名也很前,都是前几十。然后已经可以半场开香槟了,只有一道J能做。其实看完之后我很快就有思路了。但是……实现起来,思考起来,都不简单,思维难度有,是很烦人的数学题,然后WA了一发。然后我改了个绝对值,又WA了一发,然后我尝试重写逻辑,终于反复测试,并且我意识到可以暴力对拍……然后也找出了之前的出现了负数的bugs,总之写到我头皮发麻,然后再交,对了,262分钟。之后垃圾时间,剩下三道题没有一道有思路。摆了。
不过排名倒是很乐观的。第一场是rating1570,原地从灰破紫破蓝,飙到绿名(我发现这玩意是跟洛谷难度颜色逆着来的)。然后第二场排名从之前的场内84掉到了200,但是rating却升到了1708。第三场场内排名53,rating飙到了1866。
真签到
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
signed main()
{
printf("hello chino");
return 0;
}
任意找到一个
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, f0, f1;
char s[100010];
signed main()
{
sc(n);
scanf("%s", s + 1);
for (ll i = 1; i <= n; ++i)
{
if (s[i] == '0')
{
f0 = i;
}
else if (s[i] == '1')
{
f1 = i;
}
}
swap(s[f0], s[f1]);
printf("%s", s + 1);
return 0;
}
背包 DP 题,基本上改一下板子就能过了,注意不要把决策 1 和决策 2 用 if-else
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 2048
ll n, m, w[mn], dp[2][mn], mod = 1e9 + 7;
signed main()
{
sc(n), sc(m);
dp[1][0] = 1;
for (ll i = 1; i <= n; ++i)
{
sc(w[i]);
}
sort(w + 1, w + 1 + n);
for (ll i = 1, now = 1, pre = 1; i <= n; ++i, now = 1, pre = 1)
{
for (ll j = mn - 1; j >= 0; --j)
{
if (j + w[i] < mn)
{
(dp[now][j + w[i]] += dp[pre][j]) %= mod;
}
if (j + w[i] / 2 < mn)
{
(dp[now][j + w[i] / 2] += dp[pre][j]) %= mod;
}
// (dp[now][j] += 1) %= mod;
}
// for (ll j = 1; j <= m; ++j)
// {
// printf("%lld ", dp[now][j]);
// }
// printf("\n");
}
for (ll i = 1; i <= m; ++i)
{
printf("%lld ", dp[1][i]);
}
return 0;
}
一道哈希表 set STL 的签到题,哈希函数很好写
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1010
ll n, m, a[mn][mn], p = 13331;
map<string, ll> col;
map<ull, ll> cnt;
set<ll> se;
string s, t;
signed main()
{
sc(n), sc(m);
for (ll i = 1; i <= m; ++i)
{
cin >> s;
col[s] = i;
}
for (ll i = 1; i <= n; ++i)
{
for (ll j = 1; j <= m; ++j)
{
sc(a[i][j]);
}
}
cin >> t >> t >> t >> t >> t >> t >> t;
t[t.size() - 1] = ',';
for (ll i = 0, ie = t.size(), h = 0; i < ie; ++i)
{
if (t[i] == ',')
{
s = t.substr(h, i - h);
se.insert(col[s]);
h = i + 1;
}
}
for (ll i = 1; i <= n; ++i)
{
ull v = 0;
for (ll j : se)
{
v = v * p + (1 + a[i][j]);
}
cnt[v]++;
}
printf("%lld\n", cnt.size());
for (auto i : cnt)
{
printf("%lld ", i.second);
}
return 0;
}
滑动窗口。考虑维护四个单调栈对应大小写、数字、特殊字符。对于枚举的右边界,看看当前长度内(大于长度不断pop即可),如果四个栈有长度的把最近的一个拉进去,然后按先后排序一下,如果发现有三个或以上存在的,可以记一下贡献。
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, l, r, len, ans, lf = 1;
char s[100010];
deque<ll> ty[4];
ll ck(char c)
{
if (isupper(c))
{
return 0;
}
if (islower(c))
{
return 1;
}
if (isdigit(c))
{
return 2;
}
return 3;
}
signed main()
{
sc(n), sc(l), sc(r), scanf("%s", s + 1);
for (ll i = 1; i <= n; ++i)
{
ll t = ck(s[i]);
ty[t].push_back(i);
while (i - lf + 1 > r)
{
ll tt = ck(s[lf]);
ty[tt].pop_front();
lf += 1;
}
ll suc = 0;
vector<ll> idx;
for (ll j = 0; j < 4; ++j)
{
if (ty[j].size())
{
++suc;
idx.push_back(ty[j][ty[j].size() - 1]);
// printf("%lld ", idx[idx.size() - 1]);
}
}
sort(idx.begin(), idx.end());
if (suc >= 3 && i - lf + 1 >= l)
{
ll k = idx[idx.size() - 3];
k = min(k, i - l + 1);
// ans += i - k + 1;
ans += k - lf + 1;
}
// printf(" :(%lld) %lld\n", lf, ans);
}
printf("%lld", ans);
return 0;
}
对同色部分先内部排个序,每次更改后,对更改后的颜色部分排个序,然后暴力输出即可。预处理最差复杂度是
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll n, m, k, c[mn], p, q, mod = 1e9 + 7;
char v[mn];
bool cmp(char x, char y) { return x > y; }
void sorts(ll cmd = 0)
{
for (ll i = 1, lf = 1; i <= n; ++i)
{
if (c[i] != c[i + 1])
{
if (cmd == 0 || (cmd == 1 && c[i] == q))
{
sort(v + lf, v + i + 1, cmp);
}
lf = i + 1;
}
}
ll r = 0;
for (ll i = 1; i <= n; ++i)
{
r = (r * 10 + v[i] - '0') % mod;
}
printf("%lld\n", r);
}
signed main()
{
sc(n), sc(m), sc(k), scanf("%s", v + 1);
for (ll i = 1, lf = 1; i <= n; ++i)
{
sc(c[i]);
}
sorts();
while (k--)
{
sc(p), sc(q);
for (ll i = 1; i <= n; ++i)
{
if (c[i] == p)
{
c[i] = q;
}
}
sorts(1);
}
return 0;
}
解法一:直接按题意模拟平衡树操作即可,因为只有
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1024
ll n;
struct tr
{
ll fa, lc, rc;
} ta[mn], tb[mn], tc[mn];
void rotate(ll a)
{
ll b = tc[a].fa;
ll fa = tc[b].fa;
if (tc[b].lc == a) //右旋
{
tc[b].lc = tc[a].rc;
// tc[tc[a].rc].fa = b;
tc[a].rc = b;
// tc[b].fa = a;
}
else
{
tc[b].rc = tc[a].lc;
tc[a].lc = b;
}
if (fa)
{
if (tc[fa].lc == b)
{
tc[fa].lc = a;
}
else
{
tc[fa].rc = a;
}
}
}
bool ck()
{
for (ll i = 1; i <= n; ++i)
{
if (tc[i].lc != ta[i].lc || tc[i].rc != ta[i].rc)
{
// printf("-- %lld\n", i);
return false;
}
}
return true;
}
void scc(tr *t)
{
for (ll i = 1, l, r; i <= n; ++i)
{
sc(l), sc(r);
t[i].lc = l, t[i].rc = r;
t[l].fa = t[r].fa = i;
}
}
// void prt(tr *t)
// {
// for (ll i = 1; i <= n; ++i)
// {
// printf("%lld %lld %lld\n", t[i].lc, t[i].rc, t[i].fa);
// }
// printf("\n");
// }
signed main()
{
sc(n), scc(ta), scc(tb);
memcpy(tc, tb, sizeof ta);
if (ck())
{
printf("0\n");
return 0;
}
for (ll i = 1; i <= n; ++i)
{
if (tb[i].fa)
{
// printf("<< %lld\n", i);
memcpy(tc, tb, sizeof ta);
rotate(i);
// prt(tc);
if (ck())
{
printf("1\n%lld", i);
return 0;
}
}
}
return 0;
}
// ll firot(tr *t)
// {
// for (ll i = 1; i <= n; ++i)
// {
// if (t[i].fa == 0)
// {
// return i;
// }
// }
// return 0;
// }
解法二:事实上,题意有一句话说: 树旋转的本质是二叉树旋转轴节点与其父节点父子关系的改变
,分析后可以发现,旋转前后会出现互为父子的现象,直接找即可。题解说是
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
struct tree
{
int fa;
int ch[2];
} t[MAXN], a[MAXN];
int input_tree(tree *t, int n)
{
int x, y;
std::vector<bool> v(n + 1, true);
for (int i = 1; i <= n; ++i)
{
scanf("%d %d", &x, &y);
v[x] = v[y] = false;
t[i].ch[0] = x;
t[i].ch[1] = y;
if (x)
t[x].fa = i;
if (y)
t[y].fa = i;
}
for (int i = 1; i <= n; ++i)
{
if (v[i])
return i;
}
return -1;
}
int n, root_a, root_t;
vector<int> vv;
int main()
{
scanf("%d", &n);
root_a = input_tree(a, n);
root_t = input_tree(t, n);
for (int i = 1; i <= n; ++i)
{
int j = t[i].fa;
if (j && a[j].fa == i)
{
printf("1\n%d\n", i);
return 0;
}
}
printf("0\n");
}
对于正着来的过程,设本来的向量是
那么要逆这个过程,就可以从
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 2048
ll n, m, a[mn], w[mn], mod = 1e9 + 7, b[mn];
signed main()
{
sc(m);
for (ll i = 1; i <= m; ++i)
{
sc(a[i]);
}
a[0] = b[0] = 1;
for (ll i = 1; i <= m; ++i)
{
while (a[i] != 0)
{
w[++n] = i * 2;
for (ll j = i; j <= m; ++j)
{
a[j] = (a[j] - a[j - i] + mod) % mod;
if (j - 2 * i >= 0)
{
a[j] = (a[j] - a[j - 2 * i] + mod) % mod;
}
}
}
// for (ll j = 1; j <= m; ++j)
// {
// printf("%lld ", a[j]);
// }
// printf("\n");
}
// for (ll i = m; i >= 1; --i)
// {
// if (w[i] != 0)
// {
// n = i;
// break;
// }
// }
printf("%lld\n", n);
for (ll i = 1; i <= n; ++i)
{
printf("%lld ", w[i]);
}
return 0;
}
思路很快出,但答案很晚才算出来,思维和实现难度较大
核心思路是不断拆分,先求
对
特别地,若
拓展到
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll p, l, r, L, R;
ll cal(ll r, ll R) //r>=0,R>=0
{
// printf("%lld %lld %lld\n", r, R, R / p * min(r + 1, p) + min(r + 1, R % p));
// printf("cal %lld %lld\n", r, R);
if (r < 0)
{
return 0;
}
return R / p * min(r + 1, p) + min(r + 1, R % p + 1);
}
ll cal2(ll l, ll r, ll R) //l,r,R>=0
{
// printf("cal2 %lld %lld %lld\n", l, r, R);
if (R < 0)
{
return cal2(-r, -l, -R);
}
ll ans = cal(r, R);
if (l > 0)
{
ans -= cal(l - 1, R);
}
return ans;
}
ll cal3(ll l, ll r, ll L, ll R) //l,r,L,R>=0
{
// printf("cal3 %lld %lld %lld %lld\n", l, r, L, R);
if (R < 0)
{
return cal3(-r, -l, -R, -L);
}
ll ans = cal2(l, r, R);
if (L > 0)
{
ans -= cal2(l, r, L - 1);
}
if (L < 0)
{
ans += cal3(-r, -l, 1, -L);
}
return ans;
}
// ll pal(ll l, ll L) //[l,0], [L,0],l<0,L<0
// {
// return cal(-l, -L);
// }
// ll pal2(ll l, ll r, ll L)
// {
// ll ans = pal(l, L);
// if (r < 0)
// {
// ans -= pal(r + 1, L);
// }
// return ans;
// }
// ll pal3(ll l, ll r, ll L, ll R)
// {
// r = min(r, 0LL);
// ll ans = pal2(l, r, L);
// if (R < 0)
// {
// ans -= pal2(l, r, R + 1);
// }
// return ans;
// }
ll res, nl, nr, pl, pr;
signed main()
{
sc(p), sc(l), sc(r), sc(L), sc(R);
p = abs(p);
printf("%lld", cal3(l, r, L, R));
// if (R <= 0)
// {
// res = cal3(-r, -l, -R, -L);
// }
// else if (L < 0)
// {
// res = cal3(l, r, 1, R) + pal3(l, r, L, 0);
// // printf("%lld %lld\n", cal3(l, r, 0, R), pal3(l, r, L, -1));
// }
// else
// {
// res = cal3(l, r, L, R);
// }
// printf("%lld", res);
return 0;
}
/*
5 1 1 -10 12
3
3 0 1 -7 9
9
123 -10000 10000 -10000 10000
20001
67 -7 3 -100 -11
-82 -111 -2 -7 9999
*/
注意到一点:不是任给两棵树都能把它们旋转到一样的,例如对输入:
5
2 3 0 0 4 5 0 0 0 0
2 3 4 0 0 0 0 0 1 0
不能找到任何一个解法。
理解了旋转的本质是父子对换后,就可以发现类似于不断在树上跑插入排序了。一开始已插入好的部分只有根节点的父亲(空)。先序遍历排好序后的树,如果发现当前遍历到的节点在乱树里的父亲节点没有到位(不在排序好的集合里),证明这个点没有排序好,可以不断旋转这个点为父亲节点,直到它的父亲排好了,证明它也排好了,这样一定能在
用子问题的思维来解释上面过程的原因的话,对一个节点不断转,它就会变成根。在上述 DFS 先序遍历过程中,当前子树里,若排好的树的根没有就位,那就在乱树里强行让它就位,暂时不管其他的点,那么这次 DFS 就把排好子树里这个子树根节点就位了。然后再同理处理左右子树。因为题目保证有解,所以这么做一定能得到解。
代码实际上是
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1010
struct tr
{
ll fa, ch[2];
} t[mn], a[mn];
ll n, rota, rott;
ll scc(tr *t)
{
bool vis[mn] = {};
for (ll i = 1, u, v; i <= n; ++i)
{
sc(u), sc(v);
t[i].ch[0] = u;
t[i].ch[1] = v;
t[u].fa = t[v].fa = i;
vis[u] = vis[v] = true;
}
for (ll i = 1; i <= n; ++i)
{
if (!vis[i])
{
return i;
}
}
return -1;
}
void rotate(ll a)
{
ll b = t[a].fa;
ll fa = t[b].fa;
ll aisrf = (a == t[b].ch[1]);
ll bisrf = (b == t[fa].ch[1]);
ll ac = t[a].ch[1 ^ aisrf];
t[a].fa = fa;
t[a].ch[1 ^ aisrf] = b;
t[b].ch[0 ^ aisrf] = ac;
t[b].fa = a;
t[ac].fa = b;
t[fa].ch[0 ^ bisrf] = a;
}
vector<ll> ans;
bool v[mn] = {true};
void splay(ll rot)
{
while (!v[t[rot].fa])
{
ans.push_back(rot);
rotate(rot);
}
}
void dfs(ll rot)
{
if (!rot)
{
return;
}
splay(rot);
v[rot] = true;
dfs(a[rot].ch[0]);
dfs(a[rot].ch[1]);
}
signed main()
{
sc(n), rota = scc(a), rott = scc(t);
dfs(rota);
printf("%lld\n", ans.size());
for (ll i : ans)
{
printf("%lld\n", i);
}
return 0;
}
整除分块:对
可以作等价变换:
枚举取值仅有
原式化为
使得
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll p, ans, l, r, L, R;
ll it(ll a1, ll b1, ll a2, ll b2)
{ //[a1,b1]∩[a2,b2]的长度
return max(0LL, min(b1, b2) - max(a1, a2) + 1);
}
ll ap(ll a, ll d, ll lf, ll rf) //数列首项,公差,区间范围
{
ll n = rf - lf + 1;
if (d == 0) //其实应该只有一次执行到这里
{
if (a >= l && a <= r) // p在[l,r],这一段恒成立
{
// printf("%lld %lld\n", lf, lf + n);
//负数根据题目描述,可知确实是这样的(拿掉符号再来一次)
return it(L, R, lf, lf + n) + it(L, R, -lf - n, -lf);
}
else
{
return 0;
}
}
ll s = 0, t = -1; //空区间初始
//数列a-dx
if (a > r) //开始值偏移
{ //即(a-r)/d的上取整,使得偏移后首项一定在[l,r]内
s = (a - r + d - 1) / d;
}
if (a >= l) //长度为a-l,公差为d,可得项数为t
{
t = (a - l) / d;
}
// printf("%lld %lld %lld %lld %lld %lld\n", a, d, lf, rf, s, t);
return it(L, R, lf + s, lf + t) + it(L, R, -lf - t, -lf - s);
}
signed main()
{
sc(p), sc(l), sc(r), sc(L), sc(R);
//显而易见不大难理解的的特判(参考J题)
if (p < 0)
{
p = -p, l = -l, r = -r, swap(l, r);
}
l = max(l, 0LL);
// p%i=p-(p/i)*i的p/i相同时,p/i公差d,首项a,值域[lf,rf]
for (ll lf = 1, rf; lf <= p; lf = rf + 1)
{
rf = p / (p / lf);
ll a = p % lf, d = p / lf;
// printf("%lld %lld %lld %lld\n", a, d, lf, rf);
//可以发现P%x的值域实际上是以a为首项-d为公差的等差数列
ans += ap(a, d, lf, rf);
}
// p%x(x>p)的范围是[1,1e9]
ans += ap(p, 0, p + 1, 1e9);
printf("%lld", ans);
return 0;
}
启发式合并,核心思路是每次把较少块的颜色并到较多颜色的块上。
快速计算值:对
因为合并时按多寡不按题意,所以需要把题目给的颜色可逆映射成一个 ID ,具体而言,每次目标颜色的 ID 映射成较多的那一个 ID,源颜色映射为较少的那一个 ID
为了能够快速根据下标找到每一块,可以使用并查集,并且可以设每块有一个范围外的根下标,可以记为
对于合并过程,枚举要被染掉的颜色,对于这个颜色的每一块,查找其左右相邻的块,如果是目标颜色(能找到一定不是空块),直接合并两个块,然后答案减掉本来的部分,加上合并后的部分
最坏情况有
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll n, m, k, fa[mn * 2], itc[mn], cti[mn], pw[mn], mod = 1e9 + 7;
ll c[mn], inv9 = 111111112, ans, p, q;
char s[mn];
ll findf(ll x)
{
while (x != fa[x])
{
x = fa[x] = fa[fa[x]];
}
return x;
}
struct block
{
ll lf, rf, b[11] = {}, alive = 1, c;
ll get()
{
ll res = 0;
for (ll i = 9; i >= 0; --i)
{
ll v = (pw[b[i]] - 1 + mod) * inv9 % mod * i % mod;
res = (res * pw[b[i]] % mod + v) % mod;
}
return res * pw[n - rf] % mod;
}
};
block operator+(const block &lfs, const block &rfs)
{
block r;
r.lf = min(lfs.lf, rfs.lf), r.rf = max(lfs.rf, rfs.rf);
r.c = lfs.c, r.alive = 1;
for (ll i = 0; i <= 9; ++i)
{
r.b[i] = lfs.b[i] + rfs.b[i];
}
return r;
}
vector<block> v;
vector<ll> cb[mn];
void solve(vector<ll> &from, vector<ll> &to, ll &c_to)
{
for (auto &i : from)
{
if (v[i].alive != 1)
{
continue;
}
v[i].c = c_to;
ans = (ans - v[i].get() + mod) % mod;
if (v[i].lf > 1)
{
block &vl = v[findf(v[i].lf - 1) - (n + 1)];
if (vl.c == c_to)
{
ans = (ans - vl.get() + mod) % mod;
fa[findf(v[i].lf - 1)] = findf(n + 1 + i);
v[i] = vl + v[i];
vl.alive = 0;
}
}
if (v[i].rf < n)
{
block &vr = v[findf(v[i].rf + 1) - (n + 1)];
if (vr.c == c_to)
{
ans = (ans - vr.get() + mod) % mod;
fa[findf(v[i].rf + 1)] = findf(n + 1 + i);
v[i] = v[i] + vr;
vr.alive = 0;
}
}
ans = (ans + v[i].get()) % mod;
to.emplace_back(i);
}
from.clear();
}
signed main()
{
sc(n), sc(m), sc(k);
for (ll i = 1; i <= 2 * n; ++i)
{
fa[i] = i;
}
for (ll i = 1; i <= m; ++i)
{
itc[i] = cti[i] = i;
}
pw[0] = 1;
for (ll i = 1; i <= n; ++i)
{
pw[i] = pw[i - 1] * 10 % mod;
}
scanf("%s", s + 1);
for (ll i = 1; i <= n; ++i)
{
sc(c[i]);
}
for (ll lf = 1, rf = 1; lf <= n; lf = rf + 1, rf = lf)
{
while (rf + 1 <= n && c[rf + 1] == c[lf])
{
++rf;
}
block nw;
nw.lf = lf, nw.rf = rf, nw.alive = 1, nw.c = c[lf];
memset(nw.b, 0, sizeof nw.b);
for (ll i = lf, des = n + 1 + v.size(); i <= rf; ++i)
{
++nw.b[s[i] - '0'];
fa[findf(i)] = findf(des);
}
cb[c[lf]].emplace_back(v.size());
v.emplace_back(nw);
}
for (auto &i : v)
{
ans = (ans + i.get()) % mod;
}
printf("%lld\n", ans);
while (k--)
{
sc(p), sc(q);
if (p == q)
{
printf("%lld\n", ans); // cao
continue;
}
ll i_from = cti[p], i_to = cti[q], i_dest, i_emp;
vector<ll> &from = cb[i_from], &to = cb[i_to];
if (from.size() < to.size())
{
i_dest = i_to, i_emp = i_from;
solve(from, to, i_to);
}
else
{
i_dest = i_from, i_emp = i_to;
solve(to, from, i_from);
}
cti[p] = i_emp, itc[i_emp] = p;
cti[q] = i_dest, itc[i_dest] = q;
printf("%lld\n", ans);
}
return 0;
}
比赛日志:
一开始直接秒了E题,一眼递归TLE,放了几个例子猜结论猜对了,1分钟。然后看H题,因为方程推导上自己出了点问题,即右边没有开方,所以一直没算对。然后卡了几分钟才对,就A掉了,6分钟。然后K题,观察样例提示得出规律,然后很快切掉了,11分钟。然后好像是二选一(这场比赛很多时候都是两题过题数差不多的)。然后我捡了个A,很快滑窗思路有了,思考了一会儿然后实现,就交了就AC了,21分钟。然后C题,裸差分题,一下子平推掉了,26分钟。然后看F,一道小模拟,也是一下子平推掉了,32分钟。然后J题,想了一会儿之后想到了欧拉筛+lcm的唯一分解定理表达式,不会TLE地写,然后对了,切掉了,47分钟。然后二选一,我选了个比较有把握代码量较小的DP先写,I题,又是01背包变式,思考了一下,然后就过掉了,59分钟。然后回头写计算几何题,套板子过掉了,76分钟。至此,一个钟多一会的时间过了9道题,剩下又是一个二选一,我对比较少人的B反而一下有思路,跟之前的某题题解有点像,维护长数字的某一段查询,然后加上了进制而已,用树状数组维护不同进制的长数字,然后再开一个维护区间最小值。然后想错了,改成了最大值,又修了点bugs之类的,然后一交一个惨。然后自查样例,发现区间最大值不能用树状数组维护,要修改无法实现单点修改(更小的值无法覆盖之前更大的值),于是写了线段树,然后发现不用懒标记,然后过掉了。150分钟了。然后G,很快发现了公式,但是一交一个WA。然后我百思不得其解,改了个幂取模,然后得出了不一样的答案,又交一个WA。我更郁闷了,思考了很久,然后猛然发现一个事实,幂指数不能够直接取模,即
可以看出函数是把一个数不断二分,最后拆分是满足恒等性的(即
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
long long f(long long x)
{
if (x == 1)
return 1;
return f(x / 2) + f(x / 2 + x % 2);
}
signed main()
{
ll x;
sc(x);
printf("%lld", x);
return 0;
}
解方程:$(2x)^2=3a^2$ 得
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
signed main()
{
ll x;
db a = 0;
sc(x);
a = sqrt(4.0 * x * x / 3.0);
printf("%lf", a * a * a);
return 0;
}
由于
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, x;
signed main()
{
sc(x);
for (ll i = x; i > 0; i >>= 1)
{
++n;
}
printf("%lld\n", x * ((1 << n) + 1));
return 0;
}
滑动窗口算法。每次遇到
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 2000010
ll n, k, ans, rs, lf, rpos[mn], ri = 100;
char c[mn];
signed main()
{
sc(n), sc(k);
scanf("%s", c + 1);
lf = 1;
for (ll i = 1; i <= n; ++i)
{
if (c[i] == 'P')
{
rs = 0;
lf = i + 1;
}
else
{
if (c[i] == 'R')
{
rpos[ri++] = i;
}
ll lt = rpos[ri - k];
if (lt >= lf)
{
ans += (lt - lf + 1);
}
}
}
printf("%lld", ans);
return 0;
}
很显然是差分裸题,直接叠差分然后计算一次前缀和即可过题
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 200010
ll n, t, x, b[mn], r[mn], ans;
char c[mn];
signed main()
{
sc(n), sc(t), scanf("%s", c + 1);
for (ll i = 1; i <= n; ++i)
{
sc(x);
if (c[i] == 'B')
{
b[x]++;
b[x + t]--;
}
else
{
r[x]++;
r[x + t]--;
}
}
for (ll i = 1; i < mn; ++i)
{
b[i] += b[i - 1];
r[i] += r[i - 1];
if (b[i] > 0 && r[i] == 0)
{
ans++;
}
}
printf("%lld", ans);
return 0;
}
小模拟,真·签到题
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
map<char, ll> m;
char c[1010];
ll n, s;
signed main()
{
m['C'] = 1;
m['D'] = 2;
m['E'] = 3;
m['F'] = 4;
m['G'] = 5;
m['A'] = 6;
m['B'] = 7;
scanf("%s", c);
n = strlen(c);
for (ll i = 0; i < n; ++i)
{
if (c[i] == '<')
{
--s;
}
else if (c[i] == '>')
{
++s;
}
else
{
printf("%lld", m[c[i]]);
if (s < 0)
{
for (ll i = 0, ie = -s; i < ie; ++i)
{
printf(".");
}
}
else
{
for (ll i = 0, ie = s; i < ie; ++i)
{
printf("*");
}
}
}
}
return 0;
}
唯一分解定理 + 质因数分解求 LCM 的板子题
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 30010
ll l, r, k, prm[mn], he, p[mn];
bool vis[mn];
void euler(ll n = 30000)
{
vis[0] = vis[1] = true;
for (ll i = 2; i <= n; ++i)
{
if (!vis[i])
{
prm[++k] = i;
}
for (ll j = 1; j <= k; ++j)
{
if (prm[j] * i > n)
{
break;
}
vis[prm[j] * i] = true; //合数
if (i % prm[j] == 0)
{
break;
}
}
}
}
ll mod = 1e9 + 7, ans = 1;
ll qpow(ll a, ll b)
{
ll res = 1;
for (; b > 0; b >>= 1)
{
if (b & 1)
{
res = res * a % mod;
}
a = a * a % mod;
}
return res;
}
signed main()
{
// printf("%lld", qpow(2, 10));
euler();
// for (ll i = 1; i <= 10; ++i)
// {
// printf("%lld ", prm[i]);
// }
sc(l), sc(r);
l = max(4LL, l);
for (ll i = l; i <= r; ++i)
{
if (vis[i])
{
++he;
ll x = i;
ll a = 2;
while (a * a <= x)
{
if (x % a == 0)
{
ll cnt = 0;
while (x % a == 0)
{
++cnt;
x /= a;
}
p[a] = max(p[a], cnt);
}
++a;
}
if (x > 1)
{
p[x] = max(p[x], 1LL);
}
}
}
if (he == 0)
{
printf("-1");
return 0;
}
for (ll i = 1; i <= 30000; ++i)
{
if (p[i])
{
// printf("%lld %lld\n", i, p[i]);
ans = ans * qpow(i, p[i]) % mod;
}
}
printf("%lld", ans);
return 0;
}
由于
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1024
ll dp[2][mn], n, k, a, b, s[2][mn], ans;
signed main()
{
sc(n), sc(k);
s[0][0] = 1;
for (ll i = 1, now = 1, prev = 0; i <= n; ++i, now ^= 1, prev ^= 1)
{
sc(a), sc(b);
for (ll j = 0; j < k; ++j)
{
s[now][j] = s[prev][j];
dp[now][j] = dp[prev][j];
}
for (ll j = 0; j < k; ++j)
{
ll h = (j - a % k + k) % k;
if (s[prev][h])
{
dp[now][j] = max(dp[now][j], dp[prev][h] + b);
s[now][j] = 1;
}
}
}
ans = dp[n & 1][0];
if (ans == 0)
{
printf("-1");
}
else
{
printf("%lld", ans);
}
return 0;
}
题解可以通过把无方案(即
计算几何求点到线段距离的纯板子题
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lf", &x)
#define mn 200010
ll n;
db dx, dy;
struct point
{
db x, y;
point(db x = 0, db y = 0) : x(x), y(y) {}
point(const point &p) : x(p.x), y(p.y) {}
db norm() { return x * x + y * y; }
point operator+(point p) { return point(x + p.x, y + p.y); }
point operator-(point p) { return point(x - p.x, y - p.y); }
};
db abst(point p) { return sqrt(p.norm()); }
db cross(point a, point b)
{
return a.x * b.y - a.y * b.x;
}
db dot(point a, point b)
{
return a.x * b.x + a.y * b.y;
}
db solve(point a, point b, point p) //ab与p距离
{
if (dot(b - a, p - a) < 0.0)
{
return abst(p - a);
}
if (dot(a - b, p - b) < 0.0)
{
return abst(p - b);
}
return abst(cross(b - a, p - a) / abst(b - a));
}
db ans = 1e18;
point now, prv, cmp;
signed main()
{
scanf("%lld", &n);
sc(now.x), sc(now.y), sc(cmp.x), sc(cmp.y);
while (n--)
{
sc(dx), sc(dy);
prv = now;
now.x = prv.x + dx;
now.y = prv.y + dy;
// printf("%lf %lf %lf %lf\n", prv.x, prv.y, now.x, now.y);
ans = min(ans, solve(prv, now, cmp));
}
printf("%.12lf", ans);
return 0;
}
受第三第二场集训题解的启发,得以在赛时想出。
不难发现,为了使得表示的值最小,应当贪心地选择允许的最小进制,即所查询的
然后可以开多个线段树或树状数组维护进制区间和。由于长为
根据这个性质,可以用较优的复杂度维护区间进制数,然后再移动即可,因为涉及单点修改和区间查询,可以直接使用树状数组或不带懒标记的线段树
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll mod = 1e9 + 7, n, q, iv[11][mn], pw[11][mn], b[mn], op, x, y;
ll qpow(ll a, ll b)
{
ll res = 1;
for (; b > 0; b >>= 1)
{
if (b & 1)
{
res = res * a % mod;
}
a = a * a % mod;
}
return res;
}
ll inv(ll a)
{
return qpow(a, mod - 2);
}
ll lowbit(ll &k) { return k & -k; }
struct szsz
{
ll a[mn];
void update(ll i, ll v)
{
for (; i <= n; i += lowbit(i))
{
a[i] = (a[i] + v + mod) % mod;
}
}
ll ask(ll rf)
{
ll ans = 0;
for (; rf != 0; rf -= lowbit(rf))
{
ans = (ans + a[rf]) % mod;
}
return ans;
}
ll query(ll lf, ll rf)
{
return (ask(rf) - ask(lf - 1) + mod) % mod;
}
} s[11];
ll mx[mn << 2], laz[mn << 2];
#define lfs r << 1
#define rfs r << 1 | 1
#define mkcf ll cf = (lf + rf) >> 1
void build(ll r, ll lf, ll rf)
{
if (lf == rf)
{
mx[r] = b[lf];
laz[r] = -1;
return;
}
mkcf;
build(lfs, lf, cf);
build(rfs, cf + 1, rf);
mx[r] = max(mx[lfs], mx[rfs]);
laz[r] = -1;
}
void pushdown(ll r, ll lf, ll rf)
{
if (laz[r] == -1)
{
return;
}
mkcf;
mx[lfs] = max(mx[lfs], laz[r]);
mx[rfs] = max(mx[rfs], laz[r]);
laz[lfs] = laz[rfs] = laz[r];
laz[r] = -1;
}
void update_mx(ll r, ll lf, ll rf, ll i, ll v)
{
if (lf == rf)
{
laz[r] = -1;
mx[r] = v;
return;
}
mkcf;
// pushdown(r, lf, rf);
if (i <= cf)
{
update_mx(lfs, lf, cf, i, v);
}
else
{
update_mx(rfs, cf + 1, rf, i, v);
}
mx[r] = max(mx[lfs], mx[rfs]);
}
ll query_mx(ll r, ll lf, ll rf, ll lc, ll rc)
{
if (lf >= lc && rf <= rc)
{
return mx[r];
}
ll res = 0;
mkcf;
if (cf >= lc)
{
res = max(res, query_mx(lfs, lf, cf, lc, rc));
}
if (cf < rc)
{
res = max(res, query_mx(rfs, cf + 1, rf, lc, rc));
}
return res;
}
char ss[mn];
void init()
{
sc(n), sc(q), scanf("%s", ss + 1);
for (ll i = 1; i <= n; ++i)
{
b[i] = ss[n - i + 1] - '0';
}
for (ll i = 2; i <= 10; ++i)
{
iv[i][0] = 1;
pw[i][0] = 1;
}
for (ll i = 2; i <= 10; ++i)
{
for (ll j = 1; j <= n; ++j)
{
pw[i][j] = pw[i][j - 1] * i % mod;
iv[i][j] = inv(pw[i][j]);
}
}
build(1, 1, n);
for (ll i = 2; i <= 10; ++i)
{
for (ll j = 1; j <= n; ++j)
{
s[i].update(j, b[j] * pw[i][j - 1] % mod);
}
}
}
signed main()
{
init();
while (q--)
{
sc(op), sc(x), sc(y);
x = n - x + 1;
if (op == 1)
{
for (ll i = 2; i <= 10; ++i)
{
s[i].update(x, mod - (b[x] * pw[i][x - 1] % mod));
}
b[x] = y;
for (ll i = 2; i <= 10; ++i)
{
s[i].update(x, y * pw[i][x - 1] % mod);
}
update_mx(1, 1, n, x, y);
}
else
{
y = n - y + 1;
swap(x, y);
ll bs = query_mx(1, 1, n, x, y) + 1;
bs = max(bs, 2LL);
ll v = s[bs].query(x, y);
// printf("[%lld] %lld\n", bs, v);
v = v * iv[bs][x - 1] % mod;
printf("%lld\n", v);
}
}
return 0;
}
显然排序原数组后不影响结果。在排序之后,排在第
特别注意在取模意义下1,由于下面的等式不成立:
$$
a^x\bmod p=a^{x\ \bmod\ p}\bmod p
$$
所以不能直接对幂取模。可以想到利用拓展欧拉定理,实现:
$$
a^x\bmod p=a^{x\ \bmod\ \varphi(p)}\bmod p
$$
对质数
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll mod = 1e9 + 7, phi = mod - 1;
ll qpow(ll a, ll b)
{
ll res = 1;
for (; b > 0; b >>= 1)
{
if (b & 1)
{
res = res * a % mod;
}
a = a * a % mod;
}
return res;
}
#define mn 200010
ll n, a[mn], ans = 1, pw[mn];
ll qqpow(ll a, ll b) //a^(2^b)
{
return qpow(a, pw[b] + phi);
}
signed main()
{
sc(n);
pw[0] = 1;
for (ll i = 1; i <= n; ++i)
{
sc(a[i]);
pw[i] = pw[i - 1] * 2 % phi;
}
sort(a + 1, a + 1 + n);
for (ll i = 1; i <= n; ++i)
{
ans = ans * qqpow(a[i], i - 1) % mod * qqpow(a[i], n - i) % mod;
}
printf("%lld", ans);
return 0;
}
这是一道比较神奇的枚举题。赛时想出了一半的步骤,但是剩下的没想出来。问题即求静态数组任意子区间的长为
对第
那么对查询
-
$a$ 在$[l,r]$ 左边,$b$ 在$[l,r]$ 内,$c$ 在$[l,r]$ 右边 -
$a$ 在$[l,r]$ 左边,$bc$ 在$[l,r]$ 内 -
$ab$ 在$[l,r]$ 内,$c$ 在$[l,r]$ 右边
对第一种情况,根据上面类似的算法,可以很快得出公式为:
$$
op_{l-1,a}\times ed_{r+1,c}\times(op_{r,b}-op_{l-1,b})
$$
对剩下两种情况,可以再多预处理一个
预处理时间复杂度为
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 80010
#define al 26
ll op[mn][al], ed[mn][al], sum2[mn][al][al], dp[mn][al][al];
ll tmp[al][al][al], n, q, l, r;
char s[mn], cmd[10];
vector<ll> ids[al];
ll find(ll l, ll r, ll x, ll y) //在区间[l,r]内有子序列<x,y>多少个
{
return sum2[r][x][y] - sum2[l - 1][x][y] - op[l - 1][x] * (op[r][y] - op[l - 1][y]); //[1,r]<x,y>-[1,l-1]<x,y>-[1,l-1]<x>[l,r]<y>
}
signed main()
{
sc(n), sc(q), scanf("%s", s + 1);
for (ll i = 0; i < al; ++i)
{
ids[i].emplace_back(0);
}
for (ll i = 1; i <= n; ++i)
{
ll ci = s[i] - 'a';
ids[ci].emplace_back(i);
for (ll j = 0; j < al; ++j)
{
op[i][j] = op[i - 1][j];
}
++op[i][ci]; //顺前缀和
for (ll j = 0; j < al; ++j)
{
for (ll k = 0; k < al; ++k)
{
sum2[i][j][k] = sum2[i - 1][j][k];
}
sum2[i][j][ci] += op[i - 1][j];
//前i个字符的子串,子序列(j,ci)的数目(简单的滑窗)
}
}
for (ll i = n; i; --i)
{
for (ll j = 0; j < al; ++j)
{
ed[i][j] = ed[i + 1][j];
}
++ed[i][s[i] - 'a']; //逆前缀和
}
for (ll i = 1; i <= n; ++i)
{
for (ll j = 0; j < al; ++j)
{
for (ll k = 0; k < al; ++k)
{
tmp[s[i] - 'a'][j][k] += op[i - 1][j] * ed[i + 1][k];
dp[i][j][k] = tmp[s[i] - 'a'][j][k];
//第i个字母是s[i]-'a',子序列<j,ci,k>的数目是dp[i][j][k]
//对j,k考虑全长,对s[i]-'a'只考虑到当前位置
//tmp的本质是压缩掉i之后的dp
//这是个比较简单的滑窗
}
}
}
while (q--)
{
sc(l), sc(r), scanf("%s", cmd + 1);
ll x1 = cmd[1] - 'a', x2 = cmd[2] - 'a', x3 = cmd[3] - 'a';
if (op[r][x2] - op[l - 1][x2] == 0) //[l,r]没有x2
{
printf("0\n");
continue;
}
ll lf = lower_bound(ids[x2].begin(), ids[x2].end(), l) - ids[x2].begin() - 1; //大于等于l的第一个位置的前一个位置
ll rf = upper_bound(ids[x2].begin(), ids[x2].end(), r) - ids[x2].begin() - 1; //超出r的第一个位置的前一个位置
ll res = dp[ids[x2][rf]][x1][x3] - dp[ids[x2][lf]][x1][x3];
res -= op[l - 1][x1] * find(l, r, x2, x3);
res -= ed[r + 1][x3] * find(l, r, x1, x2);
res -= op[l - 1][x1] * (op[r][x2] - op[l - 1][x2]) * ed[r + 1][x3];
//先剪掉(?b)[?](?) ?即any
//再剪掉三种不合理为(a)[bc](),()[ab](c),(a)[b](c),只希望保留()[abc]()
printf("%lld\n", res);
}
return 0;
}
比赛日志:
这次比赛比较有难度……签到J五分钟过了之后第二题就给我整不会了,重读题目发现是选不重复的问题,我想不出组合数学的式子,然后猜测记忆化搜索复杂度比较低,但是没写出来,debug有问题,然后索性心一横,试试直接上个暴力DFS试试,然后就把G这道题给过掉了,30分钟,不过排名很难看。然后往后是I,这道题一开始想了一会儿,然后发现了只需要考虑相邻区间,那么就直接试着上了个空隙和,一交一发WA。然后仔细思考,发现减得不均匀,然后发现可以用前缀和+二分来做,然后试着交了一发,A掉了,53分钟。然后有两道这时候差不多过题数,但是远低于前三题的题,比较高的A题我去看了,仔细想了,暂时没想出什么办法,只会两针疫苗,不会三针。然后去看D,说是数位DP,于是我就试着推了一下,写了很久很久的实现,用暴力对拍,改了很多版,思考了很久究竟要怎么细节实现(虽然整体思路大概是对的)。然后一交一个WA,然后发现了点问题,如发现l=1不行,修了修又交又WA,然后又错了,然后自己又跑了更多例子,发现尾处理不行,比如11的r=1没有处理上,r=67的7没有处理上,然后又修又交,终于过了,223分钟,大坐牢。然后我倒回去看A,然后有思路了,发现可以枚举中间,把本来的思路往左边复刻一次,就行了,然后实现了下,255分钟过掉了A题。然后我还是比不上他,罚时高,排名也很落后。然后看了看,E题没啥思路,C题有思路(过A之前我甚至在想C),然后不顺次的DP,想想不就是记忆化DFS嘛,搜索一下,过掉了,卡得比较紧张(debug花了点时间),最后八分钟过了题,第292分钟。然后时间已经不够看下面的题目了,我就直接原地结束了。
对方的最优解是全部是桃,如果自己的杀+决斗能耗完血+桃就必胜。
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n, m, k;
signed main()
{
sc(n), sc(m), sc(k);
if (n + m > k)
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
解法一:即求长为13的4重集抽6个元素的所有结果可能有多少种(去重,组合),暴力搜索即可
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll n = 13, m = 6, k = 4;
// ll n = 3, m = 3, k = 2;
struct stt
{
ll a[15];
} ori;
ll h(const stt &s)
{
ll r = 0;
for (ll i = 1; i <= n; ++i)
{
// printf("%lld ", s.a[i]);
r = r * 10 + s.a[i];
}
// printf("\n");
return r;
}
set<ll> s,s2;
ll ans;
void dfs(stt &x, ll lf)
{
if (lf == 0)
{
++ans;
// for (ll i = 1; i <= n; ++i)
// {
// printf("%lld ", x.a[i]);
// }
// printf("\n");
ll hs = h(x);
s2.insert(hs);
return;
}
// ll hs = h(x);
// if (s.find(hs) != s.end())
// {
// return;
// }
// s.insert(hs);
for (ll i = 1; i <= n; ++i)
{
stt nw;
if (x.a[i] < k)
{
for (ll j = 1; j <= n; ++j)
{
nw.a[j] = x.a[j];
}
++nw.a[i];
}
else
{
continue;
}
ll hsw = h(nw);
// printf("%lld\n", hsw);
s.insert(hsw);
dfs(nw, lf - 1);
}
}
signed main()
{
// dfs(ori, m);
// printf("%lld\n", ans);
// ll ans2 = s.size();
// printf("%lld\n", ans2);
// ll ans3 = s2.size();
// printf("%lld", ans3);
printf("18395");
return 0;
}
//78416
//4825860
//26949
//18395
解法二:神奇模拟 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for (ll i = a; i <= b; ++i)
ll ans;
signed main()
{
rep(a1,0,4)
rep(a2,0,4)
rep(a3,0,4)
rep(a4,0,4)
rep(a5,0,4)
rep(a6,0,4)
rep(a7,0,4)
rep(a8,0,4)
rep(a9,0,4)
rep(a10,0,4)
rep(a11,0,4)
rep(a12,0,4)
rep(a13,0,4)
if(a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13==6)++ans;
printf("%lld",ans);
return 0;
}
解法三:也可以纯组合数学推导,具体推导看代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll c(ll uf, ll df)
{
ll res = 1;
for (ll i = df - uf + 1; i <= df; ++i)
res *= i;
for (ll i = 1; i <= uf; ++i)
res /= i;
return res;
}
ll ans;
signed main()
{
ans += c(6, 13); // 1 1 1 1 1 1 -> 13组选6组,每组都是1个
ans += c(5, 13) * c(1, 5); // 2 1 1 1 1 -> 先选5组,再选1组分两个
ans += c(4, 13) * c(2, 4); // 2 2 1 1
ans += c(3, 13); // 2 2 2
ans += c(4, 13) * c(1, 4); // 3 1 1 1
ans += c(3, 13) * c(1, 3) * c(1, 2); // 3 2 1
ans += c(2, 13); // 3 3
ans += c(3, 13) * c(1, 3); // 4 1 1
ans += c(2, 13) * c(1, 2); // 4 2
printf("%lld", ans);
return 0;
}
解法四 (another version of 解法二) 我自己推的枚举表达式:
按组合方式单调枚举,可以发现超过
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define repe(i, a, b) for (ll i = a; i <= b; ++i)
ll cnt;
void check(ll a1, ll a2, ll a3, ll a4, ll a5, ll a6)
{
if (a1 == a5 || a2 == a6 || a1 == a6) //重复
{
return;
}
++cnt;
}
signed main()
{
repe(a1, 1, 13)
repe(a2, a1, 13)
repe(a3, a2, 13)
repe(a4, a3, 13)
repe(a5, a4, 13)
repe(a6, a5, 13)
check(a1, a2, a3, a4, a5, a6);
printf("%lld", cnt);
return 0;
}
每一条说说只影响下一条说说之前的睡眠时间(轮不到它影响下下一条,因为下一条对下下一条的影响更早),所以把原数组分割为若干个上一条说说和这一条说说的时间差,然后按升序排序。每次查询时,小于等于k的时间段全部睡不着,其余时间段都睡得着。所以叠前缀和,先用整体和减去小于等于k的前缀和段,然后再减去k倍剩余段长(大于k的各自要花k分钟睡着),然后跟p比大小
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
ll n, q, t[mn], k, p, s[mn], cnt, a[mn], s2;
signed main()
{
sc(n), sc(q);
for (ll i = 1; i <= n; ++i)
{
sc(t[i]);
}
for (ll i = 1; i < n; ++i)
{
ll v = t[i + 1] - t[i];
a[++cnt] = v;
}
sort(a + 1, a + 1 + cnt);
for (ll i = 1; i <= n; ++i)
{
s[i] = s[i - 1] + a[i];
}
s2 = s[1 + cnt];
while (q--)
{
sc(k), sc(p);
ll idx = lower_bound(a + 1, a + 1 + cnt, k) - a;
// printf("-%lld\n", idx);
ll ans = s2 - s[idx - 1];
ans -= k * (cnt - idx + 1);
// printf("%lld\n", ans);
if (ans >= p)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
return 0;
}
解法一:直接上数位 DP ,设
根据定义,初始化为
递推方程为若
特别地,设
答案为
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll dp[12][12][2]; //后i位,最高位为j,是否出现过1,相邻数位和为素数的方案数
ll prime[] = {2, 3, 5, 7, 11, 13, 17}, ps = 7;
ll l, r, res;
ll dd[12]; //[1,10^(i-1)-1]内的答案
ll ispr(ll x)
{
for (ll i = 0; i < ps; ++i)
{
if (x == prime[i])
{
return 1;
}
}
return 0;
}
ll ask(ll rf)
{
res = 0;
if (rf <= 9 && rf >= 1)
{
return 1;
}
ll bs = 0;
ll b[12] = {};
for (ll i = rf; i; i /= 10)
{
++bs;
b[bs] = i % 10;
}
ll ever1 = 0, ever2 = 0;
for (ll i = bs; i >= 1; --i)
{
ll je = b[i] + (i == 1);
if (je > 0 && i > 1 && !ever2)
{
res += dd[i];
ever2 = 1;
// printf("%lld:%lld\n", i, dd[i]);
}
ll jb = 1 - (i != bs);
for (ll j = jb; j < je; ++j)
{
ll k = (i > 1) ? 1 : ((j == 1) ? 1 : !ever1);
if (i != bs && !ispr(b[i + 1] + j))
{
continue;
}
// printf("%lld %lld %lld %lld\n", i, j, k, dp[i][j][k]);
res += dp[i][j][k];
if (ever1)
{
res += dp[i][j][k ^ 1];
}
}
ever1 |= (b[i] == 1);
if (i != bs && !ispr(b[i] + b[i + 1]))
{
break;
}
}
return res;
}
ll bfcheck(ll lf, ll rf)
{
ll cnt = 0;
for (ll h = lf; h <= rf; ++h)
{
ll has1 = 0;
ll bs = 0;
ll b[12] = {};
for (ll j = h; j; j /= 10)
{
++bs;
b[bs] = j % 10;
has1 |= (b[bs] == 1);
}
if (bs == 1)
{
cnt += (h == 1);
continue;
}
ll ok = 1;
for (ll i = 1; i < bs; ++i)
{
ll isprime = ispr(b[i] + b[i + 1]);
if (isprime == 0)
{
ok = 0;
break;
}
}
if (ok && has1)
{
// printf("%lld\n", h);
++cnt;
}
}
return cnt;
}
signed main()
{
dp[1][1][1] = 1;
for (ll i = 2; i <= 9; ++i)
{
dp[1][i][0] = 1; //没有相邻,条件1恒成立
}
dp[1][0][0] = 1;
for (ll i = 2; i <= 11; ++i)
{
for (ll x = 0; x <= 9; ++x)
{
ll now0 = (x == 1);
for (ll y = 0; y <= 9; ++y)
{
for (ll pr = 0; pr <= 1; ++pr)
{
ll has0 = now0 || ((y == 1) || pr);
ll s = x + y;
ll isprime = 0;
for (ll j = 0; j < ps; ++j)
{
if (s == prime[j])
{
isprime = 1;
break;
}
}
if (!isprime)
{
continue;
}
// printf("%lld %lld %lld %lld %lld %lld %lld %lld [%lld]\n",
// i, x, has0, i - 1, y, pr, dp[i][x][has0], dp[i - 1][y][pr], s);
dp[i][x][has0] += dp[i - 1][y][pr];
}
}
}
}
// dd[2] = 1;
for (ll i = 2; i <= 11; ++i)
{
dd[i] = dd[i - 1];
for (ll j = 1; j <= 9; ++j)
{
dd[i] += dp[i - 1][j][1];
}
}
sc(l), sc(r);
ll ar = ask(r), al = ask(l - 1);
printf("%lld", ar - al);
// printf("\n%lld %lld %lld", bfcheck(l, r), ar, al);
return 0;
}
解法二:可以打表注意到
为了避免无限
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define sc(x) scanf("%lld", &x)
bool p[20];
ll ans, lf, rf;
void dfs(ll x, ll has1)
{
if (x > rf)
{
return;
}
if (x >= lf && has1)
{
++ans;
}
for (ll i = 0; i <= 9; ++i)
{
if (p[x % 10 + i])
{
dfs(x * 10 + i, has1 || (i == 1));
}
}
}
signed main()
{
p[2] = p[3] = p[5] = p[7] = p[11] = p[13] = p[17] = true;
sc(lf), sc(rf);
for (ll i = 1; i <= 9; ++i)
{
dfs(i, i == 1);
}
printf("%lld", ans);
return 0;
}
显然离
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
char c[mn];
ll a[mn], n, m, k, w, q, ans, nx[mn], pr[mn];
ll cal(ll x, ll y)
{
ll p = y - x;
if (x < 1 || y > n || y <= x)
{
return 0;
}
return w - abs(k - p) * q;
}
signed main()
{
sc(n), scanf("%s", c + 1), sc(k), sc(w), sc(q), ++m;
a[0] = -0x3fffffff;
for (ll i = 1; i <= n; ++i)
{
if (c[i] == '0')
{
a[++m] = i;
}
}
a[m + 1] = 0x3ffffffff;
for (ll i = 2; i <= m; ++i)
{
ll j = a[i];
ll nxi = lower_bound(a + 1, a + 1 + m, j + k) - a;
ll nx = max(cal(j, a[nxi]), cal(j, a[nxi - 1]));
ll pri = lower_bound(a + 1, a + 1 + m, j - k) - a;
ll pr = max(cal(a[pri], j), cal(a[pri - 1], j));
// printf("%lld %lld %lld\n", j, nx, pr);
ans = max(ans, nx + pr);
}
printf("%lld", ans);
return 0;
}
解法一:可以用记忆化 DFS 的 DP 方法,设
一个实现细节是避免负数下标,可以把所有分数偏差
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 22
#define mm 18010
ll n, k, s, p[mn], ca[mn], cb[mn], dt = 2001, ans;
ll dp[mn][mm][mn]; //打了i场,现在分数是j,用了k次礼遇
set<ll> me;
void dfs(ll now, ll po, ll ly, ll cn)
{
if (dp[now][po][ly] >= cn)
{
return;
}
// printf("%lld %lld %lld %lld\n", now, po - dt, ly, cn);
// for (auto i : me)
// {
// printf("%lld ", i);
// }
// printf("\n");
dp[now][po][ly] = cn;
if (n == now)
{
return;
}
for (ll i = 1; i <= n; ++i)
{
if (me.find(i) != me.end())
{
continue;
}
// printf("<%lld\n", i);
me.insert(i);
ll poa = po + ca[i];
dfs(now + 1, poa, ly, cn + (poa >= p[now + 1]));
me.erase(i);
if (ly > 0)
{
me.insert(i);
ll pob = po + cb[i];
// printf("?? %lld %lld\n", pob, p[now]);
dfs(now + 1, pob, ly - 1, cn + (pob >= p[now + 1]));
me.erase(i);
}
}
}
signed main()
{
sc(n), sc(k), sc(s), s += dt;
for (ll i = 1; i <= n; ++i)
{
sc(p[i]);
p[i] += dt;
}
for (ll i = 1, a, b, c, d; i <= n; ++i)
{
sc(a), sc(b), sc(c), sc(d);
ca[i] = max(a, b);
cb[i] = max(ca[i], max(c, d));
}
dfs(0, s, k, 1);
// printf("??%lld\n", dp[3][8093 + dt][0]);
for (ll i = 1; i <= 18000; ++i)
{
for (ll j = 0; j <= k; ++j)
{
ans = max(ans, dp[n][i][j]);
}
}
printf("%lld", ans - 1);
return 0;
}
解法二:已确定哪些局使用礼遇时,贪心是最优的(按增加分数从大到小排序打游戏),因为这样的前缀和是最大的,任意对换后得到的结果都不会比这个更优(可证)
所以可以二进制枚举所有礼遇状况,然后计算即可
AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 22
ll n, k, s, p[mn], x[mn], y[mn], z[mn], ans, res;
bool cmp(ll x, ll y) { return x > y; }
signed main()
{
sc(n), sc(k), sc(s);
for (ll i = 1; i <= n; ++i)
{
sc(p[i]);
}
for (ll i = 1, a, b, c, d; i <= n; ++i)
{
sc(a), sc(b), sc(c), sc(d);
x[i] = max(a, b);
y[i] = max(x[i], max(c, d));
}
for (ll h = 0, he = 1 << n; h < he; ++h)
{
ll cnt = 0, v = h;
for (ll i = 1; i <= n; ++i, v >>= 1)
{
if (v & 1)
{
++cnt;
z[i] = y[i];
}
else
{
z[i] = x[i];
}
}
if (cnt <= k)
{
sort(z + 1, z + 1 + n, cmp);
ll r = s;
res = 0;
for (ll i = 1; i <= n; ++i)
{
r += z[i];
res += (r >= p[i]);
}
ans = max(ans, res);
}
}
printf("%lld", ans);
return 0;
}
初始三者能力值为矩阵 1,2,3
后,分别等效于将其乘以矩阵:
$$
\begin{bmatrix}
1&0&0\
\dfrac12&\dfrac12&0\
\dfrac12&0&\dfrac12
\end{bmatrix}
\quad
\begin{bmatrix}
\dfrac12&\dfrac12&0\
0&1&0\
0&\dfrac12&\dfrac12
\end{bmatrix}
\quad
\begin{bmatrix}
\dfrac12&0&\dfrac12\
0&\dfrac12&\dfrac12\
0&0&1\
\end{bmatrix}
$$
使用线段树维护单点修改和区间查询,每个节点都是矩阵
实现上,查询时用单位矩阵乘以左查询和右查询的结果;注意到只有单点修改,所以不需要设懒标签
本题很诡异,内存实际限制得很死(虽然显示256MB),开 long long
矩阵会 CE ,但开 int
就没事
时间复杂度为
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll mod = 998244353, n, m, inv2 = 499122177, cmd, x, y;
struct matrix
{
int v[4][4] = {};
ll n = 3, m = 3;
matrix operator*(const matrix &p) const
{
matrix r;
memset(r.v, 0, sizeof r.v);
r.n = n, r.m = p.m;
for (ll i = 1; i <= n; ++i)
{
for (ll j = 1, x = 0; j <= p.m; ++j, x = 0)
{ //因为忘记最后补上 ,x=0 导致一直 debug 没 de 出来
for (ll k = 1; k <= m; ++k)
{
x = (x + 1LL * v[i][k] * p.v[k][j]) % mod;
}
r.v[i][j] = x;
}
}
return r;
}
void init(ll x = 3, ll y = 3)
{
n = x, m = y;
memset(v, 0, sizeof v);
for (ll i = 1; i <= 3; ++i)
{
v[i][i] = 1;
}
}
} a[mn << 2], b[4];
char c[mn];
#define lfs p << 1
#define rfs p << 1 | 1
#define mkcf ll cf = (lf + rf) >> 1
void build(ll p, ll lf, ll rf)
{
if (lf == rf)
{
memcpy(a[p].v, b[c[lf] - '0'].v, sizeof a[p].v);
return;
}
mkcf;
build(lfs, lf, cf);
build(rfs, cf + 1, rf);
a[p] = a[lfs] * a[rfs];
}
void update(ll p, ll lf, ll rf, ll s)
{
if (lf == rf)
{
memcpy(a[p].v, b[c[lf] - '0'].v, sizeof a[p].v);
return;
}
mkcf;
if (s <= cf)
{
update(lfs, lf, cf, s);
}
else
{
update(rfs, cf + 1, rf, s);
}
a[p] = a[lfs] * a[rfs];
}
matrix query(ll p, ll lf, ll rf, ll lc, ll rc)
{
if (lf >= lc && rf <= rc)
{
return a[p];
}
matrix q;
q.init();
mkcf;
if (cf >= lc)
{
q = q * query(lfs, lf, cf, lc, rc);
}
if (cf < rc)
{
q = q * query(rfs, cf + 1, rf, lc, rc);
}
return q;
}
signed main()
{
b[1].v[1][1] = 1;
b[1].v[2][1] = b[1].v[2][2] = b[1].v[3][1] = b[1].v[3][3] = inv2;
b[2].v[2][2] = 1;
b[2].v[1][1] = b[2].v[1][2] = b[2].v[3][2] = b[2].v[3][3] = inv2;
b[3].v[3][3] = 1;
b[3].v[1][1] = b[3].v[1][3] = b[3].v[2][2] = b[3].v[2][3] = inv2;
sc(n), sc(m), scanf("%s", c + 1);
build(1, 1, n);
while (m--)
{
sc(cmd), sc(x), sc(y);
if (cmd == 1)
{
c[x] = '0' + y;
update(1, 1, n, x);
}
else
{
matrix ans;
ans.n = 1, ans.m = 3, ans.v[1][1] = ans.v[1][2] = ans.v[1][3] = 1;
matrix md = query(1, 1, n, x, y);
ans = ans * md;
printf("%d %d %d\n", ans.v[1][1], ans.v[1][2], ans.v[1][3]);
}
}
return 0;
}
将对局信息按先后排序后;可以发现,每次已知一个对局信息,就可以唯一地确定截止目前对局双方分别选了多少张图(由选图规则易知),那么在两个已知之间隔的未知区间里,设双方分别选了
严格证明可以这么推导:每一局可以由双方选择,方案数为
特别注意:不能省略掉胜率,胜率是有用信息。需要事先排序胜率,然后做一个离散化对应,把输入的
补题 AC 代码(其一):
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll n, k, pw[mn], fac[mn], inv[mn], mod = 998244353, h[mn];
ll ans = 1, mp[mn], my[mn], mx, p1, p2, len1, len2, pr;
ll qpow(ll a, ll b = mod - 2)
{
ll r = 1;
for (; b; b >>= 1)
{
if (b & 1)
{
r = r * a % mod;
}
a = a * a % mod;
}
return r;
}
ll c(ll uf, ll df)
{
return fac[df] * inv[uf] % mod * inv[df - uf] % mod;
}
struct frac
{
ll z, m, i;
bool operator<(const frac &p) const
{
return z * p.m > m * p.z;
}
} a[mn];
signed main()
{
sc(n), sc(k), p2 = n + 1;
pw[0] = fac[0] = inv[0] = fac[1] = inv[1] = 1, pw[1] = 2;
for (ll i = 2; i <= n; ++i)
{
pw[i] = pw[i - 1] * 2 % mod;
fac[i] = fac[i - 1] * i % mod;
inv[i] = qpow(fac[i]);
}
for (ll i = 1; i <= n; ++i)
{
sc(a[i].z), sc(a[i].m), a[i].i = i;
}
sort(a + 1, a + 1 + n);
for (ll i = 1; i <= n; ++i)
{
h[a[i].i] = i;
}
for (ll i = 1, op, x, y; i <= k; ++i)
{
sc(op), sc(x), sc(y), mp[x] = op, my[x] = h[y], mx = max(x, mx);
}
for (ll i = 1; i <= n; ++i)
{
if (mp[i] == 1)
{
len1 = my[i] - p1 - 1, p1 = my[i];
len2 = i - pr - 1 - len1, p2 -= len2;
}
else if (mp[i] == 2)
{
len2 = p2 - my[i] - 1, p2 = my[i];
len1 = i - pr - 1 - len2, p1 += len1;
}
if (mp[i] >= 1)
{
pr = i;
ans = ans * c(len1, len1 + len2) % mod;
}
}
printf("%lld", ans * pw[n - mx] % mod);
return 0;
}
对操作
考虑以下核心问题:区间里等差数列值的下标都加上定值 (显然对题目中心点等效于多一次逆操作
将区间分块,块长为
对
进一步地,以
对于查询
当公差为
补题代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
#define sqr 333
ll lowbit(ll &x)
{
return x & -x;
}
ll n, m, op, x, y, len, t[mn], b[sqr][sqr], s[sqr][sqr], n2;
void update(ll i, ll v)
{
for (; i <= n; t[i] += v, i += lowbit(i))
;
}
ll query(ll i)
{
ll res = 0;
for (; i; res += t[i], i -= lowbit(i))
;
return res;
}
ll query2(ll x)
{
ll res = 0;
for (ll i = 1; i <= n2; ++i)
{
res += x / i * s[i][i] + s[x % i][i];
}
return res;
}
signed main()
{
sc(n), sc(m), n2 = sqrt(n);
while (m--)
{
sc(op), sc(x), sc(y);
if (op == 1)
{
update(x, y);
}
else if (op == 2)
{
sc(len);
if (len >= n2)
{
for (ll i = x % len + (x % len == 0 ? len : 0); i <= n; i += len)
{
if (i != x)
{
update(i, y);
}
}
}
else
{
ll c = x % len + (x % len == 0) * len;
b[c][len] += y;
for (ll i = 1; i <= n2; ++i)
{
s[i][len] = s[i - 1][len] + b[i][len];
}
update(x, -y);
}
}
else
{
ll ans = query(y) - query(x - 1);
ans += query2(y) - query2(x - 1);
printf("%lld\n", ans);
}
}
return 0;
}
下面代码比较直观易懂,不多做额外解释了;另外本题有一个结论:
所以在解法时,先把相差甚远安全地降到相差在
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
ll t, x, y, rev;
vector<pair<ll, ll>> ans;
void insert(ll x, ll y)
{
if (!rev)
{
ans.emplace_back(x, y);
return;
}
ans.emplace_back(y, x);
}
bool check(ll x, ll y)
{
if (x < y)
{
swap(x, y);
}
return ((x - y == 2 && x >= 11) || (x - y > 2 && x == 11)) && (x >= 0) && (y >= 0);
}
bool solve(ll x, ll y)
{
for (ll i = 0; i <= 11; ++i)
{
for (ll j = 0; j <= 11; ++j)
{
if (check(i, j) && check(x - i, y - j))
{
insert(i, j), insert(x - i, y - j);
return true;
}
}
}
return false;
}
signed main()
{
for (sc(t); t; --t)
{
sc(x), sc(y), rev = 0, ans.clear();
if (x < y)
{
swap(x, y), rev = 1;
}
while (x - y >= 13)
{
insert(11, 0), x -= 11;
}
bool res = false;
if ((res = check(x, y)) == true)
{
insert(x, y);
}
if (!res)
{
res = solve(x, y);
}
if (!res)
{
printf("NO\n");
}
else
{
printf("YES\n%lld\n", ans.size());
for (auto &i : ans)
{
printf("%lld %lld\n", i.first, i.second);
}
}
}
return 0;
}
一个 80%+ 的解法:
首先打表发现绝大部分数据都可以只用加减乘,且163是质数;所以可以构造出三个数结果加减三个数结果。其中三个数结果两两加减乘有九种情况,括号内加减再乘有四种结果,所以可以用
然后对每次输入,获得前三个元素的组和后三个元素的组,然后作小数量次打乱(如10次),每次打乱后获得两个组,尝试加减这两个组,如果等于 169 就输出答案
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) cin >> x
#define repe(i, a, b) for (ll i = a; i <= b; ++i)
#define mn 15
ll v[mn][mn][mn][mn], t, hv[256], a[6];
string s[mn][mn][mn][mn];
char hc[256], b;
void f()
{
for (ll i = 0; i < 6; ++i)
{
cin >> b;
a[i] = hv[b];
}
for (ll i = 0; i < 10; ++i)
{
random_shuffle(a, a + 6);
for (ll j = 0; j < 13; ++j)
{
for (ll k = 0; k < 13; ++k)
{
ll x = v[a[0]][a[1]][a[2]][j];
ll y = v[a[3]][a[4]][a[5]][k];
string &sx = s[a[0]][a[1]][a[2]][j];
string &sy = s[a[3]][a[4]][a[5]][k];
if (x + y == 163)
{
cout << sx << '+' << sy << '\n';
return;
}
if (x - y == 163)
{
cout << sx << '-' << sy << '\n';
return;
}
if (y - x == 163)
{
cout << sy << '-' << sx << '\n';
return;
}
if (x * y == 163)
{
cout << sx << '*' << sy << '\n';
return;
}
}
}
}
cout << "1+1\n";
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
for (ll i = 2; i <= 9; ++i)
{
hv[i + '0'] = i;
hc[i] = i + '0';
}
hc[1] = 'A', hc[10] = 'T', hc[11] = 'J', hc[12] = 'Q', hc[13] = 'K';
hv['A'] = 1, hv['T'] = 10, hv['J'] = 11, hv['Q'] = 12, hv['K'] = 13;
repe(i, 1, 13) repe(j, 1, 13) repe(k, 1, 13)
{
string ii(1, hc[i]), jj(1, hc[j]), kk(1, hc[k]);
ll h = 0;
v[i][j][k][h++] = i + j + k;
v[i][j][k][h++] = i + j - k;
v[i][j][k][h++] = i + j * k;
v[i][j][k][h++] = i - j + k;
v[i][j][k][h++] = i - j - k;
v[i][j][k][h++] = i - j * k;
v[i][j][k][h++] = i * j + k;
v[i][j][k][h++] = i * j - k;
v[i][j][k][h++] = i * j * k;
v[i][j][k][h++] = (i + j) * k;
v[i][j][k][h++] = (i - j) * k;
v[i][j][k][h++] = i * (j + k);
v[i][j][k][h++] = i * (j - k);
repe(l, 0, 12) s[i][j][k][l].push_back('(');
h = 0;
s[i][j][k][h++] += (ii + "+" + jj + "+" + kk);
s[i][j][k][h++] += (ii + "+" + jj + "-" + kk);
s[i][j][k][h++] += (ii + "+" + jj + "*" + kk);
s[i][j][k][h++] += (ii + "-" + jj + "+" + kk);
s[i][j][k][h++] += (ii + "-" + jj + "-" + kk);
s[i][j][k][h++] += (ii + "-" + jj + "*" + kk);
s[i][j][k][h++] += (ii + "*" + jj + "+" + kk);
s[i][j][k][h++] += (ii + "*" + jj + "-" + kk);
s[i][j][k][h++] += (ii + "*" + jj + "*" + kk);
s[i][j][k][h++] += ("(" + ii + "+" + jj + ")*" + kk);
s[i][j][k][h++] += ("(" + ii + "-" + jj + ")*" + kk);
s[i][j][k][h++] += (ii + "*(" + jj + "+" + kk + ")");
s[i][j][k][h++] += (ii + "*(" + jj + "-" + kk + ")");
repe(l, 0, 12) s[i][j][k][l].push_back(')');
}
for (sc(t); t; --t)
{
f();
}
return 0;
}
比赛日志:
开局I题高精度,手写WA了,咯噔一下,然后修了一下,又交又WA,然后开始气急败坏,然后在明知python会超时的情况下失去理智又交了一发。然后改代码,发现是少打了一个0,然后终于对了。开幕雷击,直接把我心态打崩了,整个人都不好了。16分钟。
然后F题,想出了规律,交,WA。然后造多几个样例,发现是1到-1不行,然后再改再交,对了。32分钟。然后E题,一秒出思路,WA掉了。然后我怀疑题意是另外的样子,然后对这写,又WA了,改了改又WA了,然后改回本来的思路再试试,修正了bugs,然后终于对了,63分钟。我的心态已经更加分崩离析了。整个一小时内不断地大声叫骂。然后D题,一开始看错题目,以为最多删两次(思维定势+眼瞎+心态不好所以状态低下了),然后WA了一次,然后改然后对。79分钟。
最多人过的B题不会写,然后比较少人的J有点思路,然后分析出生成函数,然后得到了表达式,然后用已经过了的题目自测跑,发现TLE,阶乘逆元不好求。然后思来想去无法解决,然后查了下百度,然后会线性复杂度求了,然后就过了这道题,125分钟,第一个一发过的题目。然后B还是不会,但是少人的H一眼一个爆搜DFS博弈论,实现好了,一交一个WA,发现是测试时n=4没改回来,一改瞬间A掉了,154分钟。然后B题,终于有点思路了,但是实现出了问题,还有就是细节问题了,然后改来改去,重写了三版代码,终于过了,一发,236分钟。终于成功反超了,但是排名还是不算特别高。后来剩下的题,G题没思路,可能是并查集我猜,然后A题也没思路,C题没细看,A读懂之后猛然想可不可能是字符串卷积,但是不会计数,然后剩下时间就都没进展了。最后排名109……
高精度裸题,能实现就行
赛时AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 200010
ll k, as, bs, v1, v2;
char a[mn], b[mn], c[mn];
ll get(char x)
{
if (isdigit(x))
{
return x - '0';
}
return 0;
}
signed main()
{
sc(k), scanf("%s%s", a + 1, b + 1);
as = strlen(a + 1);
bs = strlen(b + 1);
reverse(a + 1, a + 1 + as);
reverse(b + 1, b + 1 + bs);
// puts(a + 1);
// puts(b + 1);
for (ll i = 1; i < 200003; ++i)
{
v1 += get(a[i]) + get(b[i]);
v2 = v1 / k;
v1 = v1 % k;
c[i] = v1 + '0';
v1 = v2;
v2 = 0;
}
ll ed = 0;
for (ll i = 200005; i >= 1; --i)
{
if (isdigit(c[i]) && c[i] != '0')
{
ed = i;
break;
}
}
// printf("%lld %lld\n",ed,c[ed]);
for (ll i = ed; i >= 1; --i)
{
putchar(c[i]);
}
return 0;
}
题解思路:转置,然后先不处理进位地逐个加,然后逐个处理进位
int main()
{
sc(k);
sc(a+1);sc(b+1);
int n=strlen(a+1),m=strlen(b+1);
reverse(a+1,a+1+n);
reverse(b+1,b+1+m);
rep(i,1,max(n,m))
{
if(i<=n) c[i]+=a[i]-'0';
if(i<=m) c[i]+=b[i]-'0';
}
n=max(n,m);
rep(i,1,n)
{
c[i+1]+=c[i]/k;
c[i]%=k;
if(c[n+1]) n++;
}
for(int i=n;i>=1;i--) putchar(c[i]+'0');
}
每次更改可以让原式+2或-2,先计算原式结果,然后让其不断往0靠拢,因为必须要用完,如果刚好到0且多出机会还需要再加大一次,到1(或-1),需要变成-1(或1)
处理上可以直接看绝对值,先靠拢到0或1 (如果够),然后再处理残余并对1(+2=3)特判为1
赛时AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll t, k, n;
char s[mn];
signed main()
{
sc(t);
while (t--)
{
scanf("%s", s + 1);
sc(k);
ll cp = 0, cn = 0;
n = strlen(s + 1);
for (ll i = 1; i <= n; ++i)
{
if (s[i] == '+')
{
cp++;
}
else
{
cn++;
}
}
ll v = cp - cn;
// printf("%lld\n", v);
v = abs(v);
ll t = abs(v) / 2;
if (t >= k)
{
printf("%lld\n", v - 2 * k);
}
else
{
v %= 2;
k -= t;
if (k % 2 == 1)
{
v += 2;
}
if (v > 2)
{
v -= 2;
}
printf("%lld\n", v);
}
}
return 0;
}
题解思路类似
题意:对每个骑士,求使得除自己之外的最高攻击下能够存活(剩血>0)所额外需要的血量,输出需求血量和
个人解法:数据范围允许,可以直接结构体排序,若第一名不是自己,用第一名打自己;否则用第二名打自己
赛时AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 200010
ll t, n;
struct knight
{
ll a, h, i;
bool operator<(const knight &x) const { return a > x.a; }
} k[mn];
signed main()
{
sc(t);
while (t--)
{
sc(n);
for (ll i = 1, a, b, h; i <= n; ++i)
{
sc(a), sc(b), sc(h);
k[i].i = i;
k[i].a = a;
k[i].h = b + h;
}
sort(k + 1, k + 1 + n);
ll ans = 0;
for (ll i = 1; i <= n; ++i)
{
ll mx = k[i].i == k[1].i ? k[2].a : k[1].a;
ans += max(mx - k[i].h + 1, 0LL);
// printf("%lld %lld ", k[i].i, mx);
// printf("<%lld\n", ans);
}
printf("%lld\n", ans);
}
return 0;
}
题解思路:维护前缀max和后缀max,对每个骑士,求他前面的前缀max和他后面的后缀max更大者跟自己的差值,题解代码:
const int N=2e5+5,mod=998244353;
int n,a[N],b[N],h[N];
int main()
{
int t;sc(t);
int sum=0;
while(t--)
{
sc(n);
sum+=n;
rep(i,1,n)
vector<int>pre(n+2),bk(n+1);
rep(i,1,n) pre[i]=max(pre[i-1],a[i]);
nep(i,n,1) bk[i]=max(bk[i+1],a[i]);
ll ans=0;
rep(i,1,n)
{
ll s=1ll*max(pre[i-1],bk[i+1])-b[i]+1;
ans+=max(0ll,s-h[i]);
}
out(ans);
}
}
个人思路:贪心,一有就马上匹配,同时开尽可能多的匹配线程。由于 t 互不相同,故遍历 s ,如果它为 t 的第 j 个字符,且第 j-1 个字符已匹配次数大于第 j 个字符匹配次数,那么可以匹配第 j 个字符;思路类似 SCNUOJ 的一道题(匹配子序列)
复杂度
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
ll ts, n, m;
char s[mn], t[30];
ll cnt[30];
signed main()
{
sc(ts);
while (ts--)
{
sc(n), sc(m);
scanf("%s%s", s + 1, t + 1);
memset(cnt, 0, sizeof cnt);
cnt[0] = n + 1;
for (ll i = 1; i <= n; ++i)
{
for (ll j = 1; j <= m; ++j)
{
if (s[i] == t[j])
{
if (cnt[j] < cnt[j - 1])
{
++cnt[j];
}
break;
}
}
}
ll ans = n + 1;
for (ll i = 1; i <= m; ++i)
{
ans = min(ans, cnt[i]);
}
printf("%lld\n", ans);
}
return 0;
}
使用生成函数化简原题,设原式有
由二项式定理化简,原式为:
$$
\sum_{y=\max(0,k-a_1)}^{\max(0,a_2)}C_{a_1}^{k-y}C_{a_2}^y2^y
$$
需要在
记
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 10000010
ll mod = 998244353, n, k, a1, a2, modp2 = mod - 2, ans;
ll pw[mn], fac[mn];
ll inv[mn];
ll qpow(ll a, ll b)
{
ll res = 1;
for (; b > 0; b >>= 1)
{
if (b & 1)
{
res = res * a % mod;
}
a = a * a % mod;
}
return res;
}
ll c(ll uf, ll df)
{
// printf("%lld %lld %lld\n", fac[df], inv[uf], inv[df - uf]);
return fac[df] * inv[uf] % mod * inv[df - uf] % mod;
}
signed main()
{
sc(n), sc(k);
for (ll i = 1, v; i <= n; ++i)
{
sc(v);
a1 += (v == 1);
a2 += (v == 2);
}
pw[0] = fac[0] = 1; // = inv[0] = 1;
inv[0] = 1;
for (ll i = 1; i <= n; ++i)
{
pw[i] = pw[i - 1] * 2 % mod;
}
for (ll i = 1; i <= n; ++i)
{
fac[i] = fac[i - 1] * i % mod;
// inv[i] = qpow(fac[i], modp2);
}
inv[n] = qpow(fac[n], modp2);
for (ll i = n - 1; i >= 1; --i)
{
inv[i] = inv[i + 1] * (i + 1) % mod;
// printf("| %lld %lld\n", inv[i], inv[i + 1]);
}
// printf("%lld", c(2, 5));
for (ll y = max(0LL, k - a1); y <= a2; ++y)
{
if (k - y < 0)
{
continue;
}
ans = ans + c(k - y, a1) * c(y, a2) % mod * pw[y] % mod;
ans %= mod;
// printf("%lld %lld\n", y, ans);
}
printf("%lld", ans);
return 0;
}
直接二进制枚举并记忆化搜索所有可能的结果,然后再询问即可
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
map<string, ll> m;
ll dfs(string &now)
{
// cout << now << '\n';
if (m[now] != 0)
{
return m[now];
}
ll whites = 0;
bool haslose = false; //下一局能赢吗
// vector<ll> ws;
for (ll i = 0, ie = now.size(); i < ie; ++i)
{
if (now[i] == 'w')
{
++whites;
string nw = now;
nw[i] = 'b';
if (i > 0)
{
// for (ll &j : ws)
for (ll j = 0; j < i; ++j)
{
string nw2 = nw;
nw2[j] = nw[j] == 'w' ? 'b' : 'w';
haslose |= (2 == dfs(nw2));
}
}
else
{
haslose |= (2 == dfs(nw));
}
// haswin |= (1 == dfs(nw));
// ws.emplace_back(i);
}
}
ll res = 0;
if (!whites)
{
res = 2; // lose
}
else
{
res = haslose ? 1 : 2;
}
m[now] = res;
return res;
}
signed main()
{
for (ll h = 1; h <= 10; ++h)
{
for (ll i = 0, ie = 1 << h; i < ie; ++i)
{
string s;
s.resize(h);
for (ll j = 0, v = i; j < h; ++j, v >>= 1)
{
s[j] = (v & 1) ? 'w' : 'b';
}
dfs(s);
// cout << s << ' ' << m[s] << ' ' << s.size() << '\n';
}
}
// printf("Done\n");
ll t, n;
cin >> t;
while (t--)
{
string c;
cin >> n >> c;
// cout << c << ':';
// printf("%lld\n", m[c]);
if (m[c] == 2)
{
printf("No\n");
}
else
{
printf("Yes\n");
}
}
return 0;
}
题解用 SG 函数来做,且题解有结论:操作 1 恒让结果值变小(使得可以线性枚举),题解代码:
using namespace std;
const int N=15,mod=998244353;
int n,sg[1<<10];
char s[N];
bitset<105>vis;
int main()
for(int i=1;i<1<<10;i++)
{
vis.reset();
for(int j=0;j<10;j++)
if(i>>j&1)
{
if(j==0) vis[sg[i^(1<<j)]]=true;
else
{
for(int k=0;k<j;k++) vis[sg[i^(1<<j)^(1<<k)]]=true;
}
}
while(vis[sg[i]]) sg[i]++;
}
int t;scanf("%d",&t);
while(t--)
{
sc(n);
sc(s+1);
int st=0;
for(int i=1;i<=n;i++)
if(s[i]=='w') st|=1<<(i-1);
printf(sg[st]?"Yes\n":"No\n");
}
}
可以发现对严格单调递增或递减的一段,为了使得结果不变,首尾不可以删,中间的所有值可删可不删(即乘以
实现上,可以先把相同的一段全部合在一起,化简原数列为一个加权权重,然后找到所有的拐点,两拐点之间都可以删,拐点内部都可以删
赛时 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 100010
ll t, n, a[mn], mod = 998244353, b[mn], m, pw[mn], c[mn], v[mn], lf[mn], rf[mn];
signed main()
{
pw[0] = 1;
for (ll i = 1; i < mn; ++i)
{
pw[i] = pw[i - 1] * 2 % mod;
}
sc(t);
while (t--)
{
sc(n);
ll ans = 1;
for (ll i = 1; i <= n; ++i)
{
sc(a[i]);
v[i] = 0;
}
if (n == 1)
{
printf("1\n");
continue;
}
b[1] = a[1], c[1] = 1, m = 1; //分块
lf[1] = 1;
for (ll i = 2; i <= n; ++i)
{
if (a[i] == a[i - 1])
{
++c[m];
}
else
{
rf[m] = i - 1;
++m;
lf[m] = i;
b[m] = a[i];
c[m] = 1;
}
}
rf[m] = n;
vector<ll> d;
d.emplace_back(1);
for (ll i = 2; i < m; ++i)
{
if ((b[i] < b[i - 1] && b[i] < b[i + 1]) || (b[i] > b[i - 1] && b[i] > b[i + 1]))
{
d.emplace_back(i);
v[i] = 1;
}
}
d.emplace_back(m);
v[1] = v[m] = 1;
for (ll i = 1, ie = d.size(); i < ie; ++i)
{
ll len = lf[d[i]] - rf[d[i - 1]] - 1;
// printf("(%lld)", len);
ans = ans * max(1LL, pw[len]) % mod;
}
// printf("%lld::", ans);
for (ll i = 1; i <= m; ++i)
{
if (v[i] != 1)
{
continue;
}
// printf("%lld(%lld %lld) ", b[i], c[i], pw[c[i]] - 1);
// printf("<%lld %lld>\n", lf[i], rf[i]);
ans = ans * (pw[c[i]] - 1 + mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}
从起点开始拓展到终点,若本来可以走,那么代价是 0 ,新状态可以直接原地放回队首(使得队列必然按代价递增),否则,拓展时按照拓展方向修改当前格,加入队尾,并且保存下一个格子是怎么到达的(可以四方向减法还原路径),并且标记格子已经访问过(因为 BFS 的特性,先访问到的点必然是子图内最优的),最后沿着终点一路逆回去,发现需要代价时输出即可
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1024
ll t, n, m, dp[mn][mn], dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
char c[mn][mn], rh[] = "UDLR";
ll pr[mn][mn], h[mn], vis[mn][mn];
signed main()
{
h['U'] = 0, h['D'] = 1, h['L'] = 2, h['R'] = 3;
sc(t);
while (t--)
{
sc(n), sc(m);
for (ll i = 1; i <= n; ++i)
{
scanf("%s", c[i] + 1);
}
for (ll i = 1; i <= n; ++i)
{
for (ll j = 1; j <= m; ++j)
{
dp[i][j] = 0x7ffffff, vis[i][j] = 0;
}
}
deque<pair<ll, ll>> q;
q.push_front({1, 1});
dp[1][1] = 0;
while (!q.empty())
{
auto t = q.front();
q.pop_front();
ll ax = t.first, ay = t.second;
// printf("%lld %lld %lld %c\n", ax, ay, dp[ax][ay], rh[pr[ax][ay]]);
// if (ax == n && ay == m)
// {
// break;
// } 这个要不要都行,要了因为跑这个if反而更慢了一些些
if (vis[ax][ay])
{
continue;
}
vis[ax][ay] = 1;
for (ll i = 0; i < 4; ++i)
{
ll bx = ax + dx[i], by = ay + dy[i];
if (bx < 1 || bx > n || by < 1 || by > m)
{
continue;
}
ll v = dp[ax][ay] + (rh[i] != c[ax][ay]);
// printf("%lld %lld(%lld %lld) %lld %c %c %lld %lld\n", ax, ay, bx, by, i, rh[i], c[ax][ay], v, dp[bx][by]);
if (v < dp[bx][by])
{
dp[bx][by] = v;
pr[bx][by] = i;
// printf("?%lld %lld\n", dp[bx][by], dp[ax][ay]);
if (dp[bx][by] == dp[ax][ay])
{
// printf("<%lld %lld %c\n", bx, by, rh[i]);
q.push_front({bx, by});
}
else
{
q.push_back({bx, by});
}
}
}
}
printf("%lld\n", dp[n][m]);
for (ll x = n, y = m; !(x == 1 && y == 1);)
{
ll nx = x - dx[pr[x][y]];
ll ny = y - dy[pr[x][y]];
if (dp[nx][ny] != dp[x][y])
{
printf("%lld %lld %c\n", nx, ny, rh[pr[x][y]]);
}
x = nx, y = ny;
}
}
return 0;
}
解法一:字符串哈希+二分+差分
设原串为 a ,反串为 b,枚举 b 的子串左端点,二分右端点,找到与 a 匹配的最长前缀,这一段值都可以作 i ,其 j 就是 b 这个子串起始点,将这一段 a 部分区间用差分加 1
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
ll n, a[mn], b[mn], s[mn];
ull ah[mn], bh[mn], p = 1e9 + 7, pw[mn];
signed main()
{
sc(n);
for (ll i = 1, j = n; i <= n; ++i, --j)
{
sc(a[i]);
b[j] = a[i];
}
pw[0] = 1;
for (ll i = 1; i <= n; ++i)
{
pw[i] = pw[i - 1] * p;
ah[i] = ah[i - 1] * p + a[i];
bh[i] = bh[i - 1] * p + b[i];
}
for (ll i = 1; i <= n; ++i)
{
ll lf = i, rf = n, cf, ans = 0;
while (lf <= rf)
{
ll cf = (lf + rf) >> 1;
ll av = ah[cf - i + 1], bv = bh[cf] - bh[i - 1] * pw[cf - i + 1];
if (av == bv)
{
ans = max(ans, cf);
lf = cf + 1;
}
else
{
rf = cf - 1;
}
}
// printf("<%lld\n", max(0LL, ans - i + 1));
s[0]++;
s[max(0LL, ans - i + 2)]--;
}
for (ll i = 1; i <= n; ++i)
{
s[i] += s[i - 1];
printf("%lld ", s[i]);
}
return 0;
}
解法二:KMP树前缀和
跑 KMP 找以 i 为反串起点的最长相等(需要动手模拟可以理解下面的过程),最长相等的其所有子串对应相等,以 next 数组建树,表现为其可以一路向根走,所以最后叠一个 KMP 树上前缀和即可;难以理解的话画图造例子示意一下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 1000010
ll n, kmp[mn], a[mn], b[mn], ans[mn];
signed main()
{
sc(n);
for (ll i = 1, j = n; i <= n; ++i, --j)
{
sc(a[i]), b[j] = a[i];
}
for (ll i = 2, j = 0; i <= n; ++i)
{
while (j && a[i] != a[j + 1])
{
j = kmp[j];
}
if (a[j + 1] == a[i])
{
++j;
}
kmp[i] = j;
// printf("%lld ", kmp[i]);
}
kmp[0] = -1;
// printf("\n");
for (ll i = 1, j = 0; i <= n; ++i)
{
while (j != -1 && a[j + 1] != b[i])
{
j = kmp[j];
}
++j, ++ans[j];
// printf("%lld ", j);
// a[1->j]和a[i->i-(j-1)]构成回文
}
// printf("\n");
for (ll i = n; i >= 1; --i)
{
ans[kmp[i]] += ans[i];
}
for (ll i = 1; i <= n; ++i)
{
printf("%lld ", ans[i]);
}
return 0;
}
解法一:单调栈+二分
解法二:单调栈+并查集
对原数组叠前缀和,根据贪心,每一块都是前缀和单调不减区域;用严格单调递增栈逆序遍历数组,那么当前下标时单调栈的元素数就是美丽子数组数。每个询问保证按 pair 顺序排序。可以二分单调栈,对每个左端点,找到第一个大于等于 r 的位置,子数组数为左端点单调栈元素数减该位置元素数+1。
参考代码:(用户 yjsp114514
代码)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define M 998244353
int i,j,k,n,m,t,s[5005000],nxt[5005000],it,l[5005000],r[5005000],res[5005000],ql,qr,md,ans,cur;
ll a[5005000];
const int BufferSize=1<<16;
char buffer[BufferSize],*head,*tail;
inline char Getchar() {
if(head==tail) {
int l=fread(buffer,1,BufferSize,stdin);
tail=(head=buffer)+l;
}
return *head++;
}
inline int rd() {
int x=0,f=1;char c=Getchar();
for(;!isdigit(c);c=Getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=Getchar()) x=x*10+c-'0';
return x*f;
}
void print(int x)
{
if(x>9) print(x/10);
putchar(x%10|'0');
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
t=rd();
while(t--){
n=rd();m=rd();
it=0;
for(i=1;i<=n;i++){
a[i]=rd();
a[i]+=a[i-1];
s[++it]=i;
while(it&&a[i]<a[s[it]-1]){
nxt[s[it]]=i+1;it--;
}
//for(j=1;j<=it;j++){cout<<s[j]<<' ';}cout<<endl;
}
while(it){
nxt[s[it]]=n+1;it--;
}
//for(i=1;i<=n;i++){printf("a%d %d\n",i,nxt[i]);}
for(i=1;i<=m;i++){
l[i]=rd();r[i]=rd();
}
cur=n+1;
for(i=m;i>=1;i--){
while(cur>l[i]){
cur--;
while(it&&s[it]<=nxt[cur]){
it--;
}
s[++it]=nxt[cur];
//printf("NMSL%d\n",cur);for(j=1;j<=it;j++){cout<<s[j]<<' ';}cout<<endl;
}
//printf("a%d %d\n",l[i],r[i]);
ql=1;qr=it+1;md=0;ans=11451419;
while(ql<=qr){
md=(ql+qr)/2;
if(s[it+1-md]>r[i]){
ans=min(ans,md);qr=md-1;
}
else{
ql=md+1;
}
}
res[i]=ans;
}
for(i=1;i<=m;i++){
//cout<<res[i]<<'\n';
print(res[i]);putchar('\n');
}
}
}
也可以用并查集,对已经出栈的元素,指向让它出栈的这个元素。每次找到 r-1 的父亲即可。
补题 AC 代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define sc(x) scanf("%lld", &x)
#define mn 5000010
ll t, n, q, a[mn], x, y;
ll lf[mn], rf[mn], fa[mn], s[mn], rk[mn], ans[mn];
ll findf(ll x)
{
while (x != fa[x])
{
x = fa[x] = fa[fa[x]];
}
return x;
}
const int BufferSize = 1 << 16;
char buffer[BufferSize], *head, *tail;
inline char Getchar()
{
if (head == tail)
{
int l = fread(buffer, 1, BufferSize, stdin);
tail = (head = buffer) + l;
}
return *head++;
}
inline int read()
{
int x = 0, f = 1;
char c = Getchar();
for (; !isdigit(c); c = Getchar())
if (c == '-')
f = -1;
for (; isdigit(c); c = Getchar())
x = x * 10 + c - '0';
return x * f;
}
void print(int x)
{
if (x > 9)
print(x / 10);
putchar(x % 10 | '0');
}
signed main()
{
for (t = read(); t; --t)
{
n = read(), q = read();
for (ll i = 1; i <= n; ++i)
{
a[i] = read(), a[i] += a[i - 1];
fa[i] = i;
}
for (ll i = 1; i <= q; ++i)
{
lf[i] = read(), rf[i] = read();
--lf[i], --rf[i];
}
ll top = 0;
for (ll i = n, j = q; i >= 0; --i)
{
while (top && a[s[top]] >= a[i])
{
fa[s[top]] = i;
--top;
}
s[++top] = i;
rk[i] = top;
while (j >= 1 && lf[j] == i)
{
ans[j] = top - rk[findf(rf[j])] + 1;
--j;
}
}
for (ll i = 1; i <= q; ++i)
{
print(ans[i]), putchar('\n');
}
}
return 0;
}