\(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;
}