2019牛客多校第三场题解
B.Crazy Binary String
子序列维护前缀和即可,子串答案(2*min(0,1))的个数。
Code
#include<bits/stdc++.h>
typedef long long ll;
const int MAXN = 1e5 + 5, MAXM = 1e5 + 5, INF = 0x3f3f3f3f, MOD = 998244353;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
using namespace std;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define pb push_back
#define random(a,b) ((a)+rand()%((b)-(a)+1))
typedef double db;
int n, cnt[2], mp[MAXN << 1], delta = 100000;
char t[MAXN];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
cin >> (t + 1);
int sum = 0, ans = 0;
for (int i = 1; i <= n; i++) {
cnt[t[i] - '0']++;
if (t[i] == '1')sum++;
else sum--;
if (sum == 0)ans = max(ans, i);
if (mp[sum + delta]) {
ans = max(ans, i - mp[sum + delta]);
}
else mp[sum + delta] = i;
}
cout << ans << ' ' << min(cnt[0], cnt[1]) * 2 << '
';
return 0;
}
D.Big Integer
易得到(A(n)=frac{10^n-1}{9}),所以题目就是要求(A(n)=frac{10^n-1}{9}=0(modp))。
考虑(p!=3)时,有((10^n-1)*inv(9)=0(mod p)),因(inv(9)!=0),故式子可以化为(10^n=1(modp))。
再考虑(p!=2,p!=5)的情况,那么就有(gcd(10,p)=1),所以(10^{varphi(p)}=1(modp)),因为(10^0=1(modp)),故可以知道一个循环节为(varphi(p)=p-1)。现在我们要求最小循环节(d),那么肯定满足(d|(p-1)),这里我们直接暴力枚举来求就行了。
接下来就是求对于所有的(1leq ileq n,1leq jleq m),满足(d|i^j)的个数。
将(d)进行分解为:(p_1^{k_1}p_2^{k_2}...p_t^{k_t}),那么要满足上面的条件就有(g=p_1^{lceil frac{k_1}{j}
ceil}p_2^{lceil frac{k_2}{j}
ceil}...p_t^{lceil frac{k_t}{j}
ceil}|i),此时(i)的个数为(frac{n}{g})。之后枚举(j)就行了,这里我们只用枚举到(30),再大一点值都是一样的了。
如果(p=2||p=5)时,答案显然为(0)。
现在考虑(p=3)的情况,对于一个数来说,如果它为(3)的倍数,那么其每个位置上面的数字之和就为(3)的倍数。对于这个题来说就是求(i^j=0(mod 3))的所有(i,j)个数。直接算就行了。
详细见代码吧,这个题也可以不把(9)约去,直接枚举(varphi(9p))的约数。但是因为模数超过了int,可能会爆long long。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5, INF = 1e9;
int T;
int n, m, p;
ll qp(ll a, ll b, int P) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % P;
a = a * a % P;
b >>= 1;
}
return ans;
}
int pri[N], cnt[N], k;
void work(int x, int *a, int *b) {
k = 0;
for(int i = 2; 1ll * i * i <= x; i++) {
if(x % i == 0) {
a[++k] = i; int c = 0;
while(x % i == 0) c++, x /= i;
b[k] = c;
}
}
if(x > 1) {
a[++k] = x; b[k] = 1;
}
}
int solve(int j) {
int x = 1;
for(int i = 1; i <= k; i++) {
int t = (cnt[i] + j - 1) / j;
while(t--) x *= pri[i];
}
return n / x;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> p >> n >> m;
if(p == 2 || p == 5) {
cout << 0 << '
';
continue ;
}
if(p == 3) {
cout << 1ll * m * (n / 3) << '
';
continue;
}
int phi = p - 1, d = INF;
for(int i = 1; 1LL * i * i <= phi; i++) {
if(phi % i == 0) {
if(qp(10, i, p) == 1) d = min(d, i);
if(qp(10, phi / i, p) == 1) d = min(d, phi / i);
}
}
work(d, pri, cnt);
ll ans = 0;
for(int j = 1; j <= min(30, m); j++) ans += solve(j);
if(m > 30) ans += 1ll * (m - 30) * solve(30);
cout << ans << '
';
}
return 0;
}
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 505, INF = 1e9;
int T;
int n, m;
int a[N][N];
int cmx[N], cmn[N];
int q1[N], q2[N];
int l1, r1, l2, r2;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n >> m;
int ans = 0;
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) cin >> a[i][j];
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) cmx[j] = 0, cmn[j] = INF;
for(int j = i; j <= n; j++) {
for(int k = 1; k <= n; k++) cmx[k] = max(cmx[k], a[j][k]), cmn[k] = min(cmn[k], a[j][k]);
l1 = l2 = 1; r1 = r2 = 0;
int p = 1;
for(int k = 1; k <= n; k++) {
while(l1 <= r1 && cmx[q1[r1]] <= cmx[k]) r1--;
q1[++r1] = k;
while(l2 <= r2 && cmn[q2[r2]] >= cmn[k]) r2--;
q2[++r2] = k;
while(cmx[q1[l1]] - cmn[q2[l2]] > m) {
p++;
if(l1 <= r1 && q1[l1] < p) l1++;
if(l2 <= r2 && q2[l2] < p) l2++;
if(p > k) break;
}
ans = max(ans, (j - i + 1) * (k - p + 1));
}
}
}
cout << ans << '
';
}
return 0;
}
G.Removing Stones
可以发现必胜条件为:对于一段区间来说,(sum_r-sum_{l-1}>=2*mx)。
一个区间的最大值可以将区间分为两个部分,所以就可以考虑分治,对于最大值的位置分成的两个区间,枚举范围小的那个区间,在另一个区间里面二分就行了(枚举范围大的复杂度可能退化为O(n^2)而不是(O(nlogn)))。
复杂度(O(nlognlogn)),求最大位置时还需要个st表,不然会T。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 300005;
int T;
int n;
int a[N];
ll sum[N];
ll ans;
int f[N][19], pos[N][19];
int lg[N];
void init() {
for(int i = 1; i <= n; i++) f[i][0] = a[i], pos[i][0] = i;
for(int j = 1; j <= 18; j++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
if(f[i][j - 1] > f[i + (1 << (j - 1))][j - 1]) {
pos[i][j] = pos[i][j - 1];
} else pos[i][j] = pos[i + (1 << (j - 1))][j - 1];
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]) ;
}
}
}
int query(int l, int r) {
int k = lg[r - l + 1];
if(f[l][k] > f[r - (1 << k) + 1][k]) return pos[l][k];
return pos[r - (1 << k) + 1][k];
}
void solve(int l, int r) {
if(r - l <= 0) return;
if(r - l == 1) {
if(a[l] == a[r]) ans++;
return;
}
int k = query(l, r);
int mx = a[k];
if(k - l < r - k) {
for(int L = l; L <= k; L++) {
int LL = k, RR = r + 1, mid;
while(LL < RR) {
mid = (LL + RR) >> 1;
if(sum[mid] - sum[L - 1] - mx >= mx) RR = mid;
else LL = mid + 1;
}
ans += (r - LL + 1);
}
} else {
for(int R = k; R <= r; R++) {
int LL = l ,RR = k + 1, mid;
while(LL < RR) {
mid = (LL + RR) >> 1;
if(sum[R] - sum[mid - 1] - mx >= mx) LL = mid + 1;
else RR = mid;
}
ans += (LL - l);
}
}
solve(l, k - 1); solve(k + 1, r);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
for(int i = 2; i < N; i++) lg[i] = lg[i >> 1] + 1;
cin >> T;
while(T--) {
cin >> n; ans = 0;
for(int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i - 1] + a[i];
init();
solve(1, n);
cout << ans << '
';
}
return 0;
}
H.Magic Line
按x,y进行排序,之后考虑稍微倾斜直线就行了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1005, INF = 1e8;
int T;
struct point{
int x, y;
bool operator < (const point &A)const {
if(x == A.x) return y < A.y;
return x < A.x;
}
}a[N];
int n;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y;
sort(a + 1, a + n + 1);
if(a[n / 2].x == a[n / 2 + 1].x) {
cout << a[n / 2].x - 1 << ' ' << a[n / 2].y + INF << ' ' << a[n / 2 + 1].x + 1 << ' ' << a[n / 2 + 1].y - INF << '
';
} else {
cout << a[n / 2].x << ' ' << -INF << ' ' << a[n / 2 + 1].x << ' ' << INF << '
';
}
}
return 0;
}
I.Median
有个结论就是(a_i)一定等于影响它的中位数三者之一。
证明的话可以手推一下,最后会发现(a_i)要么都大于等于这三个数,要么都小于等于,显然取等于是可行的。
之后就进行(dp),对于当前这一位(i),通过(i-2,i-1)转移过来,设(dp[i][j][k])表示第(i)位用与之相关的第(j+1)大中位数,(i-1)位用与之相关的第(k+1)大中位数,最后从(dp[i-1][k][l])转移过来。
(v[i][j])储存的就是影响第(i)个数的第(j+1)大中位数。
转移的时候记录一下前驱就行了。
合法性判断的时候注意一下一个是前面这个状态合法,另一个是填当前这个数的话满足中位数等于(b_{i-1})(代码里面的下标是(i-1),实际上是(i-2))
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int T;
int b[N], a[N];
int dp[N][3][3], v[N][3], pre[N][3][3];
int n;
int med(int x, int y, int z) {
int tmp[3];
tmp[0] = x, tmp[1] = y, tmp[2] = z;
sort(tmp, tmp + 3);
return tmp[1];
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
for(int i = 1; i <= n; i++)
for(int j = 0; j < 3; j++)
for(int k = 0; k < 3; k++)
dp[i][j][k] = pre[i][j][k] = 0;
cin >> n;
for(int i = 2; i < n; i++) cin >> b[i];
b[0] = b[1] = b[2];
b[n + 1] = b[n] = b[n - 1];
for(int i = 1; i <= n; i++) {
for(int j = 0; j < 3; j++) {
v[i][j] = b[i + j - 1];
}
sort(v[i], v[i] + 3);
}
for(int i = 1; i <= 2; i++)
for(int j = 0; j < 3; j++)
for(int k = 0; k < 3; k++)
dp[i][j][k] = 1;
for(int i = 3; i <= n; i++) {
for(int j = 0; j < 3; j++) {
for(int k = 0; k < 3; k++) {
for(int l = 0; l < 3; l++) {
if(!dp[i - 1][k][l]) continue;
if(med(v[i - 2][l], v[i - 1][k], v[i][j]) != b[i - 1]) continue;
dp[i][j][k] = 1;
pre[i][j][k] = l;
}
}
}
}
int x = -1, y = -1;
for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) {
if(dp[n][i][j]) {
x = i, y = j;
break ;
}
}
if(x == -1 || y == -1) {
cout << -1 << '
';
continue ;
}
for(int i = n; i >= 1; i--) {
a[i] = v[i][x];
x = pre[i][x][y];
swap(x, y);
}
for(int i = 1; i <= n; i++) cout << a[i] << ' ' ;
cout << '
';
}
return 0;
}
J.LRU management
直接模拟,细节有点多。。。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 500005, M = 15;
int T;
char s[M];
int st, ed, sz;
int q, m;
int L[N], R[N], data[N];
int trie[N * 10], pos[N * 10], ch[N * 10][10], tot;
void trieInsert(int it, char *s){
assert(strlen(s) <= 10);
int p = 0;
for (int i = 0; s[i]; i++){
int &t = ch[p][s[i] - '0'];
if (!t) t = ++tot; p = t;
}
pos[trie[it] = p] = it;
}
int trieFind(char *s){
int p = 0;
for (int i = 0; s[i]; i++){
p = ch[p][s[i] - '0'];
if(!p) return 0;
}
return pos[p];
}
void Erase(int x){
int pre = L[x], succ = R[x];
if (pre) R[pre] = succ;
if (succ) L[succ] = pre;
data[x] = L[x] = R[x] = 0;
if (x == st) st = succ;
if (x == ed) ed = pre;
--sz;
}
void Insert(int x, int v){
data[x] = v;
if (!st) st = ed = x;
else L[R[ed] = x] = ed, ed = x;
++sz;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
st = ed = sz = 0;
pos[tot = 0] = 0;
cin >> q >> m;
int num = 0;
while(q--) {
int op, v;
cin >> op >> s + 1 >> v;
int it = trieFind(s + 1);
if(op == 1) {
if(!it || (v == 1 && !R[it]) || (v == -1 && !L[it])) cout << "Invalid" << '
';
else {
if(v == -1) it = L[it];
if(v == 1) it = R[it];
cout << data[it] << '
';
}
} else {
if(it) {
v = data[it];
Erase(it);
}
if(sz == m) pos[trie[st]] = 0, Erase(st);
Insert(++num, v);
trieInsert(num, s + 1);
cout << v << '
';
}
}
for(int i = 0; i <= tot; i++) {
for(int j = 0; j < 10; j++) ch[i][j] = 0;
pos[i] = trie[i] = 0;
}
while(st) Erase(st);
}
return 0;
}