1.数星星(二分)

题意

    天上有n颗星星,每颗星星从第秒(包含边界)开始闪烁,闪烁周期是秒,天上每颗星星每闪烁一次,小度便会记一次数。
    假设小度今晚从第l秒开始数,第r秒天就亮了,当数到c颗星星的时候,小度便会睡着。
    请问小度今晚是否能睡着?如果能,将在第几秒睡着,反之则输出“-1”。

数据范围

思路

    答案具备单调性,且范围极大,正解显然是二分答案!设小度第x秒睡着,显然答案下界为l,上界为r,也就是从l秒开始数直到第x秒结束,判断计数值是否不小于c
    假设星星在第x秒开始闪烁并计数,第y秒结束,闪烁周期为w,则闪烁次数为;如若在第x秒开始闪烁,但从第l秒才开始计数,第y秒结束,则严格大于第l秒的第一次开始计数的时间t为,易知,当y-x能被w整除时,边界点l也需计数一次,同理,之后的计数为.

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<bits/stdc++.h>
#define endl '\n'
using i64 = long long;
using namespace std;
signed main(){
std::ios::sync_with_stdio(false);cin.tie(0);
int n;
i64 l, r, c;
cin >> n;
vector<i64> a(n + 1), b(n + 1);
for (int i = 1; i <= n;i++)cin >> a[i];
for (int i = 1; i <= n;i++)cin >> b[i];
cin >> l >> r >> c;
auto check = [&](i64 x) -> bool
{
i64 res = 0;
for (int i = 1; i <= n;i++){
if(b[i]>x)continue; //计数值显然为零
if(b[i]<l){
i64 ll = b[i] + ((l - b[i]) / a[i] + 1) * a[i]; //ll代表从l秒往后开始第一次闪烁的秒数
if ((l - b[i]) % a[i] == 0){ //特判边界值
res++;
if(res>=c)
return 1;
}
if(x>=ll){
res += (x - ll) / a[i] + 1;
if (res >= c)
return 1;
}
}else{
res += (x - b[i]) / a[i] + 1;
if (res >= c)
return 1;
}
}
return 0;
};
i64 L = l, R = r, ans = -1;
while (L <= R){
i64 mid = L + R >> 1;
if(check(mid))
R = mid - 1, ans = mid;
else
L = mid + 1;
}
cout << ans << endl;
return 0;
}

2.激光控制器(离散化+差分)

题意

    坐标轴上每个整数点都配备了激光控制器,初始状态下所有控制器面朝北方,每拨动一次都会按照”北-东-南-西-北“的顺时针方向循环。
    小度从坐标原点出发,进行N次操作,每次操作用一个整数和一个字符表示,其中表示从当前控制器开始连续拨动多少个控制器,表示拨动方向。数据保证小度不会偏离原点单位。
    给定小度的操作序列,请求出最终朝向东方的激光器有多少台。

数据范围

思路

    对于连续区间的修改,很明显的思路就是差分,但是题目中最大的坐标范围高达,在最终统计时复杂度必然无法满足。然而,我们发现的上限仅,可以对每次进行独立的考虑。
    不难发现,每次操作的区间范围可以求得,也就是区间的端点是固定的且不超过,我们可以对这些端点离散化排序,所以区间操作的变化本质上就是端点间的“跳跃”和区间赋值
    满足条件的区间条件就是操作数量模4等于1,为方便统计,将每个区间的端点以右端点为准,表示整个块,每个独立的块内部的操作数显然是相同的,起始的右端点为原点,每次区间变化使用差分技巧,并对右端点进行特判(原因详见代码注释),在最终统计时只需遍历相邻区间端点,数量就是相邻坐标的差值。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
#define endl '\n'
using i64 = long long;
using namespace std;
signed main(){
std::ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
vector<int> a(n + 2), b(n + 2), x(n + 1), diff(n + 2);
vector<char> op(n + 1);
map<int, bool> mp; //统计存在的右端点下标值
map<int, int> idx; //右端点对应的块号
mp[0] = 1;
int cur = 0; //右端点值
for (int i = 1; i <= n;i++){
cin >> x[i] >> op[i];
if(op[i]=='R')
cur += x[i] - 1, mp[cur] = 1;
else
cur -= x[i] - 1, mp[cur] = 1;
}
int cnt = 0;
for(auto it:mp){
idx[it.first] = ++cnt;
b[cnt] = it.first;
}
cur = 0;
int mn, mx;
for (int i = 1; i <= n;i++){
if(op[i]=='R'){
mn = idx[cur];
cur += x[i] - 1;
mx = idx[cur];
}else{
mx = idx[cur];
cur -= x[i] - 1;
mn = idx[cur];
}
a[mx]++;
diff[mn]++, diff[mx]--;
//对每个右端点当做差分的上界,所以单个右端点需要进行特判
}
int res = 0, ans = 0;
for (int i = 1; i < cnt;i++){
res += diff[i];
if(res%4==1) //除去右端点的块内操作数
ans += b[i + 1] - b[i] - 1;
if ((res + a[i]) % 4 == 1) //右端点的操作数
ans++;
}
res += diff[cnt] + a[cnt];
if(res%4==1)
ans++;
cout << ans << endl;
return 0;
}

3.好像相等(字符串哈希)

题意

    给定一个长度为n的小写字母字符串s,q次询问其子串是否完全相等,如果不相等,那么将他们修改成相等字符串则需要最少将多少种字符更改为“*”,并从小到大输出更改的字符种类.

数据范围

思路

    由于q的询问次数高达次,很显然需要采用离线算法,又涉及到字符串的子串问题,不难想到字符串哈希。由于修改是针对某个字符进行修改,所以我们可以对每个字符单独计算其各个位置的前缀哈希值
    关键是如何判断对应子串位置的哈希值相同,例如字符串“[aba]ss[aba]”,对于子串[1,3]与[6,8]的a字符,只需判断之间是否存在线性关系,其系数显然等于,x表示两个子区间的距离()。
    所以接下来使用双哈希取模法,预处理pw数组(base的指数),维护出某个字符的前缀哈希值,遍历26个字母判断子区间哈希值,将不满足的字符存入vector中即可。
    PS:题设条件不保证,所以输入区间端点的时候务必判断左右方位!!!

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>
#define endl '\n'
using i64 = long long;
using namespace std;
char s[100010];
i64 Hash1[30][100010], Hash2[30][100010], pw1[100010], pw2[100010];
//Hash[i][j]表示字符i在前j个位置的前缀哈希值,pw[i]表示base^i
const i64 base1 = 1313131, base2 = 233333;
const i64 p1 = 1e9 + 7, p2 = 1e9 + 9;
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
int n, q;cin >> n >> q;
cin >> s + 1;
pw1[0] = pw2[0] = 1;
for (int i = 1; i <= 26;i++)
Hash1[i][0] = Hash2[i][0] = 0;
for (int i = 1; i <= n; i++){
pw1[i] = (pw1[i - 1] * base1) % p1;
pw2[i] = (pw2[i - 1] * base2) % p2;
for (int j = 1; j <= 26;j++){
Hash1[j][i] = Hash1[j][i - 1];
Hash2[j][i] = Hash2[j][i - 1];
if (s[i] - 'a' + 1 == j){
Hash1[j][i] = (Hash1[j][i] + (j * pw1[i]) % p1) % p1;
Hash2[j][i] = (Hash2[j][i] + (j * pw2[i]) % p2) % p2;
}
}
}
auto getHash1 = [&](int L, int R, int c) -> i64
{
return (Hash1[c][R] - Hash1[c][L - 1] % p1 + p1) % p1;
};
auto getHash2 = [&](int L, int R, int c) -> i64
{
return (Hash2[c][R] - Hash2[c][L - 1] % p2 + p2) % p2;
};
while(q--){
int lx, rx, ly, ry;
cin >> lx >> rx >> ly >> ry;
if(lx>ly) //保证区间[lx,rx]在左方位
swap(lx, ly), swap(rx, ry);
vector<int> v;
for (int i = 1; i <= 26;i++){
if ((getHash1(lx, rx, i) * pw1[ly - lx]) % p1 == getHash1(ly, ry, i) && (getHash2(lx, rx, i) * pw2[ly - lx]) % p2 == getHash2(ly, ry, i))
continue;
v.emplace_back(i);
}
cout << v.size() << endl;
for(auto it:v){
char ch = (it + 'a' - 1);
cout << ch;
}
cout << endl;
}
return 0;
}