关于最终检查
时机:
可以写一道查一道,但离考试结束还有10-20分钟时,必须统一再检查一遍。
流程:
- 文件名
- 源程序文件名
- 输入输出文件名
freopen
有没有被注释掉
- 全局变量
- 对数组,检查大小有没有开错
- 对所有类型的变量,考虑是否需要初始化
- 计数器(包括用于计数的数据结构)
- 邻接表——
h[]
置为0
- 手写栈或队列——
h = t = 0
- STL——
.clear()
【高新联训day1】数 [2017.12.19]
翻车现场
“若答案小于等于9位,输出答案,否则输出最后9位。”
人生经验
这还是头一次遇到“末9位”这种坑,以后就记住了。
不是把MOD
取为1e9就行了。输出时要判断答案满不满9位。
【高新联训day1】帮会 [2017.12.19]
直接操作lct,在access
切换轻重边的同时用dfs序+树状数组记录答案。
翻车现场
void access(Node *x) {
for (Node *y = NULL; x; x = (y = x)->f) {
splay(x);
Node* &side = x->ch[1];
if (side) {
side->isrt = true;
side->inc();
}
if (side = y) {
y->isrt = false;
y->dec();
}
}
}
事故分析
在原树中,y
不是x
的儿子,y
的链顶(深度最浅)才是,side
同理。因此应对y
和side
的链顶进行操作,而不是操作y
和side
。
Node* find(Node *x) {
while (x->ch[0]) x = x->ch[0];
return x;
}
void access(Node *x) {
for (Node *y = NULL; x; x = (y = x)->f) {
splay(x);
Node* &side = x->ch[1];
if (side) {
side->isrt = true;
find(side)->inc(); //find left
}
if (side = y) {
y->isrt = false;
find(y)->dec(); //find left
}
}
}
人生经验
splay的顶不是链顶
【高新联训day2】通讯系统 [2017.12.20]
需要在每个点维护一个odt,在插入/合并区间的同时维护答案。按重链剖分,每个点从其重儿子继承,其余暴力插入(合并),可以保证复杂度O(nlog2n)。
翻车现场
我的实现是把对set
和ans
的操作封装起来,到叶子就new
一个节点,每个点return
一个指针,父节点从重儿子直接获得指针,从轻儿子得到的指针在合并后delete
掉。这样构造函数即初始化,也不用考虑内存管理。
struct inter { int l, r; }
typedef std::set<inter> Set;
typedef Set::iterator It;
struct Answer {
Set s;
ll ans;
Answer(): ans(0) { //initialization
s.insert(inter(1,1));
s.insert(inter(n+1,n+1));
}
void insert(int l, int r) {
if (l >= r) return;
inter v(l,r);
//ignoring correct unrelated code
//......
//operate s & maintain ans
s.insert(v);
}
Answer(int l, int r) {
Answer();
insert(l,r);
}
};
事故分析
结果WA完了。
调了半天才发现,子节点根本没有保护节点,是因为Answer(l,r)
调用Answer()
是在自己之外构造了一个结构体,然后把它遗弃了,再对自己调用insert(l,r)
。。。
【高新联训day4】尘封的花环 [2017.12.22]
一道循环同构计数,burnside引理裸题,然而我不会Polard-rho分解,于是只能O(n√)分解。
然而部分分也没拿完,有2个地方取模爆long long
了。
翻车现场Ⅰ
inv(n)
事故分析Ⅰ
求逆元是通过快速幂算的。其中肯定有x = x * x % MOD
,而n是MOD
:
inv(n % MOD)
翻车现场Ⅱ
for (ll i = 1; i*i <= n; i++)
if (!(n % i)) {
ll j = n / i;
ans = (ans + phi(i) * f(j) )%MOD;
if (i < j) ans = (ans + phi(j) * f(i) )%MOD;
}
事故分析Ⅱ
phi(n)
的计算过程使你绝对不会担心爆long long
,因为自始至终不会超过n
。
但是phi(n)
的返回值是O(n)级别的,再与其它数一乘就爆long long
了。因此return
时必须取个膜。
人生经验
当n的范围超过long long
。尽量多取模。
但也不要取模过度:指数中取MOD-1
作模。
A [2017.12.9]
题意:k个工人刷一面长为
翻车现场
struct worker {
int L, P, S;
void work() {
int cur = -INF;
for (int i = std::min(S+L-1, N); i >= S; i--) {
if (i >= L) Max(cur, dp[i-L] - (i-L)*P);
Max(dp[i], cur + i*P);
}
}
} w[110];
状态转移有:
dp[S+i] <- dp[S-L+i..S-1]
(0≤i<L)
显然这是从dp[S-1]
往前数长度为L−i的一段。所以i反向枚举,记录当前最优解,就可以
事故分析
- 这样的转移不支持跳墙(中间空一段不刷),即少了一种转移
Max(dp[i+1], dp[i]);
std::min(S+L-1, N)
那里会出大事。即使dp[S+L-1]
是越界的,也不妨碍某dp[i]
从dp[S-1]
那里转移来。因此,这时也至少得枚举i
并把dp[i-L]
计入cur
中。
改过的代码这样写:
struct worker {
int L, P, S;
void work(int R) {
int cur = -INF;
for (int i = S+L-1; i >= S; i--) { //不管i越不越界
if (i >= L) Max(cur, dp[i-L] - (i-L)*P);
if (i <= N) Max(dp[i], cur + i*P); //在这里检查越界
}
for (int i = S; i <= R; i++) Max(dp[i+1], dp[i]); //R是下一个工人坐的位置
}
} w[110];
人生经验
老老实实写对拍去吧
B [2017.12.9]
翻车现场
我曾以为dp[i] = min{ dp[j] + (a[i] - a[j+1])^2 }
是决策单调+凸函数。于是写了双指针 instead of 斜率优化。
I
was
too naive
人生经验
证明决策单调性请打表观察!
证明凸函数请打表观察!
【NOIP赛前集训Day4】reverse [2017.11.2]
一道字符串水题,n2随便过。于是我把签到题写挂了
翻车现场
inline void operate(char *s, int &l, int &r, bool &p) {
if(p) p ^= s[l++] ^ 'A';
else p ^= s[--r] ^ 'A';
}
实现操作:读掉字符串最后一个字符。如果是B就把字符串翻转,如果是A就不翻转。
函数中用p记录现在A是不是反的。如果s[i] ^ 'A'
即s[i] != 'A'
就把p取反。
事故分析
用^
本意在于位运算比比较运算符快一点。这次居然翻车了。。。
^
算出的结果不是一个单纯的bool
型,同时^=
操作也不是为bool
设计的,所以在执行^=
时并没有先转换为bool
,就会出锅。实际运行结果是p一但变成true
就变不回去了。
改成这样就A了。。。
inline void operate(char *s, int &l, int &r, bool &p) {
if(p) p ^= s[l++] == 'B';
else p ^= s[--r] == 'B';
}
人生经验
除非是放在一个按语法直接需要bool
表达式的位置(比如while
里面),最好不要用^
代替!=
。
另外,指针之间不可以^
,只能!=
(编译器会报错)
【NOIP赛前集训Day6】kill [2017.11.4]
题意:把m个怪分给n个人打。二分答案。
翻车现场
bool check(long long ans) {
int h = 0, t = 0;
for (int i = 0; i < n; i++) {
ll l = (ll)x[i] + s - ans + 1 >> 1,
r = (ll)x[i] + s + ans >> 1;
while (t < m && y[t] <= r) t++;
while (h <= m && y[++h] < l);
if (h > t) return false;
}
return true;
}
实现功能:根据二分的答案ans
算出n个区间,如果可以把y[i]
分配给区间,使所有区间内都有一个不重复的y对应,则返回true
。
由于区间有单调性,类似sliding window跑即可。每次先框出合法区间,然后选取队首匹配并pop掉。
事故分析
l,r
什么的都没有出锅。
while() t++;
也没问题,把小于等于r的都入队。
while() h++;
可能比较confusing。因为把去除不合法点与匹配队首两步写在一起了(巧妙的常数优化)。
- while 只要不越界
- 二话不说先弹一个队首
- 如果刚才弹的队首是一个合法队首,那就弹对了,退出。否则回到判断是否越界开始循环
写到这里应该可以看出来了。应该写y[h++]
而不是y[++h]
,这样才是检查“刚才”弹掉的队首。
当时就是想要优化,结果把自己绕晕了。。。
人生经验
再要写这样的优化,可以先像上面这样把流程和逻辑理清楚再写。
【NOIP赛前集训Day6】weight [2017.11.4]
正解是最小生成树上倍增查询与倍增维护离线修改。
我写成了树链剖分,挂一个RMQ和一个zkw线段树,本来是可以过的(其实出题人原来就是这么做的…),但写出了点小锅。
翻车现场
int tr[200000];
事故分析
额,就是zkw线段树数组开小了。。。
这道题里n≤70000,记得zwk说他的线段树开两倍空间就可以了,比普通线段树效率高,于是我就开了2000000
然而事实是应该开N+n,其中N=2k≥n,于是k≤17,N≤131072,所以开到201100就过了。。(为什么就差了1072啊T_T
)
【NOIP赛前集训Day7】set [2017.11.6]
题面里有一句“请使用更快的读入方式,以免超时”,于是我默默地去写fread读优了。三道题写完回来检查时发现,其实也就2e6个int,scanf
没问题,于是为了避免读优写挂,就把读优删了。然而我的输入输出文件是在读优里开的,于是就以前删掉了。。。
于是签到题就这么挂了
不说,找墙去了
【NOIP赛前集训Day7】read [2017.11.6]
1s看出是卡空间求出现多于一半的众数。众数都求对了。然后一高兴就把求答案的公式推错了。。。
【NOIP赛前集训Day9】如烟(a) [2017.11.8]
正解可以做到O(nm32),不过数据水,给的n2的范围,所以我O(16∗n364)的bitset本来也是可以过的,结果写挂了。。。
翻车现场
ull set[3030][47];
...
set[x][x>>6] |= 1 << (x & 0x3f);
事故分析
这个bug很隐蔽,但也很简单。
set[x][x>>6] |= 1ULL << (x & 0x3f);
把1换成1ULL就对了
人生经验
写下位移运算符<<
,>>
的时候要想3件事:
1. 负数?
2. ULL?
3. 优先级
【NOIP赛前集训Day9】星空(b) [2017.11.8]
能相信我犯了这样的错吗???
翻车现场
int x[500050], y[50050];
不用再解释了,我或许该去砸键盘