0%

Codeforces Round 986 (Div. 2)—D. Alice's Adventures in Cards

事实证明人还是不要太固执,我之前就是一定想要写一个搜索算法,但怎么写都TLE,即便我看了hint知道是dp,还是想写搜索,但最后一发dp AC了。怎么说呢,搜索的时间复杂度还是有点玄学,也可能是我学艺不精(bushi)。其实我感觉如果你知道这题是dp的话也不是说非常难写,毕竟我这个非常抗拒dp的写出来了嘛

思路讲解

我们先想一个朴素dp,一个牌可以换到n牌当且仅当他可以换到 n牌 或 之前可以换到n牌的牌(为简化,我们称之为A牌),那就把所有这些牌都遍历一下就知道能不能换到了。

当然这个朴素dp的时间复杂度就比较随缘,最坏情况可能有O(n**2),而且其实也不是很好写。

然后我们小小的优化一下(这种优化我感觉挺常见的),其实能不能换到就是看能不能换到A牌当中优先度最低的牌。

当然三个人对牌的优先度不同,我们用pair<char,int> lowq,lowk,lowj; 分别存一下A牌中在qkj中优先度最小的。

这是对他们的初始化

1
2
3
lowq=make_pair('q',n);
lowk=make_pair('k',n);
lowj=make_pair('j',n);

我们以这段代码为例,这个牌成功换到了A牌中q优先度最小的,q不用更新,他的优先度肯定更高。

lowk以及lowj需要更新,这个 i 牌可能比他们的优先度更小。

1
2
3
4
5
6
7
8
if(pq[i]>pq[lowq.second]) {
isReach[i]=true;
dp[i]=lowq;
if(pk[i]<pk[lowk.second])
lowk=make_pair('k',i);
if(pj[i]<pj[lowj.second])
lowj=make_pair('j',i);
}

最后答案输出就很简单了,跳着存入ans,玩过邻接表的都不会陌生

1
2
3
for(int i=1;i<n;i=dp[i].second) {
ans.push_back(dp[i]);
}

AC代码

提交记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N=static_cast<int>(2e5)+17;
int pq[N],pk[N],pj[N];
int T,n;
vector<pair<char,int> > ans;
bitset<N> isReach;
pair<char,int> dp[N],lowq,lowk,lowj;

signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
while (T--) {
cin>>n;
// 初始化
ans.clear(),isReach.reset();
for(int i=1;i<=n;++i) cin>>pq[i];
for(int i=1;i<=n;++i) cin>>pk[i];
for(int i=1;i<=n;++i) cin>>pj[i];
lowq=make_pair('q',n);
lowk=make_pair('k',n);
lowj=make_pair('j',n);
for(int i=n-1;i>=1;--i) {
if(pq[i]>pq[lowq.second]) {
isReach[i]=true;
dp[i]=lowq;
if(pk[i]<pk[lowk.second])
lowk=make_pair('k',i);
if(pj[i]<pj[lowj.second])
lowj=make_pair('j',i);
}
if(pk[i]>pk[lowk.second]) {
isReach[i]=true;
dp[i]=lowk;
if(pq[i]<pq[lowq.second])
lowq=make_pair('q',i);
if(pj[i]<pj[lowj.second])
lowj=make_pair('j',i);
}
if(pj[i]>pj[lowj.second]) {
isReach[i]=true;
dp[i]=lowj;
if(pk[i]<pk[lowk.second])
lowk=make_pair('k',i);
if(pq[i]<pq[lowq.second])
lowq=make_pair('q',i);
}
}
if(!isReach[1]) {
cout<<"NO"<<endl;
continue;
}
cout<<"YES"<<endl;
for(int i=1;i<n;i=dp[i].second) {
ans.push_back(dp[i]);
}
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();++i) {
cout<<ans[i].first<<" "<<ans[i].second<<endl;
}
}
return 0;
}

心路历程(WA,TLE,MLE……)

一个暴搜dfs代码TLE

在第11个点TLE了,加了快读也不太行

19999个撑不住了

当然这个TLE之前肯定还出了点事,最重要的是把退出条件放在最前面,而且任何退出条件后进来的全部是多余的,ans需要pop一下。

1
2
3
4
if(isFind) {
ans.pop_back();
return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N=static_cast<int>(2e5)+17;
int pq[N],pk[N],pj[N];
int T,n;
bool isFind;
vector<pair<char,int> > ans;
bitset<N> vis;

void dfs(int x) { // x即为当前持有的卡牌
if(isFind) {
ans.pop_back();
return;
}
if(x==n) {
isFind=true;
return;
}
if(vis[x])
return;
vis[x]=true;
for(int i=x+1;i<=n;++i) {
if(isFind)
return;
// 可以看到,emplace_back支持直接传入构造函数的参数来构造创建元素,而push_back必须先构造元素,再通过复制构造函数传入。
if(pq[i]<pq[x]) {
ans.emplace_back('q',i);
dfs(i);
if(!isFind)
ans.pop_back();
}
if(pk[i]<pk[x]) {
ans.emplace_back('k',i);
dfs(i);
if(!isFind)
ans.pop_back();
}
if(pj[i]<pj[x]) {
ans.emplace_back('j',i);
dfs(i);
if(!isFind)
ans.pop_back();
}
}
}

signed main()
{
cin>>T;
while (T--) {
cin>>n;
// 初始化
isFind=false,ans.clear(),vis.reset();
for(int i=1;i<=n;++i) {
cin>>pq[i];
}
for(int i=1;i<=n;++i) {
cin>>pk[i];
}
for(int i=1;i<=n;++i) {
cin>>pj[i];
}
dfs(1);
if(!isFind) {
cout<<"NO"<<endl;
continue;
}
cout<<"YES"<<endl;
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();++i) {
cout<<ans[i].first<<" "<<ans[i].second<<endl;
}
}
return 0;
}