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][k]/dp[n][−k]是否可行,不断转移到之前状态,得到答案字符串
// 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 = 1e3 + 5;
int dp[N][2 * N];
int zero = 1000;
int n, T = 1, k;
char ch[N];
char chan[3] = { 'W','L','D'};
vector<char> ans;
void ready()
{
cin >> n >> k;
cin >> ch + 1;
mst(dp, -1);
}
void work()
{
dp[0][zero] = 0;
for (int i = 1; i <= n; i++) {
ffor(ki, 0, 2) {
if (ch[i] != chan[ki] && ch[i] != '?') continue;
int val = 0;
if (ki == 0) val = 1;
if (ki == 1) val = -1;
ffor(j, zero - k + 1, zero + k - 1) {
//因为中途不能够超过k,所以所有的状态都要从小于k之中去推,故左区间+1,右区间-1
if (dp[i - 1][j] != -1) {
dp[i][j + val] = j;
}
}
}
}
if (dp[n][zero + k] != -1 || dp[n][zero - k]!= -1) {
int now;
if (dp[n][zero + k] == -1) {
now = zero - k;
}
else {
now = zero + k;
}
rrep(i, n, 1) {
if (now == dp[i][now]) ans.push_back('D');
if (now == dp[i][now] + 1) ans.push_back('W');
if (now == dp[i][now] - 1) ans.push_back('L');
now = dp[i][now];
}
rrep(i, ans.size() - 1, 0) cout << ans[i];
}
else {
cout << "NO";
}
}
signed main()
{
IOS;
// cin>>T;
while (T--) {
ready();
work();
}
return 0;
}
Problem C
出处
Codeforces-909E
题意
有n个任务,并且由m个依赖关系。若要完成一个任务,则先前的依赖任务必须都全部完成。而每个任务有两种状态,要么状态为1,要么状态为0。每次完成1号任务则需要花费1次费用,但是可以完成一组1号任务,前提是这些1号任务的依赖任务全部被完成。问最小费用是多少。
题解
对两种任务开两个队列进行拓扑,先拓扑完成0号任务,再完成1号任务,也就是尽可能地将能够设为一组的1号任务都一起完成。
代码
#include <iostream>
#include <queue>
using namespace std;
const int N=1e5+5;
int n,m;
vector<int> ve[N];
int in_[N];
int ans;
int e[N];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++) cin>>e[i];
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
in_[x]++;
ve[y].push_back(x);
}
queue<int> q0,q1;
for(int i=0;i<n;i++){
if(in_[i]==0)
{
if(e[i]==0) q0.push(i);
else q1.push(i);
}
}
while(q0.size() || q1.size()){
if(q0.size()){
while(q0.size()){
int u=q0.front();
q0.pop();
for(auto v:ve[u]){
in_[v]--;
if(in_[v]==0){
if(e[v]==0) q0.push(v);
else q1.push(v);
}
}
}
}
if(q1.size()){
ans++;
while(q1.size()){
int u=q1.front();
q1.pop();
for(auto v:ve[u]){
in_[v]--;
if(in_[v]==0){
if(e[v]==0) q0.push(v);
else q1.push(v);
}
}
}
}
}
cout<<ans;
return 0;
}
Problem F
出处
Codeforces 638-C
题意
n个城市形成的一棵树,边为连接两个城市的路。现在要开始修路,对于一条路<u,v>需要两个城市的工人同时修理,且每个城市只有一个工人,一个工人一天之中有且只能修一条路。请问修完所有道路最少需要多少天?
题解
通过推理,最少的天数必定是最大的度。在已知道天数之后,就是安排道路。
开一个大小为最少天数的桶。随便从一个根节点出发,进行dfs。每进行到一条边的时候,将它放进桶里,然后桶的索引++。即保证在dfs之中,一个点的所有边都存在于不同的桶之中。
代码
#include <iostream>
#include <vector>
#include<algorithm>
#include <map>
using namespace std;
const int N = 2e5 + 5;
bool vis[N], f[N];
int ansi;
int n;
vector<int> ve[N];
vector<int> ans[N];
struct Edge {
int x, y;
}e[N];
void dfs(int u,int k) {
vis[u] = true;
for (auto id : ve[u]) {
int ex = e[id].x, ey = e[id].y;
int v = (ex == u ? ey : ex);
if (!vis[v]) {
ans[k].push_back(id);
k++;
if (k > ansi) k = 1;
}
}
for (auto id : ve[u]) {
int ex = e[id].x, ey = e[id].y;
int v = (ex == u ? ey : ex);
if (!vis[v]) {
dfs(v,k);
}
}
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
ve[x].push_back(i);
ve[y].push_back(i);
e[i].x = x; e[i].y = y;
int vex = ve[x].size(), vey = ve[y].size();
ansi = max(ansi, max(vex, vey));
}
dfs(1,1);
cout << ansi << '\n';
for(int i=1;i<=ansi;i++) {
cout << ans[i].size() << ' ';
for (auto v : ans[i]) {
cout << v << ' ';
}
cout << '\n';
}
return 0;
}
Problem E
出处
Codeforces-746G
题意
建立一棵树,1为根,下面有 t t t层。第 i i i层的根节点个数为 a i a_i ai,求一种连接方法要求最后叶子结点个数为给出的 k k k。
题解
Lnn哥的题解
E.树、叶子、构造
- 直接顺序给结点赋值,比如a[] = {2,3,2}
第一层:1
第二层:2 3
第三层:4 5 6
第四层:7 8 (这样方便找到上下层对应的结点 - 构造就简单点:先构造出叶子最少的方案,不够再加
直接每个点连到正上方结点,比如1-2,3-5,5-8
如果正上方没有,就连到上层第一个,比如6-2 - 可以想到的,一定有些结点要当叶子,比如6、7、8
如果要补叶子,就要把一个点的子节点都挪走
统一的,把子节点挪给当前层第1个,比如5-8转为4-8 - 保证树的性质,每行第一个结点不能成为叶子
- 最后,叶子数不够or多了,就输出-1
代码
// 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 = 2e5 + 5;
int n, T = 1, t, k;
int a[N], fa[N], ans;
bool f[N];
vector<int> tree[N];
void ready()
{
int temp = 1;
cin >> n >> t >> k;
t += 1;
a[1] = 1;
tree[1].push_back(1);
ffor(i, 2, t) {
cin >> a[i];
ffor(j, 1, a[i]) {
temp++;
tree[i].push_back(temp);
}
}
for (int i = 2; i <= t; i++) {
int las = tree[i - 1].size();
for (int j = 0; j < tree[i].size(); j++) {
if (j >= las) fa[tree[i][j]] = tree[i - 1][0];
else fa[tree[i][j]] = tree[i - 1][j];
}
}
ffor(i, 1, n) f[fa[i]] = true;
ffor(i, 1, n) if (!f[i]) ans++;
for (int i = 1; i <= t; i++) {
//for (auto item : tree[i]) cout << item << ' ';
//cout << '\n';
}
//cout << ans << '\n';
}
void out() {
cout << n << '\n';
ffor(i, 2, n) {
cout << i << ' ' << fa[i] << '\n';
}
return;
}
void work()
{
if (ans > k) {
cout << -1;
return;
}
if (ans == k) {
out();
return;
}
rrep(i, t, 2) {
ffor(j, 1, a[i]-1) {
if (fa[tree[i][j]] != tree[i - 1][0]) {
ans++;
fa[tree[i][j]] = tree[i - 1][0];
}
if (ans == k) {
out();
return;
}
}
}
if (ans < k) {
cout << -1;
return;
}
}
signed main()
{
IOS;
// cin>>T;
while (T--) {
ready();
work();
}
return 0;
}