代码源每日一题 div1 (301-307)
连续子序列
[Link](连续子序列 - 题目 - Daimayuan Online Judge)
思路
- d p dp dp
暴力的来设 f i : 以 i 结 尾 的 最 长 子 序 列 f_i:以i结尾的最长子序列 fi:以i结尾的最长子序列,对于一个 a i a_i ai我们想看 f a i − 1 f_{a_i-1} fai−1是否存在,存在就接到后面,设最长为 m x mx mx,我们 d p dp dp完从前往后遍历 f f f,对于第一个 f i = m x f_i=mx fi=mx的位置一定是字典序最小的。由于值域太大我们不可能开一个数组来存,因此开个 m a p map map来搞一下即可。
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 a[N];
map<int, int> f;
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
int mx = 0;
for (int i = 1; i <= n; i ++) {
int x; cin >> x;
f[x] = f[x - 1] + 1;
mx = max(mx, f[x]);
}
for (auto it : f)
if (it.second == mx) {
cout << mx << '\n';
for (int i = it.first - mx + 1; i <= it.first; i ++)
cout << i << ' ';
cout << '\n';
return 0;
}
return 0;
}
工作安排
[Link](工作安排 - 题目 - Daimayuan Online Judge)
思路
- 贪心
将任务按时间排序,对于每个任务,如果当前时间不够,若前面某个点的收益小于当前点的收益,我们就将用当前点把那个点替换掉即可,维护前面选的权值最小的值可以用一个优先队列来做。
小根堆要重载大于号。
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;
PII a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i].y >> a[i].x;
sort(a + 1, a + 1 + n,[&](PII a, PII b) {
return a.y < b.y;
});
priority_queue<PII, vector<PII>, greater<PII>> pq;
for (int i = 1; i <= n; i ++) {
if (a[i].y <= 0) continue ;
if (pq.size() < a[i].y) pq.push(a[i]);
else if (pq.top().x < a[i].x) {
pq.pop();
pq.push(a[i]);
}
}
LL res = 0;
while (pq.size()) {
res += pq.top().first;
pq.pop();
}
cout << res << '\n';
return 0;
}
三角果计数
[Link](三角果计数 - 题目 - Daimayuan Online Judge)
思路
- 树形 d p dp dp
发现当三个点在树的一条路径上无解,其他情况均有解,由于不在一条路径上因此必然存在一个中间结点分别与这些点相连,假设这个点和其他三个点相连的边权分别为 a , b , c a,b,c a,b,c,则 a + b + b + c > a + c a+b+b+c>a+c a+b+b+c>a+c,证明与权值路径权值无关了。
接下来考虑如何计数,我们可以这样来划分方案以点 u u u为中间点的方案数,这样的方案数由两部分构成:
- u u u的两个不同的子树各选一个,非 u u u的子树里选一个这样是不会构成一条路径的
- u u u的三个不同的子树各选一个,这样也不会构成一条路径
设 s z [ u ] : 以 u 为 根 的 子 树 的 节 点 数 sz[u]:以u为根的子树的节点数 sz[u]:以u为根的子树的节点数,对于方案一我们要求 非 u 子 树 节 点 数 × ∑ i = 1 n ∑ j = i + 1 n s z [ i ] × s z [ j ] 非u子树节点数\times\sum_{i=1}^n\sum_{j=i+1}^nsz[i]\times sz[j] 非u子树节点数×∑i=1n∑j=i+1nsz[i]×sz[j],后面的连成可以优化成 O ( n ) O(n) O(n),因为 ( a + b ) 2 = a 2 + b 2 + 2 a b → a b = ( a + b ) 2 − ( a 2 + b 2 ) 2 (a+b)^2=a^2+b^2+2ab\to ab = \frac{(a+b)^2-(a^2+b^2)}{2} (a+b)2=a2+b2+2ab→ab=2(a+b)2−(a2+b2),推广到 n n n个变量,我们可以 O ( n ) O(n) O(n)的求 ( a + b ) 2 (a+b)^2 (a+b)2和 a 2 + b 2 a^2+b^2 a2+b2,同理方案二我们要求 ∑ i = 1 i = n ∑ j = i + 1 j = n ∑ k = j + 1 k = n s z [ i ] × s z [ j ] × s z [ k ] \sum_{i=1}^{i=n}\sum_{j=i+1}^{j=n}\sum_{k=j+1}^{k=n}sz[i]\times sz[j]\times sz[k] ∑i=1i=n∑j=i+1j=n∑k=j+1k=nsz[i]×sz[j]×sz[k],这个通过式子也可以变形到 O ( n ) O(n) O(n),因此直接一个 d f s dfs dfs即可。
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 a[N];
LL sz[N];
LL res;
LL clac1(vector<int>&ve, int u) {
if (ve.size() < 2) return 0;
LL sum1 = 0, sum2 = 0;
for (auto v : ve) sum1 += sz[v], sum2 += sz[v] * sz[v];
return (LL)(n - sz[u]) * (sum1 * sum1 - sum2) / 2;
}
LL clac2(vector<int>&ve) {
if (ve.size() < 3) return 0;
LL sum1 = 0, sum2 = 0, sum = 0;
for (auto v : ve) sum += sz[v];
for (auto v : ve) sum1 += sz[v] * sz[v] * sz[v], sum2 += 3 * sz[v] * sz[v] * (sum - sz[v]);
return (sum * sum * sum - sum1 - sum2) / 6;
}
void dfs(int u, int fa) {
vector<int> ve;
sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue ;
dfs(j, u);
sz[u] += sz[j];
ve.push_back(j);
}
res += clac1(ve, u);
res += clac2(ve);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i ++) {
int x, y, z; cin >> x >> y >> z;
add(x, y, z), add(y, x, z);
}
dfs(1, -1);
cout << res << '\n';
return 0;
}
- 数学
如果将当前点 u u u去除掉我们可以得到好几颗子树,问题转化为从这些树中选三棵树每个每棵树选一个结点,设每个树的节点数为 s z [ i ] sz[i] sz[i],等价于要求 ∑ i = 1 i = n ∑ j = i + 1 j = n ∑ k = j + 1 k = n s z [ i ] × s z [ j ] × s z [ k ] \sum_{i=1}^{i=n}\sum_{j=i+1}^{j=n}\sum_{k=j+1}^{k=n}sz[i]\times sz[j]\times sz[k] ∑i=1i=n∑j=i+1j=n∑k=j+1k=nsz[i]×sz[j]×sz[k],优化一下这个式子,我们可以枚举中间点 j j j对于每一个 j j j我们等价于求 ( ∑ i = 1 i = j − 1 s z [ i ] ) × s z [ j ] × ( ∑ k = j + 1 k = n s z [ k ] ) (\sum_{i=1}^{i=j-1}sz[i])\times sz[j]\times (\sum_{k=j+1}^{k=n}sz[k]) (∑i=1i=j−1sz[i])×sz[j]×(∑k=j+1k=nsz[k]),也就是我们可以枚举其中一个点,然后保证这个点前面的递增,后面的递减即可,也就是等价于我们求到了 u u u这个点对于已经出现的点作为左半部分,当前枚举的作为 j j j,未枚举到的作为右半部分,因此就可以 O ( n ) O(n) O(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 = 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 a[N];
LL sz[N];
LL res;
void dfs(int u, int fa) {
sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue ;
dfs(j, u);
res += (sz[u] - 1) * sz[j] * (n - sz[u] - sz[j]);
sz[u] += sz[j];
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i < n; i ++) {
int x, y, z; cin >> x >> y >> z;
add(x, y), add(y, x);
}
dfs(1, -1);
cout << res << '\n';
return 0;
}
整齐的数组2
[Link](整齐的数组2 - 题目 - Daimayuan Online Judge)
思路
- 枚举,数论
首先给原数组排个序,如果有大于等于一般的数都相同就是无解。
首先如果给你一个 k k k,怎么判断是否和法,对于两个数 a i + x k = a j + y k → a i ≡ a j ( m o d k ) a_i+xk=a_j+yk\to a_i\equiv a_j(mod\ k) ai+xk=aj+yk→ai≡aj(mod k),因此可以开一个 m a p map map,统计出这 n n n个数 m o d k mod\ k mod k的值然后遍历 m a p map map看是否存在 ≥ n / 2 \ge n/2 ≥n/2的值。
那么怎么枚举我们的 k k k呢,由于我们要让一半的数相同,假设 a i > a j a_i>a_j ai>aj那么当 a i a_i ai减到等于 a j a_j aj的时候就不需要再减了,设 d = a i − a j d=a_i-a_j d=ai−aj,我们可以枚举 d d d的所有约数,对于大于 d d d的数不可能让他俩相同所以没必要枚举,对于非 d d d的约数 a i a_i ai一定不可能减到 a j a_j aj我们也不需要枚举了。假设最优解为 k 1 k_1 k1,那么一定是某个 a i a_i ai变到了 a j a_j aj因此一定可以枚举到。
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 a[N];
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
bool check(int x) {
map<int, int> mp;
for (int i = 1; i <= n; i ++) mp[(a[i] % x + x) % x] ++;
for (auto it : mp)
if (it.second >= n / 2) return true;
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];
sort(a + 1, a + 1 + n);
bool ok = false;
for(int i = 1; i <= n; i ++) {
int j = i;
while (j <= n && a[j] == a[i]) j ++;
if (j - i >= n / 2) {
ok = true;
break;
}
i = j - 1;
}
if (ok) {
cout << -1 << '\n';
continue ;
}
int res = 1;
for (int i = 1; i <= n; i ++)
for (int j = i + 1; j <= n; j ++) {
int d = abs(a[j] - a[i]);
for (int k = 1; k <= d / k; k ++) {
if (d % k == 0) {
if (check(k)) res = max(res, k);
if (check(d / k)) res = max(res, d / k);
}
}
}
cout << res << '\n';
}
return 0;
}
三进制循环
思路
- 树形 d p dp dp
处理出来以每个点为子树的顺序和逆序的最长长度,然后 d p dp dp判断当前点和父节点的关系,找到当前点出去和进来的最长长度相加即可。
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 = 5e5 + 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 d[N], p[N];
void dfs(int u, int fa) {
d[u] = p[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue ;
dfs(j, u);
if ((a[j] + 1) % 3 == a[u]) p[u] = max(p[u], p[j] + 1);
if ((a[u] + 1) % 3 == a[j]) d[u] = max(d[u], d[j] + 1);
}
}
int res;
void dp(int u, int fa) {
if (fa == -1) res = max(res, d[u] + p[u] - 1);
else if ((a[u] + 1) % 3 == a[fa]) res = max(res, p[u] + max(d[u], d[fa] + 1) - 1);
else if ((a[fa] + 1) % 3 == a[u]) res = max(res, d[u] + max(p[u], p[fa] + 1) - 1);
else res = max(res, d[u] + p[u] - 1);
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue ;
dp(j, u);
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i ++) {
int x, y; cin >> x >> y;
add(x, y), add(y, x);
}
for (int i = 1; i <= n; i ++) cin >> a[i];
dfs(1, -1);
dp(1, -1);
cout << res << '\n';
return 0;
}
树上逆序对
[Link](树上逆序对 - 题目 - Daimayuan Online Judge)
思路
- 离线,树状数组
很显然模拟是行不通的,我们可以找出 k k k叉树的时候每个结点的情况对于结点 i i i它的子树对应的结点区间为 [ k ( i − 1 ) , k i + 1 ] [k(i-1),ki+1] [k(i−1),ki+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 = 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 a[N];
PII b[N];
int res[N];
struct Node {
int l, r, h, id;
bool operator<(Node t)const {
return h < t.h;
}
}q[4000000];
int tr[N];
int lowbit(int x) {
return x & -x;
}
void add(int x) {
for (; x <= n; x += lowbit(x)) tr[x] ++;
}
int sum(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i], b[i] = {a[i], i};
sort(b + 1, b + 1 + n);
for (int k = 1; k < n; k ++) {
for (int i = 1; ; i ++) {
int l = (i - 1) * k + 2, r = min(n, i * k + 1);
if (l > n) break;
q[++m] = {l, r, a[i], k};
}
}
sort(q + 1, q + m + 1);
int pos = 1;
for (int i = 1; i <= m; i ++) {
while (pos <= n && q[i].h > b[pos].x) add(b[pos ++].y);
res[q[i].id] += sum(q[i].r) - sum(q[i].l - 1);
}
for (int i = 1; i < n; i ++)
cout << res[i] << ' ';
return 0;
}
约分
[link](约分 - 题目 - Daimayuan Online Judge)
思路
二进制暴力枚举分子删除哪些位,直接根据最简比搞出对应的分母,判断当前分母是否合法,即是否符合删除次数,是否可以由原数删除得到。
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;
LL a, b;
vector<int> get(LL x) {
vector<int> res;
while (x) {
res.push_back(x % 10);
x /= 10;
}
return res;
}
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
int cnt[10];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> a >> b;
vector<int> numa = get(a), numb = get(b);
LL d =gcd(a, b), p = a / d, q = b / d;
for (int j = 1; j < 1 << numa.size(); j ++) {
for (int i = 0; i < 10; i ++) cnt[i] = 0;
LL fz = 0, fm;
for (int i = numa.size() - 1; i >= 0; i --)
if (j >> i & 1) fz = fz * 10 + numa[i];
else cnt[numa[i]] ++;
if (!fz || fz % p) continue ;
fm = fz / p * q;
for (int i = 0; i < numb.size(); i ++)
if (fm % 10 == numb[i]) fm /= 10;
else cnt[numb[i]] --;
bool ok = false;
for (int i = 0; i < 10; i ++)
if (cnt[i]) {
ok = true;
break;
}
if (ok) continue ;
if (a > fz) a = fz, b = fz / p * q;
}
cout << a << ' ' << b << '\n';
return 0;
}