Codeforces Round #719 (Div. 3)题解
\(PS:\)手速局,由于晚点了15分钟左右就没打,赛后补题。
比赛链接
每个题目的链接直接点标题就好了。之前的\(D\)题惨招\(hack\),现已修改。
A. Do Not Be Distracted!
题目大意
给定一个字符串,是否有间断的相同字符序列。
思路
直接枚举然后存一下出现次数就好了。
代码
int n, m;
bool st[30];
int main()
{
int T;
cin >> T;
while (T -- )
{
memset(st, 0, sizeof st);
string s;
cin >> n >> s;
bool flag = false;
st[s[0] - 'A'] = true;
for (int i = 1; i < n; i ++ )
if (s[i] != s[i - 1] && st[s[i] - 'A'])
{
flag = true;
break;
}
else if (s[i] != s[i - 1] && !st[s[i] - 'A']) st[s[i] - 'A'] = true;
if (flag) puts("NO");
else puts("YES");
}
return 0;
}
B. Ordinary Numbers
题意
给定一个\(n\),输出从\(1\)到\(n\)中由相同数字构成的数的个数。
思路
直接枚举一下由每个数字构成的数即可,最多有\(9\)位。
代码
int n, m;
int main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n;
int res = 0;
for (int i = 1; i <= 9; i ++ )
{
LL t = i;
while (t <= n)
{
res ++ ;
t = t * 10 + i;
}
}
cout << res << endl;
}
return 0;
}
C. Not Adjacent Matrix
题意
给定一个\(n * n\)的棋盘,要求将\(1\)到\(n * n\)的数放到棋盘中,并且相邻的格子里的数不能相邻。
思路
先放奇数,再放偶数,放偶数的时候判断一下,若不能放则输出\(-1\)。
代码
const int N = 110;
int n, m;
int res[N][N];
bool put(int &x, int &y, int t)
{
for (int i = x; i <= n; i ++ )
{
int j = y;
if (i != x) j = 1;
for (; j <= n; j ++ )
{
res[i][j] = t;
t += 2;
if (abs(res[i - 1][j] - res[i][j]) == 1 && i != 1) return false;
if (t > n * n)
{
x = i;
y = j;
return true;
}
}
}
return true;
}
int main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n;
int x = 1, y = 1;
put(x, y, 1);
y ++ ;
if (!put(x, y, 2)) puts("-1");
else
{
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
cout << res[i][j] << ' ';
cout << endl;
}
}
}
return 0;
}
D. Same Differences
题意
给定一个长度为\(n\)的序列,要求输出使得\(a_j-a_i=j-i\)的数对的个数。
思路
式子变形一下可以得到\(a_j-j=a_i-i\),则使得原式子成立的数对必定有当前的数减去下标得到的值是相等的,因此我们只需要统计一下\(a_i-i\)出现的个数,然后从其中选出两个能得到的对出即可。
注意:下标可能为负,需要加\(n\)映射一下。
代码
typedef long long LL;
const int N = 400010;
int n, m;
int g[N];
LL cnt[N];
int main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> g[i];
for (int i = 0; i <= 2 * n; i ++ ) cnt[i] = 0;
for (int i = 1; i <= n; i ++ )
cnt[g[i] - i + n] ++ ;
LL res = 0;
for (int i = 0; i <= 2 * n; i ++ )
res += cnt[i] * (cnt[i] - 1) / 2;
cout << res << endl;
}
return 0;
}
E. Arranging The Sheep
题意
给定一个字符串,要求输出将字符串中的\({ * }\)全部放到一起所需要的最小步数,注意,只有当移动的位置是空(\(.\))才可以移动。
思路
两种做法
\(dp\)
\(fl[i][j]\)表示将第\(i\)个位置前的\({ * }\)排在一起,并且队头是第\(i\)个位置所需要的步数,\(fr[i][j]\)表示将第\(i\)个位置后的\({ * }\)排在一起,并且队尾是第\(i\)个位置所需要的步数。最后遍历一遍取\(min\)就好了。
中位数
我们发现,中位数的性质,就是其余所有的点到中位数的路程是最短的,所以说我们要找中位数。然后两边依次排到中位数两边即可。
两种做法没有本质区别,中位数无非就是挖掘出了\(dp\)做法中某些性质。
代码
\(dp\)
typedef long long LL;
const int N = 1000010;
int n, m;
LL fl[N];
LL fr[N];
char s[N];
int main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n >> s + 1;
for (int i = 1; i <= n + 1; i ++ ) fl[i] = 0, fr[i] = 0;
LL cnt = 0;
for (int i = 1; i <= n; i ++ )
if (s[i] == '*')
{
fl[i] = fl[i - 1];
cnt ++ ;
}
else fl[i] = fl[i - 1] + cnt;
cnt = 0;
for (int i = n; i >= 1; i -- )
if (s[i] == '*')
{
fr[i] = fr[i + 1];
cnt ++ ;
}
else fr[i] = fr[i + 1] + cnt;
LL res = 1e18;
for (int i = 1; i <= n; i ++ )
res = min(res, min(fl[i] + fr[i + 1], fl[i - 1] + fr[i]));
cout << res << endl;
}
return 0;
}
中位数
typedef long long LL;
const int N = 200010;
int n, m;
string s;
int main()
{
int T;
cin >> T;
while (T -- )
{
string s;
cin >> n >> s;
int sum = 0, cnt = 0;
for (int i = 0; i < n; i ++ )
if (s[i] == '*') sum ++ ;
sum = (sum + 1) / 2;
int t = -1;
for (int i = 0; i < n; i ++ )
if (s[i] == '*')
{
cnt ++ ;
if (cnt == sum)
{
t = i;
break;
}
}
LL res = 0;
cnt = 0;
for (int i = 0; i < n; i ++ )
if (s[i] == '*')
{
cnt ++ ;
res += abs(t - i) - abs(sum - cnt);
}
cout << res << endl;
}
return 0;
}
F1. Guess the K-th Zero (Easy version)
题意
交互(简单版本),给定一个隐藏的只包含\(0\)和\(1\)的数组,每次给出一个区间询问\([L,R]\),并返回区间\([L,R]\)之间的数的和。要求在\(20\)次询问以内输出第\(k\)个\(0\)的位置。
思路
二分查找,每次二分查找第\(k\)个\(0\)所在的区间。对于询问\([L,Mid]\)及返回的值\(sum\),其中\(0\)的个数为\(Mid+L-1-sum\)。若小于\(k\)则说明第\(k\)个\(0\)在区间\([Mid,R]\),此时我们可以区间\([L,Mid]\)中\(0\)的个数减去再继续查找。
代码
int n, m, k;
int main()
{
cin >> n >> m >> k;
int l = 1, r = n;
int sum;
while (l < r)
{
int mid = l + r >> 1;
cout << "? " << l << ' ' << mid << endl;
cout.flush();
cin >> sum;
if (mid - l + 1 - sum >= k) r = mid;
else k -= mid - l + 1 - sum, l = mid + 1;
}
cout << "! " << r << endl;
cout.flush();
return 0;
}
F2. Guess the K-th Zero (Hard version)
题意
交互(复杂版本),再\(F1\)的基础上多了几次需要查询的\(k\)值,并且每次查询之后需要将该处的\(0\)变为\(1\),询问的上界为\(6 * 10^4\)。
思路
根据\(F1\)查询的思路,发现我们是需要使用到前\(i\)个数中\(1\)出现的次数,也就是前缀和,那么我们可以首先依次询问\([1,R],(1\leq R\leq n)\)然后维护一下前缀和,再对每个查询用二分查找。
但是这样由于\(n\leq 2 * 10^5\),如果依次询问必定会超过上界,因此可以将区间\([1,n]\)分块,维护一下分块的前缀和,然后对每个边界二分询问即可。设分块大小为\(x\),则\(\frac{n}{x}+m * logx\leq 6 * 10^4\),化简之后得\(\frac{20}{x}+logx\leq 6\),解得近似\(5\leq x\leq 30\),对时间复杂度进行分析之后发现取\(x=30\)最好。
注意:我们维护的是分块边界,内部并没有维护,因此\(k\)需要减去的是边界处对应的值。
代码
const int N = 200100;
int tr[N];
int n, m, k;
int lowbit(int x)
{
return x & -x;
}
int ask(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
void add(int x, int d)
{
for (int i = x; i <= n; i += lowbit(i)) tr[i] += d;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i ++ )
{
cin >> k;
if (i == 1)
{
int sum1 = 0, sum2 = 0;
for (int i = 1; i < n; i += 30)
{
cout << "? " << 1 << " " << i << endl;
cout.flush();
cin >> sum2;
add(i, sum2 - sum1);
sum1 = sum2;
}
cout << "? " << 1 << " " << n << endl;
cout.flush();
cin >> sum2;
add(n, sum2 - sum1);
}
int l = 1, r = n;
for (int i = 1; i < n; i += 30)
if (i - ask(i) >= k)
{
l = max(1, i - 30) + 1;
r = i;
k -= l - 1 - ask(l - 1);
break;
}
while (l < r)
{
int sum;
int mid = l + r >> 1;
cout << "? " << l << " " << mid << endl;
cout.flush();
cin >> sum;
if (mid - l + 1 - sum >= k) r = mid;
else k -= mid - l + 1 - sum, l = mid + 1;
}
cout << "! " << r << endl;
cout.flush();
add(r, 1);
}
cin >> k;
return 0;
}