一轮集训已经到了半程,感觉一直在做题补题一直在做题补题,想着在摸鱼之余挑个时间来好好总结一下了。太鱼的题我就不拿出来说了,讲点有收获的吧。
CodeForces - 1061D
这题关键就是一个求前驱并修改,一般来说求前驱会用到平衡树,简便用起来就是set就够了。我当时用了一个数组循环模拟做出来的,虽然复杂度n方,但是因为跑不满所以也没被卡死,当然我也证不来真实复杂度,hrdg说是能被卡的,所以还是去学了一下set的写法。(我是真的不会set哭),包括在set里lower_bound都是第一次。
for (int i = 1; i <= n; i++)
{
multiset<long long> :: iterator it = rrr.lower_bound(l[i]); it--;
if ((l[i] - *it) * y >= x) len[i] = 0;
else len[i] = ((l[i] - *it) * y % qhqh - x + qhqh) % qhqh;
if (*it != -inf) rrr.erase(it);
}
CodeForces - 1074D
异或性质挂权值并查集,数据结构专题偷懒了没学,这个时候就得还债了:
int setfind(int x)
{
if (fa[x] == x) return fa[x];
int t = fa[x];
fa[x] = setfind(t);
val[x] ^= val[t];
return fa[x];
}
inline void setunion(int x, int y, int value)
{
if (cnt[x] == 0) {fa[x] = x; cnt[x]++;}
if (cnt[y] == 0) {fa[y] = y; cnt[y]++;}
int fx = setfind(x), fy = setfind(y);
if (fx != fy)
{
fa[fx] = fy;
val[fx] = val[x] ^ val[y] ^ value;
}
}
inline bool setcheck(int x, int y) {return setfind(x) == setfind(y);}
ZOJ - 4008
题意:给一颗树,q个询问,每个询问求节点标号[L,R]之间的节点组成的连通块的个数。进一步分析可以得到,求区间[L,R]之间包含的边的个数的题意。
先从小到大给线段询问排序,树状数组更新,先给所有R的前缀都标记加一,循环左端点,每次到了有左端点的地方先解决询问在撤销左端点前缀的个数,以便不影响后面的询问。
ZOJ - 4006
就是一个找规律推公式然后用点数学知识求逆元,这个时候我就发现自己找规律啥的太弱了,完全做不出来(一度以为是dp……
ZOJ-4009
高中爷介绍,这是一个套路题,以后一旦见到有立方和还有模数为5位啥的,答案都是有规律的,他就打个表,然后发现了公共循环节是48,然后就是一颗线段树就完事的。只能说有经验就是牛逼。至于写法需要设计线段树,其实也不难写,想了想不贴这了。
Gym - 101341A
处理这种括号配对问题可以首先先把能搭配的括号处理了,然后这题分两种情况出两个数组两种排序规则然后搭配,话说刘大爷那种巧妙的写法我直到想现在都还不太懂……
for (int i = 1; i <= n; i++)
{
scanf("%s", s + 1);
int len = strlen(s + 1), l = 0, r = 0;
for (int j = 1; j <= len; j++)
{
if (s[j] == '(') r++;
else r--;
if (r < l) l = r;
}
l = -l;
if (r >= 0) Left[++cnt1] = {l, r, i};
else Right[++cnt2] = {l + r, r, i};
}
CodeForces-1076E
这题我觉得真的很棒,利用了递归的性质,在递归中直接以深度为下标,递归前用线段树或者树状数组修改,出递归回溯时撤销。
HDU-6282
找规律,没找到,我傻了,以c为边界,找c之间的a和b的奇偶性就可以出。
CodeForces - 1042D
首先推出公式这题是求有多少个R,满足sum[R] < t + sum[L - 1],推公式是关键点,因为没有公式根本做不了,但是想到这个公式有解决的工具吗。
还是用到线段树或树状数组,首先离散化先全部处理 sum[ i ] 和 sum[ i ] + t 的坐标,先更新0 + t 的坐标,前缀全部加一,然后循环枚举一个右端点的坐标,看当前坐标上带有的权值就可以知道有多少对左端点 + t 已经满足要求,然后把这个右端点 + t 后更新。
听说与求逆序对的思路差不多,后来想想是这样的。是个不难的好题
CodeForces-478D
dp好题,反正我是没推出来,考场上推dp好费劲啊呜呜呜,不过事后来看转移关系还是明显的,至于滚动数组啥的都是优化,关键还是得把转移方程写出来。
CodeForces-440C
搜索题,容易漏情况,讲的是由带1的数字来构成另一个数字,除了可以累加还可以由一个大的1111来减。
CodeForces-510D
还是dp,是有运用数学知识的dp,一堆数里找几个让它配成对使得全gcd为1,这里是map的使用教学时间:
dp[0] = 0;
map<long long, long long> :: iterator it;
for (int i = 1; i <= n; i++)
for (it = dp.begin(); it != dp.end(); it++)
{
long long t = it->first;
long long g = gcd(a[i], t);
if (dp.find(g) != dp.end())
dp[g] = min(dp[g], it->second + c[i]);
else
dp[g] = it->second + c[i];
}
Gym - 101806Q
很不错的回文串处理题,对于新加入的节点维护它跟前一个节点为末位构成的回文串的个数,然后自己喜加一。
HDU - 6333
这题的知识点就多了,线性递推求逆元,然后是阶乘逆元来处理二项式。
然后推出不同左右端点的二项式递推关系,就可以莫队来转移
然后离线后莫队来做,注意莫队做法的左右端点移动的先后顺序,真会影响的我卡了一晚上。
CodeForces-485D
很nice的思路转换,要求取模的最大值,那就从小到大开始枚举,枚举较小数的倍数与其lower_bound()位置的前一个数的取模就是一个答案,更新出循环后的最大值就行
SPOJ-GSS5
怎么说呢,线段树教学,求线段树的前缀,后缀,区间最值。整理下来也能写的很好看:
#define ls rt << 1
#define rs rt << 1 | 1
struct node {int sum, pre, suf, maxsum;} tr[maxn << 4], None = {0, -inf, -inf, -inf};
node operator + (node a, node b){
return{
a.sum + b.sum,
max (a.pre, a.sum + b.pre),
max (a.suf + b.sum, b.suf),
max (a.suf + b.pre, max (a.maxsum, b.maxsum))
};
}
void build (int rt, int l, int r)
{
if (l == r) {tr[rt] = {a[l], a[l], a[l], a[l]}; return;}
int mid = (l + r) >> 1;
build (ls, l, mid);
build (rs, mid + 1, r);
tr[rt] = tr[ls] + tr[rs];
}
node query (int nl, int nr, int l = 1, int r = n, int rt = 1)
{
if (nl > r || nr < l) return None;
if (nl <= l && nr >= r) return tr[rt];
int mid = (l + r) >> 1;
node ret = query(nl, nr, l, mid, ls) + query(nl, nr, mid + 1, r, rs);
return ret;
}
ZOJ - 4117
好题,问题变成了两个最近的相同的值间有多少不同的值,那么怎么处理呢?
答案是树状数组,在第一个值打标记后缀更新,然后遇到相同的就可以统计了,统计完之后在后面的位置更新,前面的位置撤销使得消除了当前区间内的影响同时保存下一个区间不受改变。
HDU-6278
主席树练手题,当然我根本想不起来用,做完签到题也不知道做啥了,这还是后来才补上的。
int root[maxn];
struct hjt_tree {int ls, rs, siz;} tr[maxn * 40];
int node_cnt;
void build (int l, int r, int &rt)
{
rt = ++node_cnt;
tr[rt].siz = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(l, mid, tr[rt].ls);
build(mid + 1, r, tr[rt].rs);
}
void update (int l, int r, int pre, int &rt, int pos)
{
rt = ++node_cnt;
tr[rt] = tr[pre]; tr[rt].siz++;
if (l == r) return;
int mid = (l + r) >> 1;
if (mid >= pos) update(l, mid, tr[pre].ls, tr[rt].ls, pos);
else update(mid + 1, r, tr[pre].rs, tr[rt].rs, pos);
}
int query (int l, int r, int pre, int rt, int k)
{
if (l == r) return l;
int siz = tr[tr[rt].ls].siz - tr[tr[pre].ls].siz;
int mid = (l + r) >> 1;
if (k <= siz) return query(l, mid, tr[pre].ls, tr[rt].ls, k);
else return query(mid + 1, r, tr[pre].rs, tr[rt].rs, k - siz);
}
Gym-101755K
这个题没什么,就是放上来提醒一下别忘了二分操作,本来一道贼水的题被我做成什么样wsl。
Gym-101234A
这道题的二分和线段树操作才是亮眼。
题意是给你一堆区间,然后有递增递减两种排序规则,经过一堆排序后最后的数列中中间那个是多少。因为每个数字都可能成为中点,于是枚举数字x,比x大的数标记为1,小于等于标记为0,然后用线段树模拟维护排序,最后看中点是1还是0,(即比较与x的大小关系),然后根据大小关系移动x。这里的大小关系是具有单调性的,比如我们直到中点为1,即中点比选的x大,所以所有比x小的数都不能选,套在二分答案的操作里此时就应该移动端点换mid枚举了。
AtCoder-2582
这题就是一个推公式然后爆搜,剪枝过后时间复杂度很优秀。很棒的一题。
这样下来算是整理了一些东西了,不过暂时只有白天队内赛里的题目,晚上场的codeforces里的题目没放上来,再说吧