代码源每日一题 div1 (401-407)
蜗蜗的数列
[Link](蜗蜗的数列 - 题目 - Daimayuan Online Judge)
思路
- 差分
首先 c i = a i − b i c_i=a_i-b_i ci=ai−bi,等价于让我们求操作后 c i c_i ci均为零。对于区间操作我们倾向于改变成单点操作,这样复杂度就降下来了,观察斐波那契数列 f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2,每一项与前两项有关,我们设 d i = c i − c i − 1 − c i − 2 d_i=c_i-c_{i-1}-c_{i-2} di=ci−ci−1−ci−2,拿对 a a a进行区间操作来看,对 [ l , r ] [l,r] [l,r]进行斐波那契 d l = d l + f 1 d_l=d_l+f_1 dl=dl+f1,中间任意一项均会被消除,剩下的只有 r + 1 , r + 2 r+1,r+2 r+1,r+2这两项受到影响了,由于最终要让 c i c_i ci均为零等价于 d i d_i di均为零,因此记录一个 c n t cnt cnt为当前不为零的个数,动态的维护一下即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, q;
int a[N], b[N], c[N], d[N];
int cnt;
int f[N];
void modify(int v, LL x) {
bool ok = false;
if (d[v] == 0) ok = true;
d[v] = ((d[v] + x) % m + m) % m;
if (ok && d[v]) cnt ++;
else if (!ok && !d[v]) cnt --;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> q >> m;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++) cin >> b[i];
for (int i = 1; i <= n; i ++) c[i] = a[i] - b[i];
d[1] = c[1], d[2] = c[2] - c[1];
for (int i = 3; i <= n; i ++) d[i] = ((c[i] - c[i - 1] - c[i - 2]) % m + m) % m;
for (int i = 1; i <= n; i ++)
if (d[i]) cnt ++;
f[1] = 1;
for (int i = 2; i < N; i ++) f[i] = ((LL)f[i] + f[i - 1] + f[i - 2]) % m;
while (q --) {
char c[2];
int l, r;
cin >> c >> l >> r;
if (*c == 'A') {
modify(l, 1);
if (r + 1 <= n) modify(r + 1, -(f[r - l + 1] + f[r - l]));
if (r + 2 <= n) modify(r + 2, -f[r - l + 1]);
}
else {
modify(l, -1);
if (r + 1 <= n) modify(r + 1, f[r - l + 1] + f[r - l]);
if (r + 2 <= n) modify(r + 2, f[r - l + 1]);
}
cout << (cnt ? "No" : "Yes") << '\n';
}
return 0;
}
最大公约数
[Link](最大公约数 - 题目 - Daimayuan Online Judge)
思路
- 枚举,数论
我们发现顺着推不方便,是否有另一种方面的暴力一点的 c h e c k check check答案的方法? g c d = d gcd=d gcd=d分成了 k k k段,则 a 1 d + a 2 d + . . . + a k d = s u m → ( a 1 + a 2 + . . . + a k ) d = s u m a_1d+a_2d+...+a_kd=sum\to(a_1+a_2+...+a_k)d=sum a1d+a2d+...+akd=sum→(a1+a2+...+ak)d=sum,发现我们的 g c d gcd gcd一定总和的约数,然后约数的个数是很少的,因此我们可以直接枚举约数,判断当前约数,可以将这个数组分成多少份。
由于是 g c d gcd gcd因此能分成 k k k份的 d d d一定能分成 k − 1 k-1 k−1,对于分成 k k k份的最大的 d d d我们记录一个后缀最大值即可。0
对于一个环因为我们枚举的是总和的约数,所以我们只需要记录出现最多的 m o d d mod \ d mod d的约数即可,因为 s i ≡ s j ( m o d d ) s_i\equiv s_j (mod\ d) si≡sj(mod d),那么 s j − s i ≡ 0 ( m o d d ) s_j-s_i\equiv 0(mod\ d) sj−si≡0(mod d),由于是环且枚举总和的约数所以最后首尾一定可以合成一个。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
#define int long long
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], s[N];
int res[N];
void g(LL x) {
map<int, int> mp;
int mx = 0;
for (int i = 1; i <= n; i ++) {
mp[s[i] % x] ++;
mx = max(mx, mp[s[i] % x]);
}
res[mx] = max((LL)res[mx], x);
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++)
s[i] = a[i] + s[i - 1];
for (LL i = 1; i <= s[n] / i; i ++) {
if (s[n] % i == 0)
g(i), g(s[n] / i);
}
for (int i = n; i; i --)
res[i] = max(res[i], res[i + 1]);
for (int i = 1; i <= n; i ++)
cout << res[i] << '\n';
}
平方计数
[Link](平方计数 - 题目 - Daimayuan Online Judge)
思路
- 调和级数
暴力肯定不行,化简一下式子 a i 2 + a j = k 2 → a j = ( k + a i ) ( k − a i ) a_i^2+a_j=k^2\to a_j=(k+a_i)(k-a_i) ai2+aj=k2→aj=(k+ai)(k−ai),因此等价于找对于一个 a j a_j aj有多少个数满足是 a j a_j aj一对约数差的一半,对于每个数直接枚举约数 O ( n n ) O(n\sqrt n) O(nn)不太行,我们可以采用一个常用的倍数遍历小技巧,利用调和级数直接枚举每个约数和构成的数,然后乘法原理算一下贡献。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int cnt[1000010];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1, x; i <= n; i ++)
cin >> x, cnt[x] ++;
int res = 0;
for (int i = 1; i <= 1000000; i ++)
for (int j = i; j <= 1000000; j += i) {
int l = i, r = j / i;
int d = abs(l - r);
if (d % 2 == 0) {
res += cnt[d / 2] * cnt[j];
}
}
cout << res / 2 << '\n';
return 0;
}
字典序最小
[Link](字典序最小 - 题目 - Daimayuan Online Judge)
思路
- 贪心
我们可以用一个栈动态的来维护这个序列,因为要字典序最小,仿照单调栈的思想,每进来一个数就看看能不能将前面比它小的数弹走,但是由于要让我们选够 n n n个数,即每个数都需要选一次,因此我们还要看一下如果将当前这个弹走,后面是否还会有这个数,这个记录一下每个数最后在哪出现即可。然后这样贪心的维护即可,因为前面具有决定性,因此能让前面小就让前面小,贪心是对的。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
vector<int> last(m + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
last[a[i]] = i;
}
vector<int> st(m + 1);
stack<int> stk;
for (int i = 1; i <= n; i ++) {
if (!st[a[i]]) {
while (stk.size() && stk.top() > a[i] && last[stk.top()] > i) {
st[stk.top()] = false;
stk.pop();
}
stk.push(a[i]), st[a[i]] = true;
}
}
while (stk.size() > m) stk.pop();
vector<int> res;
while (stk.size()) res.push_back(stk.top()), stk.pop();
reverse(res.begin(), res.end());
for (int i = 0; i < res.size(); i ++)
cout << res[i] << " \n"[i == res.size() - 1];
return 0;
}
拆拆
[Link](拆拆 - 题目 - Daimayuan Online Judge)
思路
- 组合数学,分解质因数
考虑到拼凑,一般会想到算数基本定理,我们可以将 x x x分解成一系列的质数次幂的乘积,要求本质不同的问题,问题转化为我们有很多组小球,要将每一组小球放到 y y y个箱子里且可以放空的方案数,且每组之间符合乘法原理。
对于一组小球放到 y y y个箱子里不可以放空等价于, x 1 + x 2 + x 3 . . . + x y = s u m x_1+x_2+x_3...+x_y=sum x1+x2+x3...+xy=sum,相当于将总和分成 y y y份,根据隔板法方案为 C s u m − 1 y − 1 C_{sum-1}^{y-1} Csum−1y−1,如果可以防空,我们左右各加 y y y,等价于 x 1 + 1 + x 2 + 1 + x 3 + 1 + . . . + x y + 1 = s u m + y x_1+1+x_2+1+x_3+1+...+x_y+1=sum+y x1+1+x2+1+x3+1+...+xy+1=sum+y,这个时候就等价于 s u m + y sum+y sum+y个分 y y y份且不放空的情况,即 C s u m + y − 1 y − 1 C_{sum+y-1}^{y-1} Csum+y−1y−1。
对于正负的限制, y y y个数每个可正可负,一共 2 y 2^y 2y种方案,我们要的是正的,正负对半分因此再乘个 2 y − 1 2^{y-1} 2y−1。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = N * 20, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int fact[M], infact[M];
int qmi(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (LL) res * a % mod;
a = (LL) a * a % mod;
b >>= 1;
}
return res;
}
int C(int n, int m) {
if (n < m) return 0;
return (LL)fact[n] * infact[m] % mod * infact[n - m] % mod;
}
vector<PII> g(int x) {
vector<PII> res;
for (int i = 2; i <= x / i; i ++)
if (x % i == 0) {
int cnt = 0;
while (x % i == 0) x /= i, cnt ++;
res.push_back({i, cnt});
}
if (x != 1) res.push_back({x, 1});
return res;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
fact[0] = 1;
for (int i = 1; i < M; i ++) fact[i] = (LL)fact[i - 1] * i % mod;
infact[M - 1] = qmi(fact[M - 1], mod - 2);
for (int i = M - 2; i >= 0; i --) infact[i] = (LL)infact[i + 1] * (i + 1) % mod;
int T;
cin >> T;
while (T -- ) {
cin >> n >> m;
vector<PII> ve = g(n);
LL res = 1;
for (auto t : ve)
res = res * C(t.y + m - 1, m - 1) % mod;
res = res * qmi(2, m - 1) % mod;
cout << res << '\n';
}
return 0;
}
“Z”型矩阵
思路
- 树状数组
如果枚举左上角和长度怎么样至少都是 O ( n 3 ) O(n^3) O(n3)的。因此可以考虑枚举对角线,我们设 L i , j , R i , j L_{i,j},R_{i,j} Li,j,Ri,j分别 ( i , j ) (i,j) (i,j)向左向右连续的最长 z z z有多少。
暴力枚举所有的对角线,由于题目限制,对于某个斜线的某一段是连续的 z z z才可能产生贡献,对于当前单独的一个斜线怎么计算贡献呢,首先将斜线上的点按 1 ∼ c n t 1\sim cnt 1∼cnt编号,对于第 i i i个点我们需要的是能于 R i R_i Ri产生贡献的,即 [ m a x ( 1 , i − R i + 1 ) , i ] [max(1,i-R_i+1),i] [max(1,i−Ri+1),i]这些点里有多少 L j L_j Lj是大于等于他们之间斜线距离的,由于斜线的长度固定,我们 L , R L,R L,R的长度也会固定,因此我们关注的就是查的这个斜线的某个区间有多少个合法的点,区间合法点可以用树状数组来搞,具体来说,对于一个点 i i i它有效的区间为 [ i , m i n ( c n t , i + L i − 1 ) ] [i,min(cnt,i+L_i-1)] [i,min(cnt,i+Li−1)](再长斜线就会超过向左延申的长度了),因此我们可以记录一下每个到哪就不合法,动态的加入删除查询来统计贡献即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define int long long
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int n, m, k;
int a[N];
char g[3010][3010];
int L[3010][3010], R[3010][3010];
vector<PII> Line(3010);
int cnt, res;
int tr[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int v) {
for (; x <= cnt; x += lowbit(x)) tr[x] += v;
}
int sum(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
int query(int l, int r) {
if (l > r) return 0;
return sum(r) - sum(l - 1);
}
void calc() {
vector<int> G[cnt + 1];
for (int i = 1; i <= cnt; i ++) {
int l = Line[i].x, r = Line[i].y;
add(i, 1);
res += query(max(1ll, i - r + 1), i);
G[min(cnt, i + l - 1)].push_back(i);
for (auto t : G[i]) add(t, -1);
}
for (int i = 1; i <= cnt; i ++) tr[i] = 0;
cnt = 0;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> g[i][j];
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
if (g[i][j] == 'z') L[i][j] = L[i][j - 1] + 1;
for (int i = 1; i <= n; i ++)
for (int j = m; j; j --)
if (g[i][j] == 'z') R[i][j] = R[i][j + 1] + 1;
for (int k = 2; k <= n + m; k ++) {
int x = max(1ll, k - m), y = k - x;
while (true) {
if (g[x][y] == 'z') Line[++cnt] = {L[x][y], R[x][y]};
else if (cnt) calc();
x ++, y --;
if (x > min(n, k - 1)) break;
}
if (cnt) calc();
}
cout << res << '\n';
return 0;
}
好序列
[Link](好序列 - 题目 - Daimayuan Online Judge)
思路
- 启发式合并
暴力来看怎么判断某个区间某个数是否唯一出现呢?
我们记录
p
r
e
[
i
]
pre[i]
pre[i]表示
a
i
a_i
ai上一次出现的位置,
n
x
[
i
]
nx[i]
nx[i]表示
a
i
a_i
ai下一次出现的位置,如果
p
r
e
i
<
L
&
&
n
x
i
>
R
pre_i<L\&\& nx_i>R
prei<L&&nxi>R则发现这个数是唯一出现的。
如果某个数在某个区间 [ l , r ] [l,r] [l,r]里唯一出现,那么含这个数的 [ l , r ] [l,r] [l,r]的子区间一定合法,因此等价这个数将 [ l , r ] [l,r] [l,r]分成两半,设这个数位于 x x x,则只需判断 [ l , x − 1 ] , [ x + 1 , r ] [l,x-1],[x+1,r] [l,x−1],[x+1,r]这两个子区间是否合法即可,这个可以递归来搞。
这样复杂度还是 O ( n 2 ) O(n^2) O(n2),但是我们可以优化一下从两边往中间来枚举这个数是否唯一存在,这样就类似我们启发式合并里,将小的合并到大的这样每个数最多合并 l o g ( n ) log(n) log(n)次,这里我们们相当于每次都操作一个少的一边,是等价的,所以复杂度降下来了。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int pre[N], nx[N];
int a[N];
bool split(int l, int r) {
if (l >= r) return true;
int x = l, y = r;
while (x <= y) {
if (pre[x] < l && nx[x] > r) return split(l, x - 1) && split(x + 1, r);
if (pre[y] < l && nx[y] > r) return split(l, y - 1) && split(y + 1, r);
x ++, y --;
}
return false;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i], pre[i] = 0, nx[i] = n + 1;
map<int, int> mp;
for (int i = 1; i <= n; i ++) {
pre[i] = mp[a[i]];
nx[mp[a[i]]] = i;
mp[a[i]] = i;
}
if (split(1, n)) cout << "non-boring\n";
else cout << "boring\n";
}
return 0;
}