代码源每日一题 div1
摘桃子
[Link](摘桃子 - 题目 - Daimayuan Online Judge)
思路
- 枚举,推公式
设 s s s为 a a a的前缀和,对于一个 i i i等价于找前面有多少个位置满足 ( s i − s j ) % k = i − j → s i − i = s j − j (s_i-s_j)\% k=i-j\to s_i-i=s_j-j (si−sj)%k=i−j→si−i=sj−j且 i − j < k i-j<k i−j<k,因此开个 m a p map map直接维护记录贡献,搞一个索引 j j j来维护一个这个区间长度小于 k k k即可。
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 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];
map<int, int> mp;
int res = 0;
for (int i = 0, j = 0; i <= n; i ++) {
while (i - j + 1 > k) mp[((a[j] - j ++) % k + k) % k] --;
res += mp[((a[i] - i) % k + k) % k];
mp[((a[i] - i) % k + k) % k] ++;
}
cout << res << '\n';
return 0;
}
路径计数2
[Link](路径计数2 - 题目 - Daimayuan Online Judge)
思路
- d p dp dp
由于数据范围太大了 O ( n 2 ) O(n^2) O(n2)的 d p dp dp显然是不行的。
首先我们发现如果没有路障任意两个点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2)之间的合法路径为 C a b s ( x 2 − x 1 + y 2 − y 1 ) a b s ( x 1 − x 2 ) C_{abs(x_2-x_1+y_2-y_1)}^{abs(x_1-x_2)} Cabs(x2−x1+y2−y1)abs(x1−x2) ,即在总的步数中选多少步往右走。
m m m不是很大,因此可以这样 d p dp dp,将所有的障碍物排序保证 i i i在 i − 1 i-1 i−1的右上方,设 f i : 以 这 个 点 结 尾 的 合 法 方 案 f_i:以这个点结尾的合法方案 fi:以这个点结尾的合法方案,总方案可以用上面的式子直接求出,怎么容斥掉不合法的方案呢,我们将不合法的路径按照它经过的第一个路障分类,因此将第 i i i个点前面的被第 i i i个点前面包住的点的不合法路径都去除掉,这样就是到这个点的合法路径了,特殊的将 ( n , m ) (n,m) (n,m)当成最后一个障碍,我们的答案就是 f l a s t f_{last} flast
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 = 2e6 + 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 fact[N], infact[N];
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;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
fact[0] = 1;
for (int i = 1; i <= n * 2; i ++) fact[i] = (LL)fact[i - 1] * i % mod;
infact[n * 2] = qmi(fact[n * 2], mod - 2);
for (int i = n * 2 - 1; i >= 0; i --) infact[i] = (LL)infact[i + 1] * (i + 1) % mod;
for (int i = 1; i <= m; i ++) cin >> a[i].x >> a[i].y;
a[++m] = {n, n};
sort(a + 1, a + 1 + m);
vector<int> f(m + 1);
for (int i = 1; i <= m; i ++) {
f[i] = C(a[i].x + a[i].y - 2, a[i].x - 1);
for (int j = 1; j < i; j ++)
if (a[j].x <= a[i].x && a[j].y <= a[i].y)
f[i] = (((LL)f[i] - (LL)f[j] * C(a[i].x + a[i].y - a[j].x - a[j].y, a[i].x - a[j].x) % mod ) + mod) % mod;
}
cout << f[m] << '\n';
return 0;
}
函数求和
[Link](函数求和 - 题目 - Daimayuan Online Judge)
思路
- 枚举
看到与操作我们应该想到按二进制来看,我们来看什么样的 x x x最终 f ( x ) = 1 f(x)=1 f(x)=1,容斥一下,所有的 − - −最终不会取 1 1 1的就是取 1 1 1的,不会取 1 1 1的就是 a 1 a_1 a1对应二进制有 1 1 1的位均有 1 1 1的 x x x,因此除去这些位剩下的位取或不取就是不会取 1 1 1,对于取 2 2 2的数一定含有取 a 1 a_1 a1的二进制位,否则的话就会取 1 1 1,也就是对于每一个 a i a_i ai算完贡献就会确定一些二进制位,每个 a i a_i ai均容斥的算一下即可。
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 = 998244353;
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];
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 main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i ++) cin >> a[i];
LL res = 0;
LL last = 0;
LL pre = 0;
for (int i = 1; i <= n; i ++) {
LL sum = qmi(2, k - last);
LL t = (pre | a[i]);
int tmp = 0;
for (int j = 0; j < k; j ++)
if (t >> j & 1) tmp ++;
last = tmp;
tmp = k - tmp;
LL d = qmi(2, tmp);
res = ((res + (sum - d) * i % mod) % mod + mod) % mod;
pre |= a[i];
}
cout << res << '\n';
return 0;
}
XOR Inverse
[Link](XOR Inverse - 题目 - Daimayuan Online Judge)
思路
- 二进制,归并排序
显然枚举 x x x是不现实的,涉及异或操作,我们按位来确定 x x x的每一位选什么,每一位之间都是独立的,从高到低枚举每一位,如果当前位取 1 1 1会使逆序对数量减少就取 1 1 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 = 3e5 + 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 b[N], p[N];
LL revs;
void merge_sort(int l, int r) {
if (l >= r) return ;
int mid = l + r >> 1;
merge_sort(l, mid), merge_sort(mid + 1, r);
int cnt = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (a[i] <= a[j]) p[cnt ++] = a[i ++];
else revs += (LL)(mid - i + 1), p[cnt ++] = a[j ++];
while (i <= mid) p[cnt ++] = a[i ++];
while (j <= r) p[cnt ++] = a[j ++];
for (int i = l, j = 0; i <= r; i ++, j ++) a[i] = p[j];
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> b[i];
for (int i = 1; i <= n; i ++) a[i] = b[i];
LL mn = 0;
merge_sort(1, n);
mn = revs;
LL res = 0;
for (int k = 30; k >= 0; k --) {
res |= 1 << k;
for (int i = 1; i <= n; i ++)
a[i] = (b[i] ^ res);
revs = 0;
merge_sort(1, n);
if (revs < mn) {
mn = revs;
}
else res ^= 1 << k;
}
cout << mn << ' ' << res << '\n';
return 0;
}
Closest Equals
[Link](Closest Equals - 题目 - Daimayuan Online Judge)
思路
- 离线,分类,线段树
一开始用莫队瞎搞了搞, T T T了。
对于任意一个点 i i i,它和他左边第一个等于它的数构成点对是最优的,我们可以将所有的询问离线出来并按右端点分类,我们将一个点对 ( i , j ) (i,j) (i,j)的贡献记录在 i i i这个点上,因为这样我们从左到右枚举右端点的时候,只要 l ≤ i l\le i l≤i的时候就可以算上这个贡献,对于某个 r r r的某个 l l l我们要查 [ l , r ] [l,r] [l,r]这个区间结点贡献的最小值,这个可以开个线段树来维护,然后每到一个新的 r r r我们就动态的加入这个点可能构成的最近点对即可。
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];
map<int, int> mp;
vector<PII> g[N];
struct Node {
int l, r;
int mn;
int tag;
}tr[N << 2];
void pushup(int u) {
tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
}
void down(int u) {
if (tr[u].tag != INF) {
tr[u << 1].mn = min(tr[u].tag, tr[u << 1].mn);
tr[u << 1 | 1].mn = min(tr[u].tag, tr[u << 1 | 1].mn);
tr[u << 1].tag = min(tr[u << 1].tag, tr[u].tag);
tr[u << 1 | 1].tag = min(tr[u << 1 | 1].tag, tr[u].tag);
tr[u].tag = INF;
}
}
void build(int u, int l, int r) {
tr[u] = {l, r, INF, INF};
if (l == r) return ;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
int cnt;
void modify(int u, int l, int r, int v) {
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].mn = min(tr[u].mn, v);
tr[u].tag = min(tr[u].tag, v);
return ;
}
down(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, v);
if (r > mid) modify(u << 1 | 1, l, r, v);
pushup(u);
}
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].mn;
int mid = tr[u].l + tr[u].r >> 1;
down(u);
int res = INF;
if (l <= mid) res = min(res, query(u << 1, l, r));
if (r > mid) res = min(res, query(u << 1 | 1, l, r));
return res;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> a[i];
build(1, 1, n);
for (int i = 1; i <= m; i ++) {
int l, r; cin >> l >> r;
g[r].push_back({l, i});
}
vector<int> res(m);
for (int i = 1; i <= n; i ++) {
cnt ++;
if (mp[a[i]]) modify(1, 1, mp[a[i]], i - mp[a[i]]);
mp[a[i]] = i;
for (auto t : g[i]) {
int v = query(1, t.first, i);
res[t.second - 1] = (v == INF ? -1 : v);
}
}
for (auto t : res)
cout << t << '\n';
return 0;
}
CCPC Harbin 2021 G, Damaged Bicycle
[Link](CCPC Harbin 2021 G, Damaged Bicycle - 题目 - Daimayuan Online Judge)
思路
- 最短路,状压 d p dp dp,期望
车子很少,我们可以先预处理出来以每个车子为起点到其它点的最短距离,对于每个车子有两种可能,没坏我们可以直接骑车到终点,坏了的我们可以选择直接走去终点或者走到另一个没有走过的车子再去判断。
对于走到没有走过车子我们可以用一个二进制来表示当前状态下哪些车子没走过,设 f [ i ] [ j ] : 以 i 为 起 点 且 当 前 经 过 的 车 子 状 态 为 j 的 最 小 期 望 f[i][j]:以i为起点且当前经过的车子状态为j的最小期望 f[i][j]:以i为起点且当前经过的车子状态为j的最小期望。对于当前状态我们可以按照上边的转移,特殊的将起点设为一个必坏的车子,直接记忆化搜索就可以了。
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 = 6e5 + 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;
double t, r;
struct Node {
int v, w;
bool operator<(Node t)const {
return t.w < w;
}
};
int a[19];
double p[19];
int dist[19][N];
void dijkstra(int st, int id) {
priority_queue<Node> q;
memset(dist[id], 0x3f, sizeof dist[id]);
vector<bool> f(n + 1);
q.push({st, 0});
dist[id][st] = 0;
while (q.size()) {
auto t = q.top();
q.pop();
if (f[t.v]) continue ;
f[t.v] = true;
for (int i = h[t.v]; ~i; i = ne[i]) {
int j = e[i];
if (dist[id][j] > w[i] + t.w) {
dist[id][j] = w[i] + t.w;
q.push({j, dist[id][j]});
}
}
}
}
double f[19][N];
double dfs(int u, int state) {
if (f[u][state]) return f[u][state];
double res = (1 - p[u]) * dist[u][n] / r + p[u] * dist[u][n] / t;
for (int i = 0; i < k; i ++) {
if (state >> i & 1) continue ;
res = min(res, (1.0 - p[u]) * dist[u][n] / r + p[u] * (dist[u][a[i]] / t + dfs(i, (state | (1 << i)))));
}
return f[u][state] = res;
}
int main() {
memset(h, -1, sizeof h);
cin >> t >> r;
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
int x, y, z; cin >> x >> y >> z;
add(x, y, z), add(y, x, z);
}
cin >> k;
for (int i = 0; i < k; i ++) cin >> a[i] >> p[i], p[i] /= 100;
a[k] = 1, p[k] = 1;
for (int i = 0; i <= k; i ++)
dijkstra(a[i], i);
if (dist[k][n] == INF) {
cout << -1 << '\n';
return 0;
}
printf("%.10lf\n", dfs(k, 0));
return 0;
}
拆方块
[Link](拆方块 - 题目 - Daimayuan Online Judge)
思路
- d p dp dp
去模拟这个过程是很难的,整体不好搞我们可以看一下局部,对于每一列块能活到什么时候取决于,它左边活到的长度 + 1 +1 +1,它右边活到的长度 + 1 +1 +1,它本身的块数,三者取个 m i n min min,我们可以左右分别 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 = 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], f[N], g[N];
int 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 ++) f[i] = min(f[i - 1] + 1, a[i]);
for (int i = n; i; i --) g[i] = min(g[i + 1] + 1, a[i]);
int res = 0;
for (int i = 1; i <= n; i ++) res = max(res, min(f[i], g[i]));
cout << res << '\n';
return 0;
}
这篇博客涵盖了多个算法问题的解决方案,包括摘桃子问题的枚举与公式推导、路径计数的动态规划思路、函数求和的枚举方法、异或逆序对的二进制处理以及最优化问题的最短路径和状态压缩DP应用。通过深入解析这些算法,帮助读者理解并掌握各种复杂问题的解决策略。
662

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



