代码源每日一题 div1 (501-507)
社交圈
[link](社交圈 - 题目 - Daimayuan Online Judge)
思路
- 贪心
我们的思想是尽量让 L L L和 R R R最多的重复起来,这样我们付出的代价最少,最贪心的想法即将 L , R L,R L,R分别排个序对应匹配,那这样会有形成合法的解吗,答案是一定的,我们可以从某个点开始往匹配的点连边,一直连到这次最初的点的 L L L,这样就会形成一个环,类似于置换,我们一定可以形成无数个环。
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 L[N], R[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++)
cin >> L[i] >> R[i];
sort(L + 1, L + 1 + n), sort(R + 1, R + 1 + n);
LL res = n;
for (int i = 1; i <= n; i ++) res += max(L[i], R[i]);
cout << res << '\n';
return 0;
}
区间和
[Link](区间和 - 题目 - Daimayuan Online Judge)
思路
- 并查集
我们知道 s 0 = 0 s_0=0 s0=0,给定一个区间 [ l , r ] [l,r] [l,r]我们相当于知道了 s r − s l − 1 s_r-s_{l-1} sr−sl−1,知道了两点之间的关系,这些都是变量,但是 s 0 s_0 s0是已知的,所以如果最终 s n s_n sn和 s 0 s_0 s0存在连接关系就一定可以推出来 s n s_n sn的值,因此直接用并查集搞一下,最后判断联通性即可。
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];
int f[N];
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++) f[i] = i;
while (m --) {
int x, y; cin >> x >> y;
x --;
f[find(x)] = find(y);
}
cout << (find(0) == find(n) ? "Yes" : "No") << '\n';
return 0;
}
选数2
[Link](选数2 - 题目 - Daimayuan Online Judge)
思路
- 贪心,二分
将限制转化为数学式子,设一共 n n n个数,最大值为 m x mx mx,和为 s u m sum sum,则我们需要满足 m x ≤ s u m p n q → n q m x ≤ p s u m mx\le \frac{sump}{nq}\to nqmx\le psum mx≤nqsump→nqmx≤psum。在 m x mx mx不变的情况下,每多一个数 n n n的变化是恒定的,贪心来想我们想让 s u m sum sum尽量大这样才可以选更多的数,因此应该选小于 m x mx mx的最大的那些数。
所以将所有的数从小到大排序,这样选出的成立的那些数一定是连续的一个区间,对于最多选多少个数具有二段性,我们可以二分判断是否成立,即判断是否有 ≥ x \ge x ≥x的区间满足上面的式子。
假设最多选 m m m个数,我们从前往后遍历每个长度为 m m m的区间,如果当前这个区间合法,我们想判断至少有多少个数不被选,因此我们倾向于这个区间某个数换成前面的数是否不等式还成立,因为要保证 s u m sum sum即可能的大,假设区间为 [ l , r ] [l,r] [l,r]我们将 [ l ] [l] [l]换成前面的某个数是最优的,假设换成 a j a_j aj成立那么 a j + 1 a_{j+1} aj+1一定也成立( s u m sum sum更大了,因此必定成立),所以有二段性,我们可以二分向前最多替换到哪,假设为 j j j,那么 [ j , r ] [j,r] [j,r]这个区间的数都是可选的,我们给它打个标记表示成立,区间打标记可以差分一下,直接搞,最后遍历一遍,判断那个没被打标记即可。
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 = 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;
LL p, q;
PII a[N];
int s[N];
int b[N];
bool check(int x) {
for (int i = x; i <= n; i ++)
if (q * a[i].x * x <= (s[i] - s[i - x]) * p) return true;
return false;
}
bool calc(int mx, int sum) {
return mx * q * m <= sum * p;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i].x, a[i].y = i;
cin >> p >> q;
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i].x;
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
m = l;
vector<int> res;
for (int i = m; i <= n; i ++)
if (calc(a[i].x, s[i] - s[i - m])) {
int sum = s[i] - s[i - m + 1];
int l = 1, r = i - m;
while (l < r) {
int mid = l + r >> 1;
if (calc(a[i].x, sum + a[mid].x)) r = mid;
else l = mid + 1;
}
b[l] ++, b[i + 1] --;
}
for (int i = 1; i <= n; i ++) {
b[i] += b[i - 1];
if (b[i] <= 0)
res.push_back(a[i].y);
}
sort(res.begin(), res.end());
cout << res.size() << '\n';
for (auto t : res)
cout << t << ' ';
cout << '\n';
return 0;
}
数组划分
[Link](数组划分 - 题目 - Daimayuan Online Judge)
思路
- 贪心, d p dp dp
典中典的与操作,从高位往低位贪心,看当前这一位是否可以为 1 1 1,求最大值,高位具有决定性,因此高位能为 1 1 1就为 1 1 1一定更优。
问题转化为我们前面贪出来了一些位的值,假设为 r e s res res,当前在第 i i i位,我们想知道这一位是否可以取一,即 r e s ∣ = 1 < < i res|=1<<i res∣=1<<i是否可以恰好分成 k k k个子数组且和的与是 r e s res res。
对于检查这个事我们发现,假设是分成了 k k k段,每段的和为 s i s_i si,则任意一段 s i & r e s = r e s s_i\&res=res si&res=res。这个玩意是没法贪心分的,因为没有最优的前驱,因此考虑 d p dp dp,我们设 f [ i ] [ j ] : 前 i 个 数 分 了 j 段 是 否 合 , f a l s e : 不 合 法 , t r u e : 合 法 f[i][j]:前i个数分了j段是否合,false:不合法,true:合法 f[i][j]:前i个数分了j段是否合,false:不合法,true:合法,设 s i = ∑ j = 1 j = i a i s_i=\sum_{j=1}^{j=i}a_i si=∑j=1j=iai,考虑转移对于第 i i i个数如果要和前面某个 j j j构成一段要满足 ( s i − s j − 1 ) & r e s = r e s (s_i-s_{j-1})\& res=res (si−sj−1)&res=res,然后枚举决策,当前这个这一段是 f i f_i fi的第几段,转移即可,最后判断一下 f n , k f_{n,k} fn,k是否为 t r u e true true即可。
从前往后贪心,确定每一位。
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[N];
bool check(LL x) {
vector<vector<int>> f(n + 1, vector<int>(k + 1));
f[0][0] = 1;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= i; j ++)
if (((a[i] - a[j - 1]) & x) == x)
for (int len = 1; len <= k; len ++)
f[i][len] |= f[j - 1][len - 1];
return f[n][k];
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i ++) cin >> a[i], a[i] += a[i - 1];
LL res = 0;
for (LL i = 60; i >= 0; i --) {
res |= (1ll << i);
if (!check(res)) res ^= (1ll << i);
}
cout << res << '\n';
return 0;
}
namomo
[Link](namonamo - 题目 - Daimayuan Online Judge)
思路
- 折半搜索
我们有个暴力的想法直接爆搜出来每个人的字符串然后判断是否相等即可,但是 O ( 2 40 ) O(2^{40}) O(240)肯定是不行的。
优化一下我们的暴力,很多时候有个直观的想法就是将乘法变成加法,我们可以分别枚举出来前一半的选法,和后一半的选法,可以这样做的一个很重要的原因是前后独立且可拼接。那么前一半我们可以得到很多对字符串,因为最后要拼起来相同,所以这一对字符串一定要满足前缀相同,直到某个不够长了,将前一半合法的多出来的用 s e t set set存下了,后一半同理我们要让后缀相同,否则不合法,对于合法的我们从 s e t set set里查一下是否存在这个即可。
这样复杂度就砍了一半左右。
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];
set<string> sa, sb;
string str;
void dfs_1(int u, int p, string a, string b) {
if (u > p) {
for (int i = 0; i < min(a.size(), b.size()); i ++)
if (a[i] != b[i]) return ;
if (a.size() > b.size()) sa.insert(a.substr(b.size()));
else if (a.size() < b.size()) sb.insert(b.substr(a.size()));
else sa.insert(""), sb.insert("");
return ;
}
dfs_1(u + 1, p, a + str[u], b);
dfs_1(u + 1, p, a, b + str[u]);
}
bool ok = false;
void dfs_2(int u, int p, string a, string b) {
if (ok) return ;
if (u > p) {
reverse(a.begin(), a.end()), reverse(b.begin(), b.end());
for (int i = 0; i < min(a.size(), b.size()); i ++)
if (a[i] != b[i]) return ;
if (a.size() > b.size()) {
string t = a.substr(b.size());
reverse(t.begin(), t.end());
if (sb.find(t) != sb.end()) ok = true;
}
else if (a.size() < b.size()){
string t = b.substr(a.size());
reverse(t.begin(), t.end());
if (sa.find(t) != sa.end()) ok = true;
}
else if (sa.count("") || sb.count("")) ok = true;
return ;
}
dfs_2(u + 1, p, a + str[u], b);
dfs_2(u + 1, p, a, b + str[u]);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
ok = false;
cin >> str;
n = str.size();
str = " " + str;
sa.clear(), sb.clear();
dfs_1(1, n / 2, "", "");
dfs_2(n / 2 + 1, n, "", "");
cout << (ok ? "possible" : "impossible") << '\n';
}
return 0;
}
体育节
[Link](体育节 - 题目 - Daimayuan Online Judge)
思路
- 区间 d p dp dp
假设当前最大 m x mx mx,最小 m n mn mn,下一次我们一定是选了一个 ≥ m x 或 ≤ m n \ge mx或\le mn ≥mx或≤mn的,如果选了一个 m n ≤ x ≤ m x mn\le x\le mx mn≤x≤mx,那么我们在这之前某一次选这个数一定更优,因此将原数组排个序。
但是最开始从哪个位置开始选和每一次选更大的还更小的我们是不好贪心的,但是每一次要不就是来了个小的要不是就是来了个大的,转移关系很明了,因此可以写个区间 d p dp dp,对于长为 l e n len len要不就是由上一个状态左边加了一个,要不就是右边加了一个,直接转移即可。
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 f[3010][3010];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
sort(a + 1, a + 1 + n);
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i ++) f[i][i] = 0;
for (int len = 2; len <= n; len ++)
for (int i = 1; i + len - 1 <= n; i ++) {
int j = i + len - 1;
f[i][j] = min(f[i][j], a[j] - a[i] + min(f[i + 1][j], f[i][j - 1]));
}
cout << f[1][n] << '\n';
return 0;
}
测温
[Link](测温 - 题目 - Daimayuan Online Judge)
思路
考虑 f [ i ] [ j ] : 第 i 天 温 度 为 j 的 最 大 值 f[i][j]:第i天温度为j的最大值 f[i][j]:第i天温度为j的最大值,那么 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] + 1 ) k ≤ j f[i][j]=max(f[i-1][k]+1) \ k\le j f[i][j]=max(f[i−1][k]+1) k≤j,这样 d p dp dp复杂度是 O ( n w ) O(nw) O(nw)还是无法接受的,观察一下 f [ i ] f[i] f[i]的性质发现他是单调的即 f [ i ] [ j + 1 ] ≥ f [ i ] [ j ] f[i][j +1]\ge f[i][j] f[i][j+1]≥f[i][j](温度高的一定可以接到温度低的接到的地,要不就是接到一个更优的前面)。
因此 f [ i ] [ j ] f[i][j] f[i][j]的值一定是一段一段且非递减的例如 f [ i ] [ 1 ∼ 3 ] = 4 , f [ i ] [ 4 ∼ 6 ] = 5 , f [ i ] [ 7 ∼ 10 ] = 6 f[i][1\sim 3] =4,f[i][4\sim6]=5,f[i][7\sim10] = 6 f[i][1∼3]=4,f[i][4∼6]=5,f[i][7∼10]=6,因此我们可以维护上一个状态的区间而不是维护具体的点,这个可以用一个 d e q u e deque deque来搞,对于新的状态的区间 [ l i + 1 , r i + 1 ] [l_{i+1},r_{i+1}] [li+1,ri+1]就从前一个状态合法的区间转移过来即可。
例如 [ l i + 1 , r i + 1 ] [l_{i+1},r_{i+1}] [li+1,ri+1]为 [ 5 , 8 ] [5, 8] [5,8],我们的思路是:
首先如果新的区间左端点比维护的左端点更小就加入多的这一段且权值为 1 1 1
如果右端点比维护右端点更大,我们就把维护右端点置成新区间的
r
r
r,因为这个
r
r
r更大所以一定可以接到前一个区间的最右面后面(就是转移时合法的)
然后如果最左边的区间的右端点小于我们的新区间的
l
l
l,我们就把它弹出去(即不合法无法转移)
同理最右边的区间的左端点如果大于我们新区间的 r r r,也弹出去。
转移过来的区间都要加上 1 1 1,对于区间加我们可以维护一个 t a g tag tag表示这个区间加了多少次,对于左边那个新加入的区间我们让他等于 − t a g -tag −tag相当于减去了偏移量,每次和我们维护的 d e q u e deque deque的右端更新一下答案即可(非递减的因此只看有段即可)。
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 tag;
int a[N];
struct Node {
int l, r, c;
};
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
int res = 1;
deque<Node> q;
for (int i = 1; i <= n; i ++) {
int l, r; cin >> l >> r;
if (q.size() && q.front().l <= r) {
while (q.size() && q.back().l > r) q.pop_back();
if (q.size()) q.back().r = r;
if (l < q.front().l) q.push_front({l, q.front().l - 1, -tag});
while (q.size() && q.front().r < l) q.pop_front();
if (q.size()) q.front().l = max(q.front().l, l);
tag ++;
}
else {
q.clear();
q.push_back({l, r, 1});
tag = 0;
}
res = max(res, q.back().c + tag);
}
cout << res << '\n';
return 0;
}
本文探讨了五个编程题目,涉及贪心算法、区间和、选数优化、数组划分和字符串操作。通过贪心思想和区间dp,解决社交圈配对、区间并查集、选数限制、数组划分与字符串组合问题,展示了高效的编程技巧和策略。
684

被折叠的 条评论
为什么被折叠?



