2022.07.06 暑假集训 个人排位赛
Posted 晁棠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2022.07.06 暑假集训 个人排位赛相关的知识,希望对你有一定的参考价值。
2022.07.06 暑假集训 个人排位赛(一)
赛后反省
读题有些不流畅,往错误的题意方向去做题目,导致反复交错了很多题。
对动态规划的敏感度还是不太够,多补题吧。
Problem A
出处
Codeforces-793D
题意
给出一个有向图,找一条恰好经过 k 个点的最短路径,要求每次选的边不能跃过之前已经经过的节点。即对于路径中的边 x → y x→y x→y ,不存在以前经过的点 t 使得三者的编号满足 m i n ( x , y ) ≤ t ≤ m a x ( x , y ) min(x,y)≤t≤max(x,y) min(x,y)≤t≤max(x,y) 。
题解
lcj题解
路径的点有两种形式,要么如 1,10,2,8,4,6,5这样从两端往中间缩,要么如 1,3,4,5,7,8,10这样往一个方向扩(在数轴画一下会比较形象)。
反过来考虑两种情况就会都统一为点往外面扩,即已经经过的点包含的区间不断扩大,用l,r表示区间端点,我们所要求的就是所有经过k个点的区间的最小权值。
可以用dp[i][j][K][0/1]表示被K个点经过且最后一个点是i/j(第四维0对应i,1对应j)的区间[i,j]的最小权值,然后区间dp。
有四种转移,都差不多,这里给出一种:dp[i-d][j][k][0]=min(dp[i][j][k-1][0]+g[i][i-d]),(d是目标区间长度与当前区间长度的差值)。
中文版题解链接
代码
// Good Good Study, Day Day AC.
#include <iostream>
#include <stdio.h>
#include <cstdio>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <cstring>
#include <math.h>
#include <cmath>
#include <queue>
#include <deque>
#include <stack>
#include <vector>
#include <map>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#define ffor(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define rrep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define IOS ios::sync_with_stdio(false),cin.tie(0)
#define ll long long
#define INF 0x7f7f7f7f7f7f7f7f
#define inf 0x7f7f7f7f
#define PII pair<int,int>
#define int long long
using namespace std;
const int N = 105;
int n, T = 1, ki, m;
int dp[N][N][N][2];
int g[N][N];
void ready()
cin >> n >> ki >> m;
ffor(i, 1, m)
int u, v, c;
cin >> v >> u >> c;
if (g[u][v] == 0) g[u][v] = c;
else g[u][v] = min(g[u][v], c);
if (ki > n)
cout << -1;
return;
ffor(i, 1, n)
ffor(j, 1, n)
ffor(k, 1, n)
dp[i][j][k][0] = dp[i][j][k][1] = inf;
ffor(i, 1, n) dp[i][i][1][1] = dp[i][i][1][0] = 0;
ffor(len, 2, n) // l r k 0/1
ffor(l, 1, n)
ffor(r, l, n)
if (r - l + 1 == len) break;
int las = len - (r - l + 1);
ffor(k, 1, ki - 1)
if (dp[l][r][k][0] != inf)
if (l - las > 0 && g[l][l - las])
dp[l - las][r][k + 1][0] = min(dp[l - las][r][k + 1][0], dp[l][r][k][0] + g[l][l - las]);
if (r + las <= n && g[l][r + las])
dp[l][r + las][k + 1][1] = min(dp[l][r + las][k + 1][1], dp[l][r][k][0] + g[l][r + las]);
if (dp[l][r][k][1] != inf)
if (l - las > 0 && g[r][l - las])
dp[l - las][r][k + 1][0] = min(dp[l - las][r][k + 1][0], dp[l][r][k][1] + g[r][l - las]);
if (r + las <= n && g[r][r + las])
dp[l][r + las][k + 1][1] = min(dp[l][r + las][k + 1][1], dp[l][r][k][1] + g[r][r + las]);
int ans = inf;
ffor(i, 1, n)
ffor(j, 1, n)
ans = min(ans, min(dp[i][j][ki][1], dp[i][j][ki][0]));
if (ans == inf) ans = -1;
cout << ans;
void work()
signed main()
IOS;
// cin>>T;
while (T--)
ready();
work();
return 0;
Problem B
出处
Codeforces-803E
题意
一串字符长度为n,只包含L(失败)、W(胜利)、D(平局)、?(忘记了结果)。需要将?用L/W/D来替换,使得在到达最后一个字符的时候L的个数与W的个数的差的绝对值为k,并且中途不能达到k即以上。还原原序列,若无法完成输出NO。
题解
方法一:记忆化搜索
普通的搜索会超时,所以加上记忆化。记忆化为何有效呢?当搜索到第u个位置,和为sum时,如果这个状态被搜索过,如果正确,则搜索会已经结束。而能再次搜索到这个状态则表示这个状态之后的所有状态已经搜索过,并且不能够满足条件,所以再次踏进相同的状态时直接剪枝掉。
// Good Good Study, Day Day AC.
#include <iostream>
#include <stdio.h>
#include <cstdio>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <cstring>
#include <math.h>
#include <cmath>
#include <queue>
#include <deque>
#include <stack>
#include <vector>
#include <map>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#define ffor(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define rrep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define IOS ios::sync_with_stdio(false),cin.tie(0)
#define ll long long
#define INF 0x7f7f7f7f7f7f7f7f
#define inf 0x7f7f7f7f
#define PII pair<int,int>
#define int long long
using namespace std;
const int N = 1e4;
int n, T = 1,k;
char ch[N];
map<int, int> mp[N];
void ready()
cin >> n >> k;
cin >> ch + 1;
bool dfs(int u, int sum)
if (u == n + 1 && abs(sum) == k) return true;
if (u == n + 1 || abs(sum) >= k) return false;
if (mp[u][sum]) return false;
mp[u][sum] = 1;
if (ch[u] == 'W') return dfs(u + 1, sum + 1);
if (ch[u] == 'L') return dfs(u + 1, sum - 1);
if (ch[u] == 'D') return dfs(u + 1, sum);
if (dfs(u + 1, sum + 1))
ch[u] = 'W';
return true;
if (dfs(u + 1, sum - 1))
ch[u] = 'L';
return true;
if (dfs(u + 1, sum))
ch[u] = 'D';
return true;
return false;
void work()
if (dfs(1, 0)) cout << ch + 1;
else cout << "NO";
signed main()
IOS;
// cin>>T;
while (T--)
ready();
work();
return 0;
方法二:DP
Lnn哥题解:
B.字符串、类背包dp
1.n只有1000,前缀和范围-1000到1000
2.考虑枚举所有状态,利用dp
3.dp[i][j]为位置i在值j时,上一个状态值是多少;-1代表该状态不可行
4.枚举i,枚举上一个状态j,若
d
p
[
i
−
1
]
[
j
]
!
=
−
1
dp[i-1][j] != -1
dp[i−1][j]!=−1,根据当前字母
若
a
[
i
]
=
′
W
′
/
′
L
′
/
′
D
′
,
v
a
l
=
1
/
−
1
/
0
,
d
p
[
i
]
[
j
+
v
a
l
]
=
j
a[i] = 'W'/'L'/'D' , val = 1/-1/0,dp[i][j+val] = j
a[i]=′W′/′L′/′D′,val=1/−1/0,dp[i][j+val]=j
5.最后查看
d
p
[
n
]
[
k
]
/
d
p
[
n
]
[
−
k
]
dp[n][k]/dp[n][-k]
dp[n][2022年ACM暑假集训个人排位赛A-F题解