NOIP2018提高组省一冲奖班模测训练(四)

这次比赛只AC了第一题,而且花了40多分钟,貌似是A掉第一题里面最晚的

而且还有一个半小时我就放弃了……

下次即使想不出也要坚持到最后

第二题没思路

第三题想用分块搞一搞,然后发现空间开不下(其实可以用分块搞)

主要是认识了今天AK的曲恒毅大佬,收获很大

砍树

小D有一棵树,这棵树有n个节点,n-1条边,保证连通。树上每个点要么被染成黑色,要么是白色。他定义一棵树的奇怪值为:这棵树中,白色节点与黑色节点数量的差的绝对值。

比如这个例子中,树上有5个白节点,3个黑节点,奇怪值就是|3-5|=2。

小D想让你在原树中砍出来一个连通块,使得这个连通块的奇怪值最大。(注意连通块当然也会是棵树。)

Input
首先输入n。
接下来一行n个数c_1,c_2,…,c_n 。若c_i=0,表示节点i颜色为白色,否则为黑色。
接下来n-1行,每行两个数u,v,描述树上一条连接节点u,v的边。
对于20%的数据,n≤20
对于40%的数据,n≤100
对于60%的数据,n≤1000
对于100%的数据,n≤10^5,1≤x,y≤n,c_i∈{0,1}
Output
输出一行,表示最大的奇怪值。
Input示例
8
1 0 0 1 1 0 0 0
7 1
3 5
1 6
4 3
6 3
2 3
7 8
Output示例
4

看完第一反应树形dp
但是感觉这个绝对值不好处理,同时不知道怎么求这个连通块
然后绕了半天突然想到可以绝对值可以分开处理
求连通块的话更新的时候贪心一下,只加入dp值>0的子树,最后枚举哪个子树为根统计一下答案就好了
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e5 + ;
struct Edge{ int to, next; };
Edge e[MAXN << ];
int head[MAXN], tot;
int c[MAXN], d[MAXN], dp[MAXN], n; void AddEdge(int from, int to)
{
e[tot] = Edge{to, head[from]};
head[from] = tot++;
} void dfs(int u, int fa)
{
dp[u] = d[u];
for(int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if(v == fa) continue;
dfs(v, u);
if(dp[v] > ) dp[u] += dp[v];
}
} int main()
{
memset(head, -, sizeof(head)); tot = ;
scanf("%d", &n);
_for(i, , n) scanf("%d", &c[i]);
REP(i, , n)
{
int u, v;
scanf("%d%d", &u, &v);
AddEdge(u, v); AddEdge(v, u);
} int ans = ;
_for(i, , n) d[i] = c[i] ? : -;
dfs(, -);
_for(i, , n) ans = max(ans, dp[i]); _for(i, , n) d[i] = c[i] ? - : ;
dfs(, -);
_for(i, , n) ans = max(ans, dp[i]); printf("%d\n", ans); return ;
}
奇怪的回文串
小D对字符串有着奇怪的认识。对于一个字符串S,他认为S是满足“奇数-回文”性质,当且仅当这个串的所有长度为奇数的子串都是回文串。注意一个串的子串指的是串中任意连续位置形成的串,一个“奇数-回文”串本身并不需要长度为奇数。
现在小D有一个长度为N的字符串S。他有K次修改这个字符串的机会。每次修改,他可以选择字符串上的任意一个位置,把这个位置修改成任意一种字符。他希望使得修改后的串中满足“奇数-回文”性质的子串的长度最大。注意K次机会不必用完。

Input
第一行包含两个整数K,N。
接下来一行N个整数,第i个整数代表字符串S第i位的字符。
对于20%的数据,N≤10
对于40%的数据,N≤100
对于60%的数据,N≤5000
对于100%的数据,1≤K≤N≤500000,保证给定的字符串的字符为1到10^9间的整数。
Output
输出一个整数,表示最大可能的满足“奇数-回文”性质的子串长度。
Input示例
1 6
1 2 3 4 5 6
Output示例
3

这道题没怎么深入思考
首先要看出一个结论
如果所有奇数子串都是回文串,等价于所有长度为3的字串都为回文串 所有长度为3的字串都为回文串等价于奇数位置字符同,偶数位置字符同
这个结论画画图就可以发现
那么我们显然可以枚举子串,然后分别算出偶数位置和奇数位置同一个字符最多有多少个
如果分别由w0, w1个
那么就需要修改
序列长度-w0-w1次
那么问题就怎么高效地维护这个最大值
我们可以用一个滑动窗口来做
固定左端点,每次右端点在次数小于k的情况下尽量拓展,用序列长度更新答案
然后左端点加加,重复上个过程
在其中写两个支持删除操作的堆,维护奇数位置和偶数位置中同一个字符最多有多少个
支持删除操作的堆可以用堆优化dijsktra算法中的堆的思路 右端点共右移n次,左端点同样,所以滑动窗口的复杂度是O(n)的
然后每次操作维护堆是logn的
总复杂度是O(nlogn)的
然后我惊奇的发现用unorderer_map和手写离散化跑出来的时间基本相同。
那以后就可以用unordered_map离散化了,不需要手写了
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 5e5 + ;
int a[MAXN], n, k; unordered_map<int, int> cnt[];
struct node
{
int id, w;
bool operator < (const node& rhs) const
{
return w < rhs.w;
}
};
priority_queue<node> q[]; int main()
{
scanf("%d%d", &k, &n);
_for(i, , n) scanf("%d", &a[i]); int l = , r = , ans = ;
_for(i, , n)
{ while(r <= n)
{
if(r & ) q[].push(node{a[r], ++cnt[][a[r++]]});
else q[].push(node{a[r], ++cnt[][a[r++]]}); node t1, t0;
while(!q[].empty())
{
t1 = q[].top();
if(cnt[][t1.id] == t1.w) break;
q[].pop();
}
while(!q[].empty())
{
t0 = q[].top();
if(cnt[][t0.id] == t0.w) break;
q[].pop();
} if(r - l - t1.w - t0.w > k) break;
ans = max(ans, r - l);
} if(l & ) q[].push(node{a[l], --cnt[][a[l++]]});
else q[].push(node{a[l], --cnt[][a[l++]]});
} printf("%d\n", ans); return ;
}

范围查询
小D得到了一个数组A=[a_0,a_1,…,a_(n-1) ]。他对这个数组A进行了q个查询,每个查询都会给定四个数left,right,x,y,你需要求出在数组A中,有多少位置i满足下列条件:
1.left≤i≤right
2.a_i≡y(mod x)
其中a_i≡y(mod x)表示,a_i对x取模的值为y。

Input
首先输入两个数n,q。
接下来一行n个数a_0,a_1,…,a_(n-1) 。
接下来q行,每行四个数left,right,x,y,描述一个询问。
总共有10个数据点。
对于第1,2个数据点,n,q≤1000
对于第3,4,5,6个数据点,x≤1000
对于100%的数据,1≤n,q≤40000,0≤a_i≤40000,1≤x≤40000,0≤y<x,0≤left≤right<n。
Output
输出q行,表示每个询问对应的答案。
Input示例
5 3
250 501 5000 5 4
0 4 5 0
0 4 10 0
0 4 3 2
Output示例
3
2
2

这道题曲恒毅大佬用莫队跑过去了,他说因为数据比较水
我学习了一下他的写法,自己写了一遍,练习一波莫队
这道题是问l到r中符合a_i≡y(mod x)的有多少个
直观的想法是枚举每一个a_i看满不满足
但我们可以逆向思维
枚举所有 mod x等于y的数,看这些数在l到r中出现了几次。
所以可以用莫队维护一个桶
不过这个算法显然可以卡掉,当很多x非常小而最大x非常大的时候就要遍历很多次
但是这题的数据是随机的比较水。
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 4e4 + ;
int bl[MAXN], ans[MAXN], a[MAXN];
int f[MAXN], n, m, blo, maxt;
struct node
{
int l, r, x, y, id;
bool operator < (const node& rhs) const
{
return bl[l] < bl[rhs.l] || bl[l] == bl[rhs.l] && r < rhs.r;
}
}q[MAXN]; int main()
{
scanf("%d%d", &n, &m);
blo = sqrt(n); _for(i, , n)
{
scanf("%d", &a[i]);
maxt = max(maxt, a[i]);
bl[i] = i / blo;
} _for(i, , m)
{
scanf("%d%d%d%d", &q[i].l, &q[i].r, &q[i].x, &q[i].y);
q[i].l++; q[i].r++;
q[i].id = i;
} sort(q + , q + m + ); int l = , r = ;
_for(i, , m)
{
while(r < q[i].r) ++f[a[++r]];
while(l > q[i].l) ++f[a[--l]];
while(r > q[i].r) --f[a[r--]];
while(l < q[i].l) --f[a[l++]];
for(int j = q[i].y; j <= maxt; j += q[i].x)
ans[q[i].id] += f[j];
} _for(i, , m) printf("%d\n", ans[i]); return ;
}

讲一下正解,写了好久好久才AC,而且发现最慢的点984ms,还不如写莫队时间快

顺便吐糟一下数据有问题,竟然有x=0的,题目中给的是x>0的,我交上去RE,调试了好久才发现是数据本身的问题

正解包含了x<=1000的解法, 所以先讲x比较小的情况

对于x<=1000的时候,而询问有40000,这意味着有很多很多的x是重复的,平均下来重复40个x

那么这种情况下就启发我们可以把x相同的一起处理

那么我们就按照x排序,把x相同的一起处理

对于每个询问,我们可以打标机,在l-1处打上id, y, -1的标记,在r处打上id, y, 1的标记, id是这个询问在题目输入中是第几个,方便统计答案

有什么用呢?

打完标机后我们可以从1到n扫一遍数组,同时用一个cnt记录每个数模x等于y中y有多少次

那么遇到标记就可以使 ans[id] += cnt[y] * p;

这个p有什么用?对于区间[l, r], 可以转化为[1,r] - [1, l - 1]

这个p就是维护这个正负的

同时注意一个位置可能有很多个标记,所以要用一个vector存

复杂度的话,有xmax组(xmax表示最大的x),每组要扫一遍数组O(n)

所以复杂度是O(nxmax),和最大的x有关。

40000*1000其实很极限了,所以有一个点是9秒多卡过去的

如果最大的x比较小,就用上述方法。然后我们考虑x比较大的情况

这时候的解法和莫队的写法有一点点相似之处

莫队的写法使用与x比较大的情况,所以去枚举所有modx=y的数不会太慢

而这道题也因为x比较大,所以也去枚举modx=y的数

那么对于枚举出的每一个数,怎么迅速这个数在[l,r]中存在多少次?

不知道大家有没有做过区间众数那道题,那道题也存在上一行的问题

我们可以把数组中每一数,以值作为下标,位置作为值扔到一个vector中

比如对于5的数,在3 4 6 7这4个位置中出现过

那么如果询问在位置3到5中存在几个5

那么首先可以二分出左边界,也就是找>=3的第一个数是的位置

因为在3 4 6 7这4个位置中出现过,所以这个数显然是3,位置是1(3 4 6 7的下标从1开始,从0开始也可以,反正求的是差值)

然后可以二分出右边界+1,也就是找出大于(没有等于)5的第一个数是多少

因为在3 4 6 7这4个位置中出现过,这个数显然是6,位置是3

那么答案就是右边界+1-左边界,也就是3 - 1 = 2

我们验证一下,在3 4 6 7中3到5有3和4两个地方

所以答案是对的

可能看一遍看不懂,多看几遍,注意哪个是下标,哪个是值

如果设最大的数组中的值为M

那么这个复杂度是O(Q * M / xmin * logn)

那么现在存在一个问题,到底以x为多少分界线是最优的,这个时候我们可以用类似算分块大小中每一块多大的的方法来算

总复杂度是O(Q * M / xmin * logn +n*xmax)

这里的xmax和xmin就是我们要求的分界值,设为L

那么O(Q * M / L * logn +n*L)

这里Q和n都是小于等于40000,是同阶的,可以都看作Q

那么Q提出来

O( Q(M / L * logn +L))

那么由均值不等式知

当M / L * logn =L时,它们相乘最小

可得L = sqrt(M * logn)

最后提一下我代码中用了unordered_map

如果本地没有加c++11的编译命令的话会CE(怎么加自行百度)

提交代码的时候也要注意选C++11, 选C++会CE

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 4e4 + ;
struct node
{
int l, r, x, y, id;
bool operator < (const node& rhs) const
{
return x < rhs.x;
}
}q1[MAXN], q2[MAXN];
int a[MAXN], cnt1, cnt2;
int M, n, m, L, ans[MAXN]; struct flag
{
int id, y, p;
};
vector<flag> t[MAXN];
unordered_map<int, int> cnt; void solve1()
{
sort(q1 + , q1 + cnt1 + );
int i = ;
while(i <= cnt1)
{
cnt.clear();
int j = i - ;
while(j + <= cnt1 && q1[j+].x == q1[i].x)
{
j++;
t[q1[j].l - ].push_back(flag{q1[j].id, q1[j].y, -});
t[q1[j].r].push_back(flag{q1[j].id, q1[j].y, });
} _for(k, , n)
{
cnt[a[k] % q1[i].x]++;
if(t[k].size())
{
REP(r, , t[k].size())
ans[t[k][r].id] += cnt[t[k][r].y] * t[k][r].p;
t[k].clear();
}
}
i = j + ;
}
} vector<int> g[MAXN]; void solve2()
{
_for(i, , n) g[a[i]].push_back(i);
_for(i, , M) sort(g[i].begin(), g[i].end());
_for(i, , cnt2)
for(int j = q2[i].y; j <= M; j += q2[i].x)
{
int L = lower_bound(g[j].begin(), g[j].end(), q2[i].l) - g[j].begin();
int R = upper_bound(g[j].begin(), g[j].end(), q2[i].r) - g[j].begin();
ans[q2[i].id] += R - L;
}
} int main()
{
scanf("%d%d", &n, &m);
_for(i, , n)
{
scanf("%d", &a[i]);
M = max(M, a[i]);
} L = sqrt(M * log2(n)); _for(i, , m)
{
node t;
scanf("%d%d%d%d", &t.l, &t.r, &t.x, &t.y); t.l++, t.r++;
if(t.x == ) continue; //好坑!!
if(t.x < L) q1[++cnt1] = t, q1[cnt1].id = i;
else q2[++cnt2] = t, q2[cnt2].id = i;
} solve1();
solve2();
_for(i, , m) printf("%d\n", ans[i]); return ;
}

总结

(1)认识大佬

(2)字符串观察结论,写带删除的堆

(3)位置二分,逆向思维,莫队,合并处理

NOIP2018提高组省一冲奖班模测训练(四)的更多相关文章

  1. NOIP2018提高组省一冲奖班模测训练(六)

    NOIP2018提高组省一冲奖班模测训练(六) https://www.51nod.com/Contest/ContestDescription.html#!#contestId=80 20分钟AC掉 ...

  2. NOIP2018提高组省一冲奖班模测训练(五)

    NOIP2018提高组省一冲奖班模测训练(五) http://www.51nod.com/Contest/ContestDescription.html#!#contestId=79 今天有点浪…… ...

  3. NOIP2018提高组省一冲奖班模测训练(三)

    NOIP2018提高组省一冲奖班模测训练(三) 自己按照noip的方式考,只在最后一两分钟交了一次 第一题过了,对拍拍到尾. 第二题不会.考试时往组合计数的方向想,推公式,推了一个多小时,大脑爆炸,还 ...

  4. NOIP2018提高组省一冲奖班模测训练(二)

    比赛链接 NOIP2018提高组省一冲奖班模测训练(二) 今天发挥正常,昨天不在状态…… 花了很久A了第一题 第二题打了30分暴力 第三题投机取巧输出test1答案(连暴力都不知道怎么打,太弱了) 2 ...

  5. NOIP2018提高组省一冲奖班模测训练(一)

    比赛链接 https://www.51nod.com/contest/problemList.html#!contestId=72&randomCode=147206 这次考试的题非常有质量 ...

  6. [51Nod]NOIP2018提高组省一冲奖班模测训练(三) 题解

    链接 A.Anan的派对 题意:Anan想举办一个派对.Anan的朋友总共有 n 人.第i个人如果参加派对会得到 \(c_i\) 的快乐值,除他自己外每多一个人参加他会减少 \(d_i\) 的快乐值. ...

  7. [51Nod]NOIP2018提高组省一冲奖班模测训练(四)翻车记+题解

    链接 下午5点的时候,突然想起来有这个比赛,看看还有一个小时,打算来AK一下,结果因为最近智商越来越低,翻车了,我还是太菜了.上来10分钟先切掉了C和A,结果卡在了B题,唉. A.砍树 一眼题,两遍树 ...

  8. [51Nod]NOIP2018提高组省一冲奖班模测训练(二)

    http://www.51nod.com/contest/problemList.html#!contestId=73&randomCode=4408520896354389006 还是原题大 ...

  9. [51Nod]NOIP2018提高组省一冲奖班模测训练(一)题解

    http://www.51nod.com/contest/problemList.html#!contestId=72&randomCode=147206 原题水题大赛.. A.珂朵莉的旅行 ...

随机推荐

  1. Linux 字符设备驱动简单总结(转)

    http://my.oschina.net/u/1169027/blog/191538

  2. Redis 报错:MISCONF Redis is configured to save RDB snapshots

    MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Com ...

  3. 详解Mysql分布式事务XA(跨数据库事务)

    详解Mysql分布式事务XA(跨数据库事务) 学习了:http://blog.csdn.net/soonfly/article/details/70677138 mysql执行XA事物的时候,mysq ...

  4. Batch Normalization的算法本质是在网络每一层的输入前增加一层BN层(也即归一化层),对数据进行归一化处理,然后再进入网络下一层,但是BN并不是简单的对数据进行求归一化,而是引入了两个参数λ和β去进行数据重构

    Batch Normalization Batch Normalization是深度学习领域在2015年非常热门的一个算法,许多网络应用该方法进行训练,并且取得了非常好的效果. 众所周知,深度学习是应 ...

  5. [IOI 1998] Polygon

    [题目链接] http://poj.org/problem?id=1179 [算法] 区间DP [代码] #include <algorithm> #include <bitset& ...

  6. 1.matlab基础准备及入门

    1.1 Command Window(命令行窗口)运用入门 1 计算器的用法 2 数值变量与表达式 3. 计算结果的图形表示 代码及注释 function [ output_args ] = Unti ...

  7. tomcat指定JDK版本

    在windows环境下以批处理文件方式启动tomcat,只要运行<CATALINA_HOME>/bin/startup.bat这个文件,就可以启动Tomcat. 在启动时,startup. ...

  8. HD-ACM算法专攻系列(18)——Largest prime factor

    题目描述: 源码: 需要注意,若使用cin,cout输入输出,会超时. #include"iostream" #include"memory.h" #defin ...

  9. Microsoft Edge 首个 Chromium 内核版释出

    翻译功能释出 navigator.userAgent"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, ...

  10. canvas绘制三等分饼型图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...