文章目录
引言
本来在预习课程期末的,结果还是没能忍住来写了一套题。
20230416更新:终于把之前没写的题都补上了~
本文juejin:https://juejin.cn/post/7222663980391759928/
本文CSDN:https://blog.csdn.net/hans774882968/article/details/122158687
作者:hans774882968以及hans774882968以及hans774882968
A-签到
签到。数有多少个个位为9的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 3e5 + 5;
int n;
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
int main (int argc, char **argv) {
int T;
read (T);
while (T--) {
read (n);
printf ("%d\n", (n + 1) / 10);
}
return 0;
}
B-多重背包
看到m <= 1000
就猜到大约是m^2
的算法,所以显然要先取模+开桶计数。把模m的和看成重量,于是转化为多重背包求是否存在方案的问题。
但麻烦的是,不能直接用dp[m][0]
,因为它受到dp[0][0] = true
的影响,必然为true;并且也无法直接算方案数。那么我们就在dp转移的过程中监听即可。
不难的一个背包居然tag是1900
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 1e3 + 5;
int n, m, c[N];
bool dp[N * 15][N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
void pack (int &tot, bool &ans, int v, int w) {
if (!v) return;
++tot;
dwn (j, m - 1, 0) {
dp[tot][j] = dp[tot - 1][j] || dp[tot - 1][ (j - w + m) % m];
if (!j) ans |= dp[tot - 1][ (j - w + m) % m];
}
}
int main (int argc, char **argv) {
read (n, m);
rep (i, 1, n) {
int x;
read (x);
c[x % m + 1]++;
}
dp[0][0] = true;
int tot = 0;
bool ans = false;
rep (i, 1, m) {
int u = c[i];
for (int j = 0; (1 << j) < u; ++j) {
pack (tot, ans, 1 << j, (1 << j) * (i - 1) % m);
u -= (1 << j);
}
pack (tot, ans, u, u * (i - 1) % m);
}
puts (ans ? "YES" : "NO");
return 0;
}
C-分类讨论
签到。简单分类讨论。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 3e5 + 5;
int n;
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
int main (int argc, char **argv) {
int T;
read (T);
while (T--) {
read (n);
int s = 0;
rep (i, 1, n) {
int x;
read (x);
s += x;
}
if (s >= n) printf ("%d\n", s - n);
else puts ("1");
}
return 0;
}
D-CF682D-预处理最长公共后缀+前缀定义dp
首先能想到一个前缀定义dp:dp[i][j1][j2]
表示考虑了字符串s[1~j1], t[1~j2]
(1-indexed),选择了i
个相同子串的最大长度和。状态转移方程:
- 不选择公共后缀的情况有2种:
max(dp[i][j1-1][j2], dp[i][j1][j2-1])
。 - 选择公共后缀时,直接贪心地选择最长公共后缀即可:
dp[i-1][j1-suf[j1][j2]][j2-suf[j1][j2]] + suf[j1][j2]
。
最长公共后缀可以O(n * m)
dp预处理:suf[i][j] = s[i] == t[j] ? suf[i - 1][j - 1] + 1 : 0
。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 1e3 + 5;
int n, m, k, dp[11][N][N], suf[N][N];
char s[N], t[N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
int main() {
read (n, m, k);
scanf ("%s", s + 1);
scanf ("%s", t + 1);
rep (i, 1, n) {
rep (j, 1, m) {
suf[i][j] = s[i] == t[j] ? suf[i - 1][j - 1] + 1 : 0;
}
}
rep (i, 1, k) {
rep (j1, 1, n) {
rep (j2, 1, m) {
dp[i][j1][j2] = max (dp[i][j1 - 1][j2], dp[i][j1][j2 - 1]);
if (!suf[j1][j2]) continue;
dp[i][j1][j2] = max (dp[i][j1][j2], dp[i - 1][j1 - suf[j1][j2]][j2 - suf[j1][j2]] + suf[j1][j2]);
}
}
}
printf ("%d\n", dp[k][n][m]);
return 0;
}
E-组合数学经典模型
一看就很组合数学,而且没法dp。突破口就是令一些房间为空。于是非空的房间相当于经典的方程求解的个数问题,如果没听说过,可以参考我的这篇介绍。
枚举空房间个数i = 0~min(n-1,k)
,则C(n,i)
选出空房间,C(n-i+i-1,i)
表示n-i
个变量,和为i
的方案数,乘起来就是贡献。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 3e5 + 5, mod = 1e9 + 7;
int n, k;
LL fac[N], ifac[N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
LL q_pow (LL a, LL b, LL mod) {
LL ret = 1;
for (; b; b >>= 1) {
if (b & 1) ret = ret * a % mod;
a = a * a % mod;
}
return ret;
}
void init (int n) {
fac[0] = 1;
rep (i, 1, n) fac[i] = fac[i - 1] * i % mod;
ifac[n] = q_pow (fac[n], mod - 2, mod);
dwn (i, n - 1, 0) ifac[i] = (i + 1) * ifac[i + 1] % mod;
}
LL C (int x, int y) {
if (x < y) return 0;
return fac[x] * ifac[y] % mod * ifac[x - y] % mod;
}
int main (int argc, char **argv) {
read (n, k);
init (n);
int ans = 0;
rep (i, 0, min (n - 1, k) )
ans = (ans + C (n, i) * C (n - 1, i) % mod) % mod;
printf ("%d\n", ans);
return 0;
}
F-贪心
贪心依据是排序不等式。贪心过程需要一个数据结构,维护当前可选但未选的下标,支持插入、删除和求最小权值。这个数据结构就是优先队列。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 3e5 + 5;
int n, k, a[N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
int main (int argc, char **argv) {
read (n, k);
rep (i, 1, n) read (a[i]);
priority_queue<pair<int, int>> q;
rep (i, 1, k) q.push ({a[i], i});
LL tot = 0;
vector<int> ans (n);
rep (i, k + 1, k + n) {
if (i <= n) q.push ({a[i], i});
pair<int, int> u = q.top();
q.pop();
tot += 1LL * u.first * (i - u.second);
ans[u.second - 1] = i;
}
printf ("%lld\n", tot);
re_ (i, 0, n) cout << ans[i] << " \n"[i == n - 1];
return 0;
}
G-最短路拼接
最短路拼接这个技巧都忘了QAQ。暴力做法就是直接枚举所有边,如果当前边连上后,经过当前边的s到t的最短路比现有的更短,那么当前边不合法;否则合法。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 1e3 + 5;
int n, m, s, t, a[N][N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
void bfs (int s, vector<int> &dis) {
queue<int> q;
q.push (s);
dis[s] = 0;
while (!q.empty() ) {
int u = q.front();
q.pop();
rep (v, 1, n) if (a[u][v] && (!~dis[v]) ) {
dis[v] = dis[u] + 1;
q.push (v);
}
}
}
int main (int argc, char **argv) {
read (n, m, s, t);
rep (i, 1, m) {
int u, v;
read (u, v);
a[u][v] = a[v][u] = true;
}
vector<int> dis1 (n + 1, -1), dis2 (n + 1, -1);
bfs (s, dis1);
bfs (t, dis2);
int ans = 0;
rep (i, 1, n) rep (j, i + 1, n) if (!a[i][j]) {
int d = min (dis1[i] + 1 + dis2[j], dis1[j] + 1 + dis2[i]);
ans += (d >= dis1[t]);
}
printf ("%d\n", ans);
return 0;
}
H-CF1070C-权值树状数组O(nlog^2n),也可以用权值线段树O(nlogn)
显然某两天之间没有影响,所以我们只需要单独优化每一天的选择即可。
对于某一天,我们只需要一个简单的贪心:不断取当天提供的最便宜的CPU,直到个数达到k
个或取完。但暴力是O(n*m)
的。因此不难想到需要维护数据结构的差分,即当天与前一天的差异。我们引入一个struct Op {int typ, c, p;};
,和一个vector<Op> op[N]
,typ = 1
表示当天要加入一个计划,typ = -1
表示当天要删除一个计划。代码如下:
rep (i, 1, m) {
int l, r, c, p;
read (l, r, c, p);
op[l].push_back ({1, c, p});
op[r + 1].push_back ({-1, c, p});
}
接下来考虑使用什么数据结构。因为要按照价格升序贪心,所以我们以价格为下标,开两个桶C1[v], C2[v]
分别表示价格为v
的CPU总数和总价格。于是我们需要找到最小的价格mn_idx
使得sum(C1[mn_idx]) >= k, sum(C1[mn_idx-1]) < k
。则这一天的总价格就是sum(C2[mn_idx-1]) + (k - sum(C1[mn_idx-1])) * mn_idx
。这两个桶的操作是单点修改和求前缀和,因此可以用树状数组。但是求mn_idx
的朴素做法就需要二分答案了,复杂度就有两个log,幸好CF还是让我过了。
可以用权值线段树来避免二分答案的操作,参考:https://www.cnblogs.com/wushansinger/p/15750956.html。线段树的下标仍定义为价格,维护cnt, cost
属性表示价格在这个区间内的CPU总个数和总价格。这时,贪心策略在线段树的体现就是:优先遍历左侧节点,左侧足够了就不必遍历右侧节点。涉及的操作:单点修改、区间查询。如何处理CPU不够的问题?参考链接的做法是在线段树之外进行判定。这个做法的思想和上述权值树状数组的做法一样朴素,我就懒得写代码了。
还有一种叫“树状数组上倍增”的做法:https://www.luogu.com.cn/blog/FrozaFerrari/solution-cf1070c。这种做法也是一个log,我看不懂,等一个佬来教教我~
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define lowbit(x) ((x) & (-(x)))
const int N = 1e6 + 5;
int n, m, k, mx_p = 0;
LL C1[N], C2[N];
struct Op {
int typ, c, p;
};
vector<Op> op[N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
void mdy (LL C[], int x, LL v) {
for (; x <= mx_p; x += lowbit (x) ) C[x] += v;
}
LL qry (LL C[], int x) {
LL ans = 0;
for (; x; x -= lowbit (x) ) ans += C[x];
return ans;
}
int main() {
read (n, k, m);
rep (i, 1, m) {
int l, r, c, p;
read (l, r, c, p);
op[l].push_back ({1, c, p});
op[r + 1].push_back ({-1, c, p});
mx_p = max (mx_p, p);
}
LL ans = 0;
rep (i, 1, n) {
for (auto x : op[i]) {
mdy (C1, x.p, x.typ * x.c);
mdy (C2, x.p, 1LL * x.typ * x.c * x.p);
}
int l = 1, r = mx_p;
int goal = min (1LL * k, qry (C1, mx_p) );
while (l < r) {
int mid = (l + r) >> 1;
LL val = qry (C1, mid);
if (val >= goal) r = mid;
else l = mid + 1;
}
int mn_idx = l;
ans += qry (C2, mn_idx - 1) + (goal - qry (C1, mn_idx - 1) ) * mn_idx;
}
printf ("%lld\n", ans);
return 0;
}
I-二分答案+树状数组
看到“最小值最大化”就先试试二分ans。怎么贪呢?考虑端点。最靠左的小于x的数是必须变成x的。那么我们选取的区间一定要尽量靠右,因为它左边的数都不需要被增加。
这时我们需要一个数据结构支持区间加、求单点值。这就是差分数组,用树状数组维护。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define lowbit(x) ((x) & (-(x)))
const int N = 3e5 + 5;
int n, m, w, a[N], C[N];
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
void mdy (int x, int v) {
for (; x <= n; x += lowbit (x) ) C[x] += v;
}
int qry (int x) {
int ans = 0;
for (; x; x -= lowbit (x) ) ans += C[x];
return ans;
}
bool jdg (int x) {
rep (i, 1, n) C[i] = 0;
rep (i, 1, n) mdy (i, a[i]), mdy (i + 1, -a[i]);
int u = 0;
rep (i, 1, n) {
int v = qry (i);
if (v >= x) continue;
u += x - v;
if (u > m) return false;
mdy (i, x - v);
mdy (i + w, v - x);
}
return true;
}
int main (int argc, char **argv) {
read (n, m, w);
rep (i, 1, n) read (a[i]);
int l = *min_element (a + 1, a + n + 1), r = *max_element (a + 1, a + n + 1) + m;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (jdg (mid) ) l = mid;
else r = mid - 1;
}
printf ("%d\n", l);
return 0;
}
J-利用树的直径相关结论
这个构造题不难啊?但过的人不够多……
首先需要一个树的直径的结论:设一个离根最远的点为u,以u为根求出一个离u最远的点v。则(u,v)
路径是其中一个树的直径。于是我们构造1 ~ h+1
是一条链,而h+2 ~ d+1
是1号点的另一个子树(另一条链),然后剩下的点全部放到h号点下面。
值得注意的是一些特判:2*h < d
无解,d == 1
无法构造两棵子树。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 3e5 + 5;
int n, d, h;
vector<int> G[N];
vector<pair<int, int>> ans;
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
void dfs (int u, int ufa) {
if (ufa) ans.push_back ({ufa, u});
for (int v : G[u]) dfs (v, u);
}
int main (int argc, char **argv) {
read (n, d, h);
if (2 * h < d) {
puts ("-1");
return 0;
}
if (d == 1 && n > 2) {
puts ("-1");
return 0;
}
rep (i, 1, h) G[i].push_back (i + 1);
rep (i, h + 1, d) G[i == h + 1 ? 1 : i].push_back (i + 1);
rep (i, d + 2, n) G[h].push_back (i);
dfs (1, 0);
re_ (i, 0, n - 1) printf ("%d %d\n", ans[i].first, ans[i].second);
return 0;
}