ABC-271解题报告

C. Manga

题意:有一本书有 nn 卷,你需要从第一卷开始依次看,一旦没有某一卷就停止。在看之前你可以进行若干次操作,每次卖掉任意两卷并买新的任意一卷。问操作结束后最多能看多少卷。

做法 1

注意到拥有的重复的卷都可以没有损失地卖掉,提前记录一下。然后从小到大扫,如果没有这一卷就尝试卖两本并买这一本。卖的两本优先从重复的卷里考虑,然后再贪心从大到小卖。正确性显然,数据结构维护即可。

By KrK

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
#include <bits/stdc++.h>
using namespace std;

int n;
set <int> S;
int cur;

int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int a; scanf("%d", &a);
if (S.count(a)) cur++;
else S.insert(a);
}
for (int i = 1; ; i++)
if (!S.count(i)) {
while (cur < 2 && !S.empty() && *S.rbegin() > i) {
auto it = S.end(); it--;
cur++; S.erase(it);
}
if (cur >= 2) {
cur -= 2;
S.insert(i);
} else {
printf("%d\n", i - 1);
return 0;
}
}
return 0;
}

做法 2

考虑二分答案。对于读 ii 卷的情况,将 >i+1>i+1 的卷和 i\le i 的重复的卷全部卖掉替换,判断是否能填补空隙。

By heno239

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
void solve() {
int n; cin >> n;
vector<int> a(n);
rep(i, n)cin >> a[i];
//[1,x]
auto can = [&](int x) {
vector<int> cnt(x+1);
int z = 0;
rep(i, n) {
if (a[i] > x)z++;
else {
if (cnt[a[i]])z++;
cnt[a[i]] = 1;
}
}
int r = 0;
rep1(i, x) {
if (!cnt[i])r++;
}
if (z >= 2 * r)return true;
return false;
};
int le = 0, ri = n + 5;
while (ri - le > 1) {
int m = (le + ri) / 2;
if (can(m)) {
le = m;
}
else {
ri = m;
}
}
cout << le << "\n";
}

做法 3

与二分答案类似,但将二分改成了枚举。注意到读的卷数一定不会超过 nn,所以 >n>n 的卷都可以直接卖掉。考虑统计能否读 ii 卷。考虑设计 bak[i]bak[i] 表示 i\le i 的卷有多少,则如果要读 ii 卷,将会有 ibak[i]i-bak[i] 个位置空缺。这些位置可以使用多余的补上,而只有 bak[i]bak[i] 卷是不多余的(每种只有一卷不多余,剩下的都多余),所以总的多余的数量为 nbak[i]n-bak[i],比较空缺位置与多余数量的一半即可。

By He_Ren

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 3e5 + 5;

int a[MAXN], bak[MAXN];

int main(void)
{
int n;
scanf("%d",&n);
for(int i=1; i<=n; ++i)
scanf("%d",&a[i]);

for(int i=1; i<=n; ++i)
if(a[i] <= n) bak[a[i]] = 1;
for(int i=1; i<=n+1; ++i)
bak[i] += bak[i-1];

for(int i=1; i<=n+1; ++i)
{
if(i - bak[i] > (n - bak[i]) / 2)
{
printf("%d\n",i-1);
return 0;
}
}
return 0;
}

错解

By daisybunny

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
#include<bits/stdc++.h>
using namespace std;
int n;
int a[300005];
map<int,int> mp;
int ba;
int ans;
int main()
{
cin>>n;
for(int t=0;t<n;t++)
{
cin>>a[t];
mp[a[t]]++;
}
sort(a,a+n);
for(int t=1;;t++)
{
if(mp[t]==0)
{
if(ba>1)
{
ba-=2;
ans++;
}
else if(n>=1&&t<a[n-1]&&ba==1)
{
mp[a[n-1]]--;n--;ba--;ans++;
}
else if(n>=2&&t<a[n-2])
{
mp[a[n-1]]--;mp[a[n-2]]--;n-=2;ans++;
}
else
break;
}
else
{
ans++;
ba+=mp[t]-1;
mp[t]--;
}
}
cout<<ans;
return 0;
}

一组 hack 数据为

1
2
6
1 3 4 4 5 5

此时在算到 2 的空缺时,该代码会优先卖掉两个 5,而最优解应该卖掉多余的一个 4 一个 5。此程序的问题在于,他考虑到了优先卖掉重复的,但只判断了优先卖掉 i\le i 的重复的,而没有优先处理 >i>i 的重复。

D. Flip and Adjust

题意:有 nn 张卡片,第 ii 张正面为 aia_i,反面为 bib_i,问是否存在一种翻转方案使得卡片的和为 SS。如果有,输出方案。

dp[i][j]=0/1dp[i][j]=0/1 表示前 ii 张卡片能否得到 jj,则

dp[i][j]=dp[i1][jai] or dp[i1][jbi]dp[i][j]=dp[i-1][j-a_i] \text{ or } dp[i-1][j-b_i]

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
#include<bits/stdc++.h>
using namespace std;
int a[110],b[110];
bool f[110][10010],pre[110][10010];
void get(int now,int x) {
if(now==0) return;
if(pre[now][x])
get(now-1,x-a[now]);
else get(now-1,x-b[now]);
cout<<(pre[now][x]?'H':'T');
}
signed main() {
int n,s;
cin>>n>>s;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i];
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=s;j++) {
if(j>=a[i]&&f[i-1][j-a[i]])
f[i][j]=1,pre[i][j]=1;
if(j>=b[i]&&f[i-1][j-b[i]])
f[i][j]=1,pre[i][j]=0;
}
if(f[n][s]) {
cout<<"Yes"<<endl;
get(n,s);
cout<<endl;
}
else cout<<"No"<<endl;
return 0;
}

E. Subsequence Path

题意:有 nn 个点 mm 条有向有权边 uiwiviu_i\xrightarrow{w_i} v_i 和一个边的编号序列 eie_i,你可以选择编号序列的一个子序列,使得该子序列的边构成一条 1n1\to n 的路径,求最短路径。

直接 DP。令 fi,jf_{i,j} 表示只考虑序列中前 jj 条边,1j1\to j 的最短路径,则

fi,j={min(fi1,j,fi1,uei+wei) if vei=jfi1,j otherwisef_{i,j}=\begin{cases}\min(f_{i-1,j},f_{i-1,u_{e_i}}+w_{e_i})\text{ if }v_{e_i}=j\\f_{i-1,j}\text{ otherwise}\end{cases}

此时第一维可以省略。转移时只需要更新 fvaif_{v_{a_i}} 即可。

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
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct edge {
int u,v,w;
} a[200010];
int e[200010];
int f[200010];
signed main() {
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
cin>>a[i].u>>a[i].v>>a[i].w;
for(int i=1;i<=k;i++)
cin>>e[i];
memset(f,-1,sizeof(f));
f[1]=0;
for(int i=1;i<=k;i++) {
if(f[a[e[i]].u]==-1) continue;
if(f[a[e[i]].v]==-1||f[a[e[i]].v]>f[a[e[i]].u]+a[e[i]].w)
f[a[e[i]].v]=f[a[e[i]].u]+a[e[i]].w;
}
cout<<f[n]<<endl;
return 0;
}

F. XOR on Grid Path

题意:有一个 n×nn\times n 的矩阵,每个格子有一个正整数,每次只能向右走或向下走,问有多少条路径能从 (1,1)(1,1) 走到 (n,n)(n,n),且路径异或和为 00

双向搜索。搜索从 (1,1)(1,1) 到副对角线的所有方案,存到 mapmap 里,再搜索从 (n,n)(n,n) 到副对角线的所有方案,计算答案。

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
#include<bits/stdc++.h>
using namespace std;
int n,a[30][30];
map<int,int> m[2][21];
void dfs(int x,int y,int f,int flag) {
f^=a[x][y];
if(x+y==n+1) m[flag][x][f]++;
else if(flag) dfs(x-1,y,f,flag),dfs(x,y-1,f,flag);
else dfs(x+1,y,f,flag),dfs(x,y+1,f,flag);
}
signed main() {
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
dfs(1,1,0,0);
dfs(n,n,0,1);
long long ans=0;
for(int i=1;i<=n;i++) {
int x=a[i][n+1-i];
for(auto tmp:m[0][i])
ans+=1ll*tmp.second*m[1][i][tmp.first^x];
}
cout<<ans<<endl;
return 0;
}