初见安~这里是被毒瘤了两天后终于心(bao)平(fu)气(she)和(hui)地写下了这篇总结。
手动艾特这位非常大(du)佬(liu)的出题人:@lty
传送门:入门OJ P6221
一道简单的题
Description
众所周知,hby喜欢点集。
现在hby有m个点集,第i个点集包含[0,m]中编号为i的倍数的点。
现在有n个区间[li,ri],要你对于这m个点集中的每一个,求出它们
与多少个区间有交。
Input
第一行两个整数n和m。
接下来n行,第i行两个整数li和ri。
对于20%的数据,1≤n≤10^2,1≤m≤10^3。
对于40%的数据,1≤n≤10^4,1≤m≤10^3。
对于100%的数据,1≤n≤3×10^5,1≤m≤10^5,1≤li≤ri≤m。
Output
一行一个整数,表示答案。
Sample Input
3 3
1 2
2 3
3 3
Sample Output
3
2
2
Sol
题目看起来是如此的简洁清晰明了,仿佛是可以秒过的难度【然而并不!!!】题意就是:给你n个区间,m个点集,每个点集存的都是点集序号的倍数,问你每个点集覆盖区间数。
40分的n^2做法:枚举每个区间,枚举每个点集,将这个区间的左右端点(左端点要-1)各自整除这个点集的序号作差,看看是否>0,是则有覆盖这个点集的倍数。这个做法的正确性是可以保证的,但是时间确实撑不住。
进一步思考——刚刚那种做法的核心在于区间的左右端点操作,看看是否有覆盖这个点集。我们再进一步,从端点到区间长度呢?易得:如果一个区间的长度大于等于这个点集的序号,那么一定覆盖了这个点集。如果小于呢?就不一定了。
如果枚举点集,设当前点为i,并把所有长度小于i的区间标记出来,如果有i的倍数所在的点被区间覆盖,那么点集i包含的区间数量+=覆盖所在点的区间数。自然,如果直接枚举区间数,还是会超时的,所以很容易想到差分;再加上我们查询的是i的倍数的点,也就是单点查询,所以我们可以用树状数组or线段树来维护一个差分数组的前缀和。单点查询+区间修改,都是logn的复杂度,所以这个题这么暴力地枚举就不会超时啦!
顺便提醒——不能直接标记所有的区间然后单点查询。因为如果有区间的长度>=i的两倍,那么就会重复计算。反之,只标记的所有长度<i的区间,最多包含1个点集i的数,所以这样计数就不重不漏了。
上代码——
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define maxn 300006
using namespace std;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m;
vector<int> v[maxn];
int ans[maxn];
int tree[maxn];
int ask(int x) {int res = 0; for(; x; x -= x & (-x)) res += tree[x]; return res;}
void change(int x, int y) {for(; x <= m; x += x & (-x)) tree[x] += y;}
//树状维护前缀和,我们的tree是个差分数组
int main() {
register int l, r;
n = read(), m = read();
for(int i = 1; i <= n; i++) {
l = read(), r = read();
v[r - l + 1].push_back(l);//以长度为准绳存区间
}
int ans;
for(int i = 1; i <= m; i++) {//枚举点集
ans = n;//n代表长度>=i的区间数量,可以直接确定会包含这个点集的点。
for(int j = i; j <= m; j += i) ans += ask(j);//单点查询
printf("%d\n", ans);
for(int j = 0; j < v[i].size(); j++) change(v[i][j], 1), change(v[i][j] + i, -1), n--;//差分
}
}
怎么第一题都能讲这么多【我果然还是太颓了】。
传送门:入门OJ P6222
一道很简单的题 & 机房的强者
Description
众所周知, hby 是机房的强者,他的膜法更是一流。不过现在他在外面遇到了难题,失去了计算机和膜法帮助的 hby 求助于你需要你解决两个问题:
Task1:
现在 hby 要从 X 城前往 Y 城参加膜法师交流大会, X 城与 Y 城之间有 n 个交通枢纽(包括这两个城市),它们之间分别由 m 条交通路径相连,交通路径的连接的两个交通枢纽可以通过这条路径相互到达。现在距离膜法师交流大会的开始时间还有 t,但是由于游客过多而有些交通枢纽或交通路径并不能容纳过多的过往游客,因此有一些交通枢纽或交通路径堵塞了导致 hby 不能通过。现在 hby 想要知道他能否在膜法师交流大会开始之前赶到 Y 城。如果能赶到的话请告诉他最早是在多久到达,否则就需要他使用膜法,则输出Please use the spells.。
注意:可能存在 X 城和 Y 城被堵塞的情况,数据保证 X 城和 Y 城并不在同一位置
Task2:
作为机房的强者, hby 有一个数列,数列里的数从 1 到正无穷。现在hby 要将他的数列摆成优美的样子,他的操作如下:- 把这个数列按照奇偶分成两部分 - 先放入一个奇数 1,作为新的数列的第一个,再放入两个偶数 2 和 4,表示数列第二项和第三项,后面每一块的长度都是 2 的幂次排列,数列前几项如下:1 2 4 3 5 7 9 6 8 10 12 14 16 18 20 11 13 15...
现在 hby 想知道他摆出来的数列中任意一段的优美指数,他定义优美指数为这一段的数字的和(笑)。由于答案可能很大,所以请输出对 109 + 7
取模后的结果。
Input
第一行一个整数 K,如果 K = 1 则需要你解决 Task1,如果 K = 2 则需要你解决 Task2。
对于 Task1:
第一行三个整数第一行三个整数 n, m, t, q, X, Y 。
接下来 m 行,每行三个整数 u, v, w,表示编号为 u 的交通枢纽
到编号为 v 交通枢纽之间有一条交通路径,通过这条路径需要花费 w 的时间。
接下来 q 组询问,第一行有两个整数 opt 和 cnt,若 opt = 1 则有一个交通枢纽堵塞了,若 opt = 2 则有一条交通路径堵塞了。
若 opt = 1,则第二行有 cnt 个整数,第 i 个整数为 ai,表示堵塞的交通枢纽的编号。
若 opt = 2,则接下来 cnt 行,每行两个整数 x 和 y,表示堵塞的交通路径所连接的两个交通枢纽编号。
对于 Task2:
一行两个整数 l 和 r。
Constraint
下述所有形如 X pts 的格式皆表示 X 分。
对于 Task1:
共 50pts。
本任务采用捆绑测试
Subtask 1 : 10pts, 1 ≤ n ≤ 10^2, 1 ≤ m ≤ 10^3
Subtask 2 : 10pts, 1 ≤ n ≤ 10^3, 1 ≤ m ≤ 10^4
Subtask 3 : 15pts,无特殊限制条件。
对于 5pts 的数据, t = 0。
对于 5pts 的数据, opt = 1。
对于 5pts 的数据, m = n − 1
对于所有的数据, 1 ≤ n ≤ 10^4, 1 ≤ m ≤ 10^5, 0 ≤ t ≤ 10^6,
1 ≤ q ≤ 102, 1 ≤ X, Y, u, v, ai ≤ n, 1 ≤ w ≤ 10^3, 0 ≤ cnt ≤ opt == 1?n : m。
对于 Task2:
共 50pts。
对于 10pts 的数据, 1 ≤ l ≤ r ≤ 10^7。
对于 40pts 的数据, 1 ≤ l ≤ r ≤ 10^18。
Output
对于 Task1:
对于每组询问输出一个答案。
对于 Task2:
一行一个整数,表示对 10^9 + 7 取模后的优美指数
Sample Input
1
7 10 12 5 1 6
1 2 3
2 3 3
2 5 4
1 4 10
3 4 4
3 5 2
5 6 1
3 6 5
4 7 9
6 7 7
1 2 4 7
2 2
2 3
3 5
1 1 5
2 3
2 5
2 3
5 6
1 5 2 7 4 3 5
Sample Output
8
8
11
Please use the spells.
Please use the spells.
Sol
这一个题就相当于两个题了……【所以我们机房根本就没有善良的出题人】
Task1:
其实说白了就是不让你走一些边或者一些点,问你能否在规定的距离内到达目的地。看数据范围——O(qn)的暴力是可以卡过去的,每次都走一遍最短路然后看距离是否在规定范围内,否则使用膜法。
然而善良的出题人卡了常——如果存不能走的边你用的map,恭喜你,TLE了。【map在什么情况下会被卡常?被卡的时候。】所以只能老老实实地手写hash表。这个题就过了。
Task2:
求区间和,可以想到前缀和——用。对于sum,我们可以利用这个数列的性质,倍增统计奇数和偶数出现的次数,然后运用到等差数列求和公式得出答案【高一知识白学了】
公式很简单:。在这个题里面,对于奇数:从1开始,所以求和时可以化简为:
,偶数同理,可以化简为:。
然后这个题就完了【??!】出题人说很简单,都是送分题。思路确实都不难,但是Task2——思路本身就有点难度。
#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#define maxn 10005
#define maxm 100005//q <= 100
using namespace std;
const int mod = 1e9 + 7;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m, Tm, q, s, t, OPTION;
struct edge {
int to, w, nxt;
edge() {}
edge(int t, int ww, int nn) {to = t, w = ww, nxt = nn;}
}e[maxm << 1];
int head[maxn], k = 0;
void add(int u, int v, int w) {
e[k] = edge(v, w, head[u]);
head[u] = k++;
}
const int p = 13331;
struct node {int u, v, nxt;} Hash[maxm];
int Head[p + 10], cnt = 0;
void insert(int u, int v) {//hash表的insert,邻接表式操作
int key = (u * p + v) % p;
for(int i = Head[key]; i; i = Hash[i].nxt)
if(Hash[i].u == u && Hash[i].v == v) return;
cnt++;
Hash[cnt].u = u, Hash[cnt].v = v, Hash[cnt].nxt = Head[key]; Head[key] = cnt;
}
bool find(int u, int v) {//hash查询
int key = (u * p + v) % p;
for(int i = Head[key]; i; i = Hash[i].nxt) {
if(Hash[i].u == u && Hash[i].v == v) return true;
}
return false;
}
bool cannot[maxn], vis[maxn];
int dis[maxn];
void spfa(bool flag) {
memset(dis, 0x3f, sizeof dis); dis[s] = 0;
if((flag && (cannot[s] || cannot[t]))) return; //这里一定要判定flag,若上次标记了起点或终点,这次就误判了
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
q.push(make_pair(0, s));//优先队列优化【幸好没有卡堆的常数……连SPFA都卡了。
register int u, v;
while(q.size()) {
u = q.top().second; q.pop();vis[u]=0;
for(int i = head[u]; ~i; i = e[i].nxt) {
v = e[i].to;
if((!flag && find(u, v)) || (flag && cannot[v])) continue;
if(dis[u] + e[i].w < dis[v]) {
dis[v] = dis[u] + e[i].w;
if(!vis[v]) q.push(make_pair(dis[v], v)), vis[v] = true;
}
}
}
}
ll l, r;
ll sum(ll x) {
ll len = 0, i = 1, sum1 = 0, sum2 = 0;//sum1 奇 sum2 偶
if(x <= 0) return 0;
bool flag = false;//false 奇 true 偶
while(1) {
len += i;
if(len > x) {//越过x了
len -= i;//回退
if(!flag) sum1 = (sum1 + x - len) % mod;//累加
else sum2 = (sum2 + x - len) % mod;
break;//可以返回值了
}
if(!flag) sum1 = (sum1 + i) % mod;
else sum2 = (sum2 + i) % mod;
i <<= 1; flag ^= 1;
}
return (sum1 * sum1 % mod + sum2 * (sum2 + 1) % mod + mod) % mod;//求和公式
}
int main() {
OPTION = read();
if(OPTION == 1) {
memset(head, -1, sizeof head);
register int u, v, w;
n = read(), m = read(), Tm = read(), q = read(), s = read(), t = read();
for(int i = 1; i <= m; i++)
u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
register int op, askcnt;
for(int i = 1; i <= q; i++) {
op = read(), askcnt = read();
if(op == 1) {
memset(cannot, 0, sizeof cannot);
for(int i = 1; i <= askcnt; i++)
u = read(), cannot[u] = true;
spfa(1);//1 标记点,0标记边
}
else {
memset(Hash, 0, sizeof Hash); cnt = 0;//一定要清空
memset(Head, 0, sizeof Head);
for(int i = 1; i <= askcnt; i++)
u = read(), v = read(), insert(u, v), insert(v, u);//存hash
spfa(0);
}
if(dis[t] > Tm) puts("Please use the spells.");
else printf("%d\n", dis[t]);
}
}
else {//Task 2
scanf("%lld%lld", &l, &r);
printf("%lld\n", (sum(r) - sum(l - 1) + mod) % mod);
}
return 0;
}
一道超级简单的题
传送门:入门OJ P6223
Description
众所周知,神犇 hby 喜欢排列。
现在 hby 有一个从 1 到 n 的顺序排列,他每次选择两个数并交换它们,
他已经进行了 3n 次操作。现在大蒟蒻 lty 也拿来了一个长度一样的顺序排
列想参与其中,不过因为他比较菜,所以他进行了 7n + 1 次操作。
现在机房第二强者 lcx 从游戏现场偷来了最后的排列之一,他现在想
知道这个排列是谁的。如果是 hby 的请输出 Hyscere tql!!!,否则请输出Luvwgyx tcl。
Input
第一行一个整数 n 表示排列的长度。
接下来一行 n 个整数,是 lcx 偷来的序列。
本题采用捆绑测试。
Subtask1 : 20pts, 1 ≤ n ≤ 103。
Subtask2 : 30pts, 1 ≤ n ≤ 106。
subtask3 : 50pts, 1 ≤ n ≤ 5 × 106, timelimits :2s。
Output
对于每组数据输出一行答案。
Sample Input
5
2 4 5 1 3
Sample Output
Hyscere tql!!!
Sol
当前的排列还原回去的最小操作次数一定等于原本的序列操作到当前序列的最小次数。所以——如果你用a次(a < 3n)就操作完毕了,然而非要你用3n or 7n+1次,剩下的次数你会怎么做?两两不停地交换!!保证交换完了过后不影响答案!!而要做到这一点,你最后剩余的可交换次数一定是偶数。而3n和7n+1一定一奇一偶 。所以这就变成了一个让你求出最少交换次数然后判断奇偶的蠢问题……良心出题人,题目难度没有梯度啊!!
本狸对于最少交换次数的判断,是当成并查集一样地找环做的……【有点蠢】
粘一下代码。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 5000005
using namespace std;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int fa[maxn], n, ans = 0;
bool vis[maxn];
void dfs(int x) {
int cnt = 1;
while(fa[x] != x && !vis[x]) {//找环中。fa[x] = x就说明一条链到此为止了。性质不同于环
vis[x] = true;
cnt++; x = fa[x];
}
if(fa[x] == x) ans += cnt - 1;
else ans += cnt - 2;
}
int main() {
n = read();
for(int i = 1; i <= n; i++) fa[i] = read();
for(int i = 1; i <= n; i++)
if(!vis[i]) dfs(i);
if((n * 3 - ans) & 1) puts("Luvwgyx tcl");//判断奇偶
else puts("Hyscere tql!!!");
}
啊啊啊啊啊啊啊啊三个毒瘤题改了好久【接下来还要面对另外三个毒瘤题……