一道经典的dp题:
(1)明确dp数组含义:
dp[i][j][k]: 表示前i个数字中选择j个凑成k的方法数。
(2)确定状态转移方程:
dp[i][j][k] = dp[i - 1][j - 1][k - i] + dp[i - 1][j][k]
有两种情况,如果当前遍历到的数字i大于背包容量则肯定不取,如果小于则可以取。
(3)初始化:
由于后续的所有方法数都是依赖于底层组成0的方法数:
所以将第一层容量为0的背包都设置为1。
(4)遍历顺序:
就是从小到大遍历即可,并未涉及状态压缩:
- #include
- using namespace std;
-
- int main(){
- long long dp[2023][11][2023] = {0};
- for(int i = 0; i < 2023; i++){
- dp[i][0][0] = 1;
- }
- for(int i = 1; i < 2023; i++){
- for(int j = 1; j <= 10; j++){
- for(int k = 1; k < 2023; k++){
- if(i <= k){
- dp[i][j][k] = dp[i - 1][j - 1][k - i] + dp[i - 1][j][k];
- }else{
- dp[i][j][k] = dp[i - 1][j][k];
- }
- }
- }
- }
- cout << dp[2022][10][2022] << endl;
-
- return 0;
- }
简单模拟题:
- #include
- using namespace std;
- int main()
- {
- // 时针:
- // 360 / 12 = 30°
- // 30 / 60 = 0.5°
- // 0.5 / 60 = 1/120°
- // 分针:
- // 360 / 60 = 6°
- // 6 / 60 = 0.1°
- // 秒针:
- // 360 / 60 = 6°
- for(int i = 0; i <= 6; i++){
- for(int j = 0; j < 60; j++){
- for(int k = 0; k < 60; k++){
- double h = i * 30.0 + j / 2.0 + k / 120.0; // 6点附近
- double m = j * 6.0 + k / 10;
- double s = k * 6.0;
- double a = abs(m - h);
- double b = abs(s - m);
- if(a > 180){
- a = 360 - a;
- }
- if(b > 180){
- b = 360 - b;
- }
- if(a == 2 * b){
- if(i == 0 && j == 0 && k == 0){
- continue;
- }
- cout << i << " " << j << " " << k;
- }
- }
- }
- }
- return 0;
- }
题目:
笔记:
这道题目有点贪心的意思:
我们的思路是优先将已有牌中数目最少的牌补齐:所以我们需要有一个数组来存储已有牌的数目以及对应最多能够补几张:然后我们对该数组进行排序,优先补齐数量最少的牌查看是否能够凑齐指定套数的牌:
为了更好地处理,我们先将数字转换成字符串类型进行处理:
明确我们的目的:将数字改的尽可能的大,我们的限制条件是:opt_1操作次数不能超过A次,opt_2操作不能超过B次:我们可以先判断opt_2的数量是否大于当前数字cur + 1,如果不大于就只选择opt_1不再进行opt_2操作,如果当opt_2的数量大于cur + 1, 那么就优先将opt_2的数量消耗掉,因为opt_2只能通过正好将cur变成9才会变大,而opt_1无论怎样都会使数字变大。
- #include
- #include
- using namespace std;
- typedef long long ll;
-
-
- int main() {
- ll n, a, b;
- cin >> n >> a >> b;
-
- stringstream ss;
- ss << n;
- string str = ss.str();
-
- for (ll i = 0; i < str.size(); i++) {
- ll digit = str[i] - '0'; // 将字符转换为整数
- if (b >= digit + 1) {
- b -= (digit + 1);
- str[i] = '9';
- } else {
- if (a <= 0) break;
- ll need = 9 - digit;
- if (need <= a) {
- str[i] = '9';
- a -= need;
- } else {
- str[i] = digit + a + '0'; // 将整数转换回字符
- a = 0;
- break;
- }
- }
- }
-
- // 使用 stringstream 将字符串转换为整数
- ll result;
- stringstream(str) >> result;
- cout << result << endl;
-
- return 0;
- }
但是这么写只过90%,
这道题就是一个迪杰斯特拉的模板题,让我们找到一条从城市1出发到达城市n的最短路径,
- #include
- #include
- #include
- #include
- using namespace std;
- #define int long long
- typedef pair<int,int>PII;
- const int N=1e5+5,M=2*N;
- int h[N],e[M],ne[M],w[M],a[N],dist[N],vis[N],idx;
- void add(int x,int y,int z){
- e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;
- //数组e是代表当前边的终点
- //数组w是代表当前边的权重
- //数组ne表示next的指针是只想同一起点的边的下标
- }
- void dijkstra(){
- memset(dist,0x3f,sizeof dist);
- priority_queue
,greater >q; - q.push({0,1});
- dist[1]=0;
- while(q.size()){
- int dis=q.top().first;
- int s=q.top().second;
- q.pop();
- if(vis[s])continue;
- vis[s]=1;
- for(int i=h[s];~i;i=ne[i]){
- int j=e[i];
- if(dist[j]>dis+w[i]){
- dist[j]=dis+w[i];
- q.push({dist[j],j});
- }
- }
- }
- }
- signed main(){
- memset(h,-1,sizeof h);
- int n,m;
- cin>>n>>m;
- for(int i=1;i<=n;i++)cin>>a[i];
- while(m--){
- int x,y,z;
- cin>>x>>y>>z;
- add(x,y,z+a[y]);
- add(y,x,z+a[x]);
- }
- dijkstra();
- cout<
- return 0;
- }
- #include
- using namespace std;
-
- int main(){
- int n, m;
- cin >> n >> m;
- vector<int> t(n + 1, 0);
- for(int i = 1; i <= n; i++){
- cin >> t[i];
- }
- // 存图:
- vector
int>> grid(n + 1, vector(n + 1, INT_MAX)); - for(int i = 1; i <= m; i++){
- int start, end, cost;
- cin >> start >> end >> cost;
- grid[start][end] = cost + t[end];
- grid[end][start] = cost + t[start];
- }
-
- priority_queue
int, int>, vectorint, int>>, greaterint, int>>> que; - vector<bool> visited(n + 1, false);
- que.push(make_pair(0, 1));
- vector<int> mindist(n + 1, INT_MAX);
- mindist[1] = 0;
- while(!que.empty()){
- pair<int, int> cur = que.top(); que.pop();
- int end = cur.first;
- int start = cur.second;
- if(visited[start]) continue;
- visited[start] = true;
- for(int i = 1; i <= n; i++){
- if(!visited[i] && grid[start][i] != INT_MAX && mindist[start] + grid[start][i] < mindist[i]){
- mindist[i] = grid[start][i] + mindist[start];
- que.push(make_pair(mindist[i], i));
- }
- }
- }
- if(mindist[n] == INT_MAX){
- cout << -1;
- }else{
- cout << mindist[n] - t[n];
- }
-
- return 0;
- }
注意优先队列中元素排列的顺序。
F - 费用报销:
题目:
笔记:
- #include
- using namespace std;
-
- const int N = 1010;
- int f[N][N * 5];
- int s[N], last[N];
- int d[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};//每个月份的天数
- int n, m, k;
- struct Node
- {
- int m, d, v, t;
- } p[N];// 定义一个日期结构体:包含月份,天数,价值,相对年初的天数
-
- bool cmp(Node &a, Node &b)
- {
- return a.t < b.t;
- }
-
- int main()
- {
- scanf("%d%d%d", &n, &m, &k);
- for (int i = 2; i <= 12; i++) s[i] = s[i - 1] + d[i - 1];// 前缀和计算当前月相对年初的天数
- // 为每一个日期结构体的t日期进行赋值:
- for (int i = 1; i <= n; i++)
- {
- scanf("%d%d%d", &p[i].m, &p[i].d, &p[i].v);
- p[i].t = s[p[i].m] + p[i].d;
- }
- // 按日期结构的天数进行升序排列
- sort(p + 1, p + 1 + n, cmp);
-
- // 找出距离当前项目最近的可以选择的项目的编号,因为当你遍历到一个日期需要查看预支配对的上一个可行的日期。
- for (int i = 1; i <= n; i++)
- for (int j = 0; j < i; j++)
- if (p[i].t - p[j].t >= k)
- last[i] = j;
-
-
- for (int i = 1; i <= n; i++)
- for (int j = 0; j <= m; j++)
- {
- f[i][j] = f[i - 1][j]; // 不选第i个,状态则为i-1
- if (p[i].v <= j && (p[i].t - p[i - 1].t) >= k) // 如果选第i个,则要看背包是否装得下才比较
- f[i][j] = max(f[i][j], f[i - 1][j - p[i].v] + p[i].v);
- }
-
- cout << f[n][m];
-
- return 0;
- }
耗费大量精力也是终于复刻出来了:
这道题归根到底就是一道01背包的题目,但是我们需要注意的是在我们对当前物品取与不取的选择中,如果我们选择取那么记忆化搜索调用上一个物品的最大金额dp[i - 1][j - v],这里并不仅仅是上一个物品,因为我们的限制条件是天数要大于等于k,所以上一个日期可能并没有达到k,可能需要用到上上个日期或者上上上个日期,所以我们就需要注意到底是取哪一个日期作为i- 1,这里我们用到了一个last数组记录每一个日期对应的上一个可选择日期,我们利用一个快慢指针搜索距离当前日期最近的可选日期。然后就可以按正常的逻辑进行dp:
dp[i][j] = max(dp[i - 1][j], dp[last[i]][j - value] + value):
- #include
- using namespace std;
- struct date_m{
- int m, d, v, t;
- // t表示当前日期与年初的天数
- };
- // 用于结构体排序,将日期按距离年初的天数从小到大排序
- bool com(date_m& a, date_m& b){
- return a.t < b.t;
- }
- // 每个月的日期,用来计算当前日期距年初的天数
- int mon[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
- int sum(int x){
- int a = 0;
- for(int i = 1; i < x; i++){
- a += mon[i];
- }
- return a;
- }
-
- int main(){
- int n, m, k;
- cin >> n >> m >> k;
- vector
date(n + 1) ; - for(int i = 1; i <= n; i++){
- cin >> date[i].m >> date[i].d >> date[i].v;
- date[i].t = sum(date[i].m) + date[i].d;
- }
- sort(date.begin(), date.end(), com);
- //cout << date[4].t << endl;
- vector<int> last(n + 1, 0);
- for(int i = 2; i <= n; i++){
- for(int j = 1; j < i; j++){
- if((date[i].t - date[j].t) >= k){
- last[i] = j;
- }
- }
- }
- vector
int> > dp(n + 1, vector<int>(m + 1, 0)); - for(int i = 1; i <= n; i++){
- for(int j = 0;j <= m; j++){
- dp[i][j] = dp[i - 1][j];
- if(j >= date[i].v){
- dp[i][j] = max(dp[i - 1][j], dp[last[i]][j - date[i].v] + date[i].v);
- }
- }
- }
-
- cout << dp[n][m] << endl;
- return 0;
- }
H - 机房:
题目:
笔记:
就是迪杰斯特拉的模板题:
- #include
- using namespace std;
- const int Max = 1e5 + 5;
-
- int main(){
- int n, m;
- cin >> n >> m;
- vector<int> edge[Max];
- // 读取 m 条边
- for(int i = 0 ; i < n - 1; i++){
- int a, b;
- cin >> a >> b;
- edge[a].push_back(b);
- edge[b].push_back(a);
- }
- vector<int> res(m, 0);
- for(int i = 0; i < m; i++){
- int u, v;
- cin >> u >> v;
- priority_queue
int, int>, vectorint, int> >, greaterint, int> > > que; - vector<int> mindist(n + 1, INT_MAX); // 使用 INT_MAX 初始化
- mindist[u] = 0;
- vector<bool> vis(n + 1, false);
- que.push(make_pair(0, u));
- while(!que.empty()){
- pair<int, int> cur = que.top(); que.pop();
- int cost = cur.first;
- int start = cur.second;
- if(vis[start]) continue;
- vis[start] = true;
- int len = edge[start].size();
- for(int j = 0; j < len; j++){
- int dot = edge[start][j];
- if( mindist[dot] > mindist[start] + len){
- mindist[dot] = mindist[start] + len;
- que.push(make_pair(mindist[dot], dot)); // 更新优先队列
- }
- }
- }
- // 检查是否存在路径
- if(mindist[v] != INT_MAX) {
- res[i] = mindist[v] + edge[v].size();
- } else {
- res[i] = -1; // 如果不存在路径,输出 -1
- }
- }
- for(int i = 0; i < m; i++){
- cout << res[i] << endl;
- }
-
- return 0;
- }
- #include
- using namespace std;
- const int Max = 1e5 + 5;
-
- int main(){
- int n, m;
- cin >> n >> m;
- vector<int> edge[Max];
- // 读取 m 条边
- for(int i = 0 ; i < n - 1; i++){
- int a, b;
- cin >> a >> b;
- edge[a].push_back(b);
- edge[b].push_back(a);
- }
- vector<int> res(m, 0);
- for(int i = 0; i < m; i++){
- int u, v;
- cin >> u >> v;
- priority_queue
int, int>, vectorint, int> >, greaterint, int> > > que; - vector<int> mindist(n + 1, INT_MAX); // 使用 INT_MAX 初始化
- mindist[u] = 0;
- vector<bool> vis(n + 1, false);
- que.push(make_pair(0, u));
- while(!que.empty()){
- pair<int, int> cur = que.top(); que.pop();
- int cost = cur.first;
- int start = cur.second;
- if(vis[start]) continue;
- vis[start] = true;
- int len = edge[start].size();
- for(int j = 0; j < len; j++){
- int dot = edge[start][j];
- if(!vis[dot] && mindist[dot] > mindist[start] + len){
- mindist[dot] = mindist[start] + len;
- que.push(make_pair(mindist[dot], dot)); // 更新优先队列
- }
- }
- }
- // 检查是否存在路径
- if(mindist[v] != INT_MAX) {
- res[i] = mindist[v] + edge[v].size();
- } else {
- res[i] = -1; // 如果不存在路径,输出 -1
- }
- }
- for(int i = 0; i < m; i++){
- cout << res[i] << endl;
- }
-
- return 0;
- }