8.28 校内模拟赛 题解报告
扯
T1 SB 结论题 抄错结论直接暴毙
T2 神仙单调队列 根本没想到
T3 神仙数据结构题 一会不会
大概这场考试就是这样...
关于考试过程以及一些题外话
T1 看题 一脸懵逼 题意比较鬼畜 对着模样例的时候看了一眼数据范围 (10^{16}) 显然这是个结论题
T2 看题 只想到了一个 (O(n^2)) 的暴力 看了一下后面的点 好像还有二十分的亚子
T3 看题 三元环计数? 神仙题 惹不起惹不起
回去开 T1
(详细读题)
"嗯? 这还能是小数?"
(反复读题 试图找到输入的两个数是整数的信息 然后没有找到)
"麻了 小数的怎么做.."
然后推了大半张纸 把结论干出来了 想着卡精 忘了除 2 下取整 所以就暴毙了
开 T2
想了一下 好像还是只会四十分的 那二十分看了一下 没怎么想就跑路了
看着数据范围比较二分 试着二分一下
然后开始尝试二分序列... 发现很难确定那个长为 (d) 的区间
然后开始尝试二分值域... 发现答案好像没有单调性...
麻了 不会 弃了弃了
然后死磕 T3 最后也只有十分
不知道当时怎么想的 好像感觉 T3 比较可做的亚子 即使暴力都不会写...
赛后
Ariel: T2 我写了个 (O(n log n)) 的 就是感觉常数有点大
BS: jk (这就 A 穿了..)
最后 Ariel 由于常数过大被卡了...
又是没有题解的一天
对着 std 强行领悟
出题不做题解的人真是差劲
得分情况
50 + 40 + 10 = 100
这结论抄错了还能过五十分是没想到的 当时自己都以为没了的
题解
很显然没有题解
对着 std 的代码自行领悟...
所以可能会有偏差之类的
T1 智商锁(lock)
推出来之后再也不想讲的题 BS 尝试讲了一下发现真的很难讲明白
这里先给出结论
在 (0 leq R leq 1) 时 直接输出 (0)
在 (1 < R leq 2) 时 直接输出 (1)
在 (2 < R leq 3) 时 直接输出 (2)
在 (R > 3) 时 输出 (left lfloor frac {R - L - 2}2 ight floor + 2)
完结撒花(雾
关于得出这个结论的过程就不写了 反正是一点一点卡着边界推出来的 而且有一个不严格的证明 但是 BS 是真的不想写了...
这里提供一个正确的构造方案
首先向左边杯子里面倒 (frac {L + 1}2) 的水 然后向右边杯子里面倒 (frac {L + 3}2) 的水 剩下的水左边倒 (2) 然后右边倒 (2) 直到倒空
显然无论有多少水 这样的构造一定是正确的且是最优的
同时这也是上面结论来源
代码
/*
Source: 智商锁
*/
#include<cstdio>
#include<cstring>
#define int long long
#define pt putchar(' ')
#define pn putchar('
')
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const double ex = 1e-8;
/*----------------------------------------------------------*/
inline void File() {
freopen("lock.in", "r", stdin);
freopen("lock.out", "w", stdout);
}
/*----------------------------------------------------------*/
double L, R;
/*----------------------------------------------------------*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
void Main() {
File();
scanf("%lf%lf", &L, &R);
if((int)(R + ex) <= 1) Print(0);
else if((int)(R + ex) <= 2) Print(1);
else if((int)(R - L + ex) <= 3) Print(2);
else Print((int)((R - L - 2 + ex) / 2) + 2);
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}
T2 LYK 与序列 (sequence)
考场上只想到了 (O(n^2)) 的暴力 完全没想到要用单调队列之类的
首先前缀和数组 (sum) 是一定要维护的 然后再维护一个数组 (a) 表示从每个位置开始向前 (d) 个数的和
双指针维护区间 以单调队列维护区间中最大的 (a) 统计答案
代码
/*
Source:
*/
#include<cstdio>
#include<cstring>
#define int long long
#define pt putchar(' ')
#define pn putchar('
')
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, p, d, a[C], sum[C], q[C], head = 1, tail, ans;
/*----------------------------------------------------------*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
void Main() {
n = read(); p = read(); d = read();
for(int i = 1; i ^ n + 1; ++i) sum[i] = sum[i - 1] + read();
for(int i = 1; i ^ n + 1; ++i) a[i] = sum[i] - sum[Max(0, i - d)];
for(int l = 0, r = 1; r ^ n + 1; ++r)
{
while(head <= tail && a[q[tail]] <= a[r]) --tail;
q[++tail] = r;
while(sum[r] - sum[l] - a[q[head]] > p && l < r)
{
l++;
while(head <= tail && l + d > q[head]) ++head;
}
if(sum[r] - sum[l] - a[q[head]] <= p) ans = Max(ans, r - l);
}
Print(ans);
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}
T3 打架 (fight)
线段树 + 三元环的神仙题
考试的时候推了半个类似结论的东西 然后时间不过随便猜了个结论 然而并不对
首先可以发现数的实际大小对答案显然是没有影响的 无论数的大小如何 都可以通过离散化将其值域映射到 ([1, n]) 上
然后题中要求的三元组是无序的 那么数的顺序也不重要 显然可以排序使其成为一个单调的排列 这样处理会更方便 而且这样就完全可以将每个数映射到下标上 默认左边的数小于右边的数
此时所有的操作一定为一个连续的区间 而改变大小关系的也是一个个连续的区间
将其想象为图的话 改变即为将所有边取反 所以大概可以通过一个比较神奇的做法或者数据结构维护连边情况同时统计答案
这些是在考试的时候能想到的... 然后就不会做了
考完之后对着代码强行领悟 大概懂了
将数值映射到下标 此时默认左边的数小于右边的数 用线段树维护连边情况 线段树中所有的数为 0
在将数映射为排列并作为下标后 所有的操作一定为连续的区间 而改变的大小关系的也是一个个的连续区间
不难发现不相交的区间一定互不影响
那么对于每个点 可以只计算对其造成影响的区间 将这些区间中的大小关系进行取反
具体来说 每到一个点的时候 将以该点作为左端点的区间取反 在处理完一个点之后 将以该点作为右端点的区间取反
将线段树理解为一种状态 表示所有点与当前点的大小关系
为方便处理 不妨设该点左侧所有点以 0 表示小于当前点 该点右侧所有点以 0 表示大于当前点 1 则相反
此处所说的所有大小关系均为下标之间
从容斥的方面考虑 对于一个点 向左统计 0 的个数 向右统计 1 的个数 即从当前点指到的点
显然从这些点中任取两个点与当前点构成的三元环均为不合法的三元环 将其数量从三元环总数中减去即可
具体可以见代码
代码
/*
Source: 打架 (fight)
*/
#include<cstdio>
#include<algorithm>
#define pt putchar(' ')
#define pn putchar('
')
#define int long long
#define mk std::make_pair
#define pr std::pair <int, int>
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, m, c[B], ans;
pr a[B], b[B];
/*----------------------------------------------------------*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
void Print(int x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
namespace Seg {
#define ls(x) x << 1
#define rs(x) x << 1 | 1
#define mid (t[p].l + t[p].r >> 1)
struct node {int l, r, sum, lzy;} t[B << 2];
void build(int p, int l, int r) {
t[p].l = l; t[p].r = r; if(l == r) return ;
build(ls(p), l, mid); build(rs(p), mid + 1, r);
}
void f(int p) {t[p].sum = t[p].r - t[p].l + 1 - t[p].sum; t[p].lzy ^= 1;}
void push_down(int p) {f(ls(p)); f(rs(p)); t[p].lzy = 0;}
void up_date(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {f(p); return ;}
if(t[p].lzy) push_down(p);
if(l <= mid) up_date(ls(p), l, r); if(r > mid) up_date(rs(p), l, r);
t[p].sum = t[ls(p)].sum + t[rs(p)].sum;
}
int query(int p, int l, int r, bool opt) {
if(l > r) return 0;
if(l <= t[p].l && t[p].r <= r) return opt ? t[p].sum : t[p].r - t[p].l + 1 - t[p].sum;
if(t[p].lzy) push_down(p);
if(l <= mid) if(r > mid) return query(ls(p), l, r, opt) + query(rs(p), l, r, opt);
else return query(ls(p), l, r, opt); else return query(rs(p), l, r, opt);
}
}
void Main() {
n = read(); m = read(); Seg::build(1, 1, n); ans = n * (n - 1) * (n - 2) / 6;
for(int i = 1; i ^ n + 1; ++i) c[i] = read();
std::sort(c + 1, c + 1 + n); int tot = std::unique(c + 1, c + 1 + n) - c - 1;
for(int i = 1; i ^ m + 1; ++i)
{
int l = read(), r = read();
l = std::lower_bound(c + 1, c + 1 + tot, l) - c;
r = std::upper_bound(c + 1, c + 1 + tot, r) - c - 1;
a[i] = mk(l, r); b[i] = mk(r, l);
}
std::sort(a + 1, a + 1 + m); std::sort(b + 1, b + 1 + m);
for(int i = 1, j = 1, k = 1; i ^ n + 1; ++i)
{
while(j <= m && a[j].first == i) Seg::up_date(1, a[j].first, a[j].second), ++j;
int x = Seg::query(1, 1, i - 1, 0) + Seg::query(1, i + 1, n, 1);
ans -= x * (x - 1) / 2;
while(k <= m && b[k].first == i) Seg::up_date(1, b[k].second, b[k].first), ++k;
}
Print(ans);
}
/*----------------------------------------------------------*/
signed main() {Main(); return 0;}