错题集

关于最终检查

时机:

可以写一道查一道,但离考试结束还有10-20分钟时,必须统一再检查一遍。

流程:
  1. 文件名
    • 源程序文件名
    • 输入输出文件名
    • freopen有没有被注释掉
  2. 全局变量
    • 对数组,检查大小有没有开错
    • 对所有类型的变量,考虑是否需要初始化
      • 计数器(包括用于计数的数据结构)
      • 邻接表——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同理。因此应对yside的链顶进行操作,而不是操作yside

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)

翻车现场

我的实现是把对setans的操作封装起来,到叶子就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 1018,一进去就爆了。 应在外面先膜一个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 的范围超过109时要特别当心,避免 n n相乘爆long long。尽量多取模。
但也不要取模过度:指数中取MOD-1作模。

A [2017.12.9]

题意: k 个工人刷一面长为n的墙。第 i 个人在Si,最多刷长度 Li ,单价 Pi 。每个人要么不刷,要么刷包含 Si 的连续的一段。求总价最大。

翻车现场

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]( 0i<L )
显然这是从dp[S-1]往前数长度为 Li 的一段。所以 i 反向枚举,记录当前最优解,就可以O(1)转移。

事故分析

  1. 这样的转移不支持跳墙(中间空一段不刷),即少了一种转移 Max(dp[i+1], dp[i]);
  2. 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线段树数组开小了。。。
这道题里 n70000 ,记得zwk说他的线段树开两倍空间就可以了,比普通线段树效率高,于是我就开了2000000
然而事实是应该开 N+n ,其中 N=2kn ,于是 k17,N131072 ,所以开到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(16n364) 的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];

不用再解释了,我或许该去砸键盘

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值