A - Binary Imbalance
题意:给定一个01串,你能够在相邻相同字符中插入‘1’,在相邻不同字符中插入‘0’,求最终能否使得0的数量严格大于1的数量。
思路:可以发现,当出现了‘01’或者‘10’子序列时,能够无限在中间插入‘0’,所以只要出现了0,最终一定能满足题意。
// Problem: F. Trees and XOR Queries Again
// Contest: Codeforces - Educational Codeforces Round 159 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1902/problem/F
// Memory Limit: 512 MB
// Time Limit: 6500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n;
string s;
cin >> s;
for(int i = 0 ; i < s.size() ; i ++){
if(s[i] == '0'){
cout <<"YES\n";
return;
}
}
cout <<"NO\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
B - Getting Points
题意:给定数字n,p,l,t。表示了总共有n天,其中每一天能够休息或者去狠狠得分,每次能够得l的分数。同时会在第1天、第8天、第15天....第(7x+1)天得到一个任务,任务能够在之后的任意一天完成,且每个任务的分值为t。同时每天能够在得分的过程中完成最多两项任务。求达到目标分数p及以上的得分方案中,休息天数最大的天数。
思路:由于任务是累计的,因此在越后面的天数选择得分就越优。因此有如下最优方案:从最后一天往前开始得分,直到满足分数p为止。注意任务总数有上限即可。
// Problem: B. Getting Points
// Contest: Codeforces - Educational Codeforces Round 159 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1902/problem/B
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
LL n , p , l , t;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n >> p >> l >> t;
LL maxt = (n - 1) / 7 + 1;
LL day = 0;
if(maxt * t + l * ((maxt + 1) / 2) >= p){//最后(maxt +1)/2天上课做任务
day += (p - 1) / (l + 2 * t) + 1;
cout << n - day<< endl;
}
else{
p -= maxt * t + l * ((maxt + 1) / 2);
day += (maxt + 1) / 2;
day += (p - 1) / l + 1;
cout << n - day<< endl;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
C - Insert and Equalize
题意:给定一个完全不同的数组a,要求添加不同于a数组中任意一个数的自然数到数组a当中,且选择一个正整数x,要求数组中每个数加上若干次x后全部都相等。求加x操作的最小操作数。
思路:可以发现x应该是越大越好。注意到如下事实:若两个数最终加上若干次x相等,那么必然能够满足其中一个数加上若干次x后等于另一个数。也就是说两个数相减是x的倍数。因此x应当为所有数差值的gcd。
求出x以后,考虑添加的数为多少,我们可以令最终所有的数都成为整个数组当中最大的数,那么添加的数就应该尽可能的靠近最大的数。这样才能够使得操作数最小。
所有都求完时候求出需要增加的总数/x即可。
// Problem: C. Insert and Equalize
// Contest: Codeforces - Educational Codeforces Round 159 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1902/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<LL>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
a[0] = -llinf;
cin >> n;
LL sum = 0;
for(int i = 1 ; i <= n ; i ++)
cin >> a[i] , sum += a[i];
sort(a.begin() + 1 , a.begin() + n + 1);
if(n == 1){
cout << 1 << endl;
return;
}
int x = a[2] - a[1];
for(int i = 3 ; i <= n ; i ++){
int nx = gcd(x , a[i] - a[i - 1]);
x = nx;
}
for(int i = n ; i >= 1; i --){
if(a[i] - a[i - 1] != x){
sum += a[i] - x;
break;
}
}
cout << (a[n] * (n + 1) - sum) / x << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
D - Robot Queries
题意:有一个无限的 2 维网格。一开始,机器人站在 (0,0)点。机器人可以执行四条指令:
- U - 从点 (x,y)移动到 (x,y+1);
- D - 从点 (x,y)移动到 (x,y−1);
- L--从点 (x,y)移动到 (x−1,y);
- R--从点 (x,y)移动到 (x+1,y)。
给你一串长度为 n的命令 s 。您的任务是回答 q 独立查询:给定四个整数 x 、 y 、 l 和 r ;判断机器人在执行命令序列 s 时是否访问了点 (x,y) ,但 l 到 r 的子串是相反的(即机器人执行命令的顺序是 s1s2s3…sl−1srsr−1sr−2…slsr+1sr+2…sn )。
思路:(一定要想清楚题目在干什么...比赛时想假了)可以发现:对于反转[L,R]的操作而言,机器人从[0,L - 1]上的位置时不会改变的,从[R,n]的位置也是不会改变的,因此考虑记录机器人所有时刻能够到达的点的情况,同时记录一下机器人到达某个点的时间情况。查询的时候只需要判断(x,y)所对应的时间情况是否存在与上述两个区间当中。
在处理一个查询时,我们不可能去从[L,R]逐个修改位置,因此考虑的是(x , y)这个点在[L,R]范围内反转的等价情况(x',y')。例如pos(r - 1) = {0 , 1} , pos(r) = {1 , 1} , pos(l - 1) = {x , y} 。 那么在翻转之后,pos(l - 1 + 1) = {x + (pos(r - 1).x - pos(r).x , y + (pos(r - 1).y - pos(r).y)} ,将r - 1 中的 1改成任意数也是一样的情况。也就是说[L,R]中翻转后的点p相当于为{pos(l - 1).x + pos(r).x - pos(p).x , pos(l-1).y +pos(r).y - pos(p).y}。我们对范围内每个点进行操作显然是不成立的,因此可以对(x , y)进行操作。然后判断(x' ,y')是否出现在[L,R]内。
// Problem: D. Robot Queries
// Contest: Codeforces - Educational Codeforces Round 159 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1902/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n >> m;
int nx = 0 , ny = 0;
string s;
cin >> s;
s = " " + s;
pair<int,int>pos[n + 5];
pos[0] = {nx , ny};
for(int i = 1 ; i <= n ; i ++){
if(s[i] == 'R'){
nx++;
}
else if(s[i] == 'D'){
ny--;
}
else if(s[i] == 'L'){
nx--;
}
else{
ny++;
}
pos[i] = {nx , ny};
}
map< pair<int,int> , set<int> >vis;
for(int i = 0 ; i <= n ; i ++){
vis[pos[i]].insert(i);
}
auto check = [&] (pair<int,int> p, int l ,int r){
if(!vis.count(p)){
return false;
}
else{
auto it = vis[p].lower_bound(l);
return it != vis[p].end() && *it <= r;
}
};
//X' = POS[L - 1].X + (POS[R].X - X)
for(int i = 0 ; i < m ; i ++){
int x , y , l , r;
cin >> x >> y >> l >> r;
if(check({x , y} , 0 , l - 1) || check({x , y} , r , n) || check({pos[l - 1].x + pos[r].x - x ,pos[l - 1].y + pos[r].y - y} , l , r)){
cout << "YES\n";
}
else{
cout << "NO\n" ;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
//cin>>t;
while(t--)
{
solve();
}
return 0;
}
E - Collapsing Strings
题意:给你 n 个由小写拉丁字母组成的字符串 s1,s2,…,sn 。假设 |x| 是字符串 x
的长度。
假设两个字符串 a 和 b 的合并 C(a,b)
运算如下:
- 如果 a为空,则 C(a,b)=b;
- 如果 b为空,则 C(a,b)=a;
- 如果 a的最后一个字母等于 b 的第一个字母,则 C(a,b)=C(,) ,其中 是 s 从 l字母到 r字母的子串;
- 否则为 C(a,b)=a+b,即两个字符串的连接。
计算 .
思路:所有区间求和考虑定1求1,即枚举每个,考虑整体维护的情况,整个过程中,我们需要记录的是满足的后缀等于的每个前缀子串的数量。最傻瓜的方法就是直接用字符串哈希(很容易被卡)来维护每个后缀的数量以及这些后缀所对应的字符串的总长度。然后再枚举每个的前缀去逐一相加。
// Problem: E. Collapsing Strings
// Contest: Codeforces - Educational Codeforces Round 159 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1902/problem/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
const int M1 = 1e9 + 7;
const int B1 = 29;
const int M2 = 1e15 + 7;
const int B2 = 31;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
LL get_hash1(LL pre , char c) {
return (pre * B1 + (LL)(c - 'a' + 1)) % M1;
}
LL get_hash2(LL pre , char c){
return (pre * B2 + (LL)(c - 'a' + 1)) % M2;
}
map<pair<int,int>,int>cnt_r , r;
void solve()
{
cin >> n;
string s[n + 5];
int tot = 0;
for(int i = 1 ; i <= n ; i ++)
cin >> s[i];
for(int i = 1 ; i <= n ; i ++){
int len = s[i].size();
tot += len;
LL hash1 = 0;
LL hash2 = 0;
for(int j = len - 1 ; j >= 0 ; j --){
hash1 = get_hash1(hash1 , s[i][j]);
hash2 = get_hash2(hash2 , s[i][j]);
cnt_r[{hash1 , hash2}]++;
r[{hash1 , hash2}] += len;
}
}
LL ans = 0;
for(int i = 1 ; i <= n ; i ++){
int len = s[i].size();
int res = n;
LL num = tot;//所有字符串中未被计算过的字符串总长度
LL hash1 = 0;
LL hash2 = 0;
hash1 = get_hash1(hash1 , s[i][0]);
hash2 = get_hash2(hash2 , s[i][0]);
int pre = cnt_r[{hash1 , hash2}];
int prer = r[{hash1 , hash2}];
int t = res - pre;//字符串结尾与当前字符串开头不一样的个数
LL ss = num - (prer);
num -= ss;
res -= t;
ans += len * t + ss;
int f = 1;
for(int j = 0 ; j < len - 1; j ++){
LL H1 = get_hash1(hash1 , s[i][j + 1]);
LL H2 = get_hash2(hash2 , s[i][j + 1]);
int npre;
int nprer;
if(!cnt_r.count({H1 , H2})){
f = 0;
npre = 0;
nprer = 0;
}
else{
npre = cnt_r[{H1 , H2}];
nprer = r[{H1 , H2}];
}
int t = pre - npre;
LL ss = num - (nprer);
ans += (len - j - 1) * t + ss - t * (j + 1);
num -= ss;
res -= t;
if(!f)
break;
hash1 = H1;
hash2 = H2;
pre = npre;
prer = nprer;
}
if(f)
ans += r[{hash1 , hash2}] - cnt_r[{hash1,hash2}] * len;
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
solve();
}
return 0;
}
傻瓜版本十分复杂,过程中我们会发现:要求不同字符串的总和非常困难,但是相反,对于相同字符串的统计非常简单,直接用map即可。因此考虑正难则反,先假设不考虑删字母的情况,然后去逐一删去相同字符。
// Problem: E. Collapsing Strings
// Contest: Codeforces - Educational Codeforces Round 159 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1902/problem/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
const int M1 = 1e9 + 7;
const int B1 = 29;
const int M2 = 1e15 + 7;
const int B2 = 31;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
LL get_hash1(LL pre , char c) {
return (pre * B1 + (LL)(c - 'a' + 1)) % M1;
}
LL get_hash2(LL pre , char c){
return (pre * B2 + (LL)(c - 'a' + 1)) % M2;
}
map<pair<int,int>,int>cnt_r , r;
void solve()
{
cin >> n;
string s[n + 5];
int tot = 0;
for(int i = 1 ; i <= n ; i ++)
cin >> s[i];
for(int i = 1 ; i <= n ; i ++){
int len = s[i].size();
tot += len;
LL hash1 = 0;
LL hash2 = 0;
for(int j = len - 1 ; j >= 0 ; j --){
hash1 = get_hash1(hash1 , s[i][j]);
hash2 = get_hash2(hash2 , s[i][j]);
cnt_r[{hash1 , hash2}]++;
}
}
LL ans = n * 2 * tot;
for(int i = 1 ; i <= n ; i ++){
int len = s[i].size();
LL hash1 = 0;
LL hash2 = 0;
for(int j = 0 ; j < len; j ++){
hash1 = get_hash1(hash1 , s[i][j]);
hash2 = get_hash2(hash2 , s[i][j]);
if(cnt_r.count({hash1 , hash2})){
// cout << cnt_r[{hash1 , hash2}] << endl;
ans -= 2 * cnt_r[{hash1 , hash2}];
}
else{
break;
}
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
solve();
}
return 0;
}
发现只需要统计后缀的数量即可,再进一步可以用字典树来维护数量。
F - Trees and XOR Queries Again
题意:给你一棵由 n 个顶点组成的树。每个顶点上都写有一个整数;第 i 个顶点上写有整数 ai。您必须处理 q个查询。第 i 个查询由三个整数 xi 、 yi 和 ki 组成。对于这个查询,您必须回答是否有可能选择一个顶点集合 v1,v2,…,vm(可能是空),使得:
- 每个顶点 vj都在 xi 和 yi之间的简单路径上(也可以使用端点);
- ,其中 ⊕ 表示位向 XOR 运算符。
思路:对于一段序列中任选若干数的异或和是否为某个数,可以用线性基来求解。因此考虑对树上每个点创建一个线性基。一个节点的线性基由其到根节点路径上所有点构成。这个操作可以由一遍DFS然后向下传递线性基完成。接下来考虑如何去解决问题:对于一个询问x , y 而言,两者简单路径上的线性基由 lca(x , y) - > x 的线性基与lca(x,y)->y 的线性基相合并组成。现在已知根到该点的线性基,考虑如何去知晓某个祖先到该点的线性基。这里用了加入时间戳(深度戳)的思想,记录线性基上面每一位的最深深度,然后如果深度更深的点也能够组成该位,那么就替换掉。这样就能够确定线性基上的每一位是由哪个深度的节点组成的。即能够判断线性基上的某一位是否在lca(x,y)->x的路径上。