07.12&07.14模拟赛总结
前言:
这是最戏剧性的一集,两场都是同级第一,只不过一场正数、一场倒数。
07.12——Day 1
T1
P8093 [USACO22JAN] Searching for Soulmates S
正解
以下除以 $2$ 操作、加 $1$ 操作、乘以 $2$ 操作分别为操作 1、2、3。
思考发现,如果我们先前一直进行操作 1 和操作 2,那等到我们再进行操作 3 时,就不会再进行操作 1,(因为不会更优)。
换句话说,我们操作的步骤大约分为两个部分:
- 前半段:进行操作 1、2
- 后半段:进行操作 2、3
容易想到处理出前后半段中间的一个中间值 $t$。
由于后半段的操作不是乘以 $2$ 就是加 $1$,所以这个 $t$ 一定是 $b$ 的前缀(二进制中)。
那就很清晰了,直接枚举 $t$,在记录步数即可。
代码
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
|
#include <bits/stdc++.h>
using namespace std;
#define int long long
int d(int x) {
for (int i = 63; i >= 0; --i)
if (x >> i)
return i + 1;
return 0;
}
int get(int num, int x, int len) { return num >> (len - x); }
void solve() {
int a, b;
scanf("%lld%lld", &a, &b);
if (a == b) {
puts("0");
return;
}
int len = d(b);
int ans = 0x3f3f3f3f;
for (int i = 1; i <= len; i++) {
int cnt = 0;
int t = get(b, i, len);
int ta = a;
while (ta != t) {
if (ta > t) {
if (ta & 1)
++ta;
else
ta /= 2;
++cnt;
} else {
cnt += t - ta;
ta = t;
}
}
for (int j = i + 1; j <= len; j++) {
t = get(b, j, len);
ta <<= 1;
++cnt;
if (t != ta)
++ta, ++cnt;
}
ans = min(ans, cnt);
}
printf("%lld\n", ans);
}
signed main() {
int n;
scanf("%lld", &n);
while (n--) solve();
return 0;
}
|
T2
P8094 [USACO22JAN] Cow Frisbee S
正解
直接枚举、用栈维护即可。
代码
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
|
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 3e5 + 5;
int n, ans;
int h[MAXN], x[MAXN];
stack<int> s;
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &h[i]), x[h[i]] = i;
for (int i = 1; i <= n; i++) {
while (!s.empty()) {
int t = s.top();
ans += abs(x[h[i]] - x[t]) + 1;
if (h[i] <= t)
break;
s.pop();
}
s.push(h[i]);
}
printf("%lld\n", ans);
return 0;
}
|
T3
P8095 [USACO22JAN] Cereal 2 S
正解
对于这道题,一眼二分图最大匹配,直接匈牙利带走。
结果发现 $\Omicron(nm)$ 把匈牙利带走。
但是没有关系!直接建超级源点和超级汇点,dinic 网络流直接 $\Omicron(\sqrt{n}m)$ 带走。
但是,这样非常难打,而且在输出排队顺序是也异常繁琐,可以说是拿考场时间换代码运行时间了……
怎么办呢?还好这场比赛这道题最难,所以应该没有人会 A,打点部分分也是不错的选择【大拇指d( ̄▽ ̄)b】。
实际上这道题数据很水,匈牙利+卡常+优秀的代码逻辑是可以 A 的。
至于输出方案,这个想想就出来了,这里不做过多赘述。
代码
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
|
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int MAXN = 2e6 + 5;
int n, m;
int a[MAXN];
int f[MAXN], s[MAXN];
int lk[MAXN], ilk[MAXN];
bool vis[MAXN];
vector<int> v[MAXN], vec[MAXN];
bool dfs(int x) {
for (auto i : v[x]) {
if (!vis[i]) {
vis[i] = 1;
if (!lk[i] || dfs(lk[i])) {
lk[i] = x, ilk[x] = i;
vis[i] = 0;
return 1;
}
}
}
return 0;
}
int MXMC() {
int re = 0;
for (int i = 1; i <= n; i++) re += dfs(i);
return re;
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lld%lld", &f[i], &s[i]);
v[i].pb(f[i]), v[i].pb(s[i]);
vec[f[i]].pb(i);
}
int ans = MXMC();
printf("%lld\n", n - ans);
queue<int> q;
for (int i = 1; i <= n; i++) {
if (ilk[i] != f[i])
continue;
printf("%lld\n", i);
for (auto j : vec[f[i]]) {
if (ilk[j] == s[j])
q.push(j);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
printf("%lld\n", u);
for (int j : vec[s[u]]) {
if (ilk[j] == s[j])
q.push(j);
}
}
for (int i = 1; i <= n; i++)
if (!ilk[i])
printf("%lld\n", i);
return 0;
}
|
总结
$8+27+7=42/rk12$
同级倒一、直接趋势。
总结发现是答题时心态没有调整好,导致陷入思维怪圈,进一步导致简单题做不对、难题不会做的情况。
07.13——Day 2
原题检测,没打比赛。
一早上复习,复习了上一周打过的模拟赛。幸好我都写了总结、方便复习。
结果:
我:(开始啦开始啦!)咦?这个“旅行商简化版”是个什么东西?
隔壁的新初一:好像是我们作业里的一道题。
隔壁同级:教练,这个我们要做吗?
教练:新初三的也要做,注意一下啊。
新初三的我们:……
顺提,我们原题检测里的题,一题没A跑2圈……
但成绩挺可观的:
$100+100+52+100+71+0=423/rk2$
好吧,跑6圈。遗憾的是期望得到 $500$ 分,可惜有两题打挂了……
07.14——Day 3
T1
P9186 [USACO23OPEN] Milk Sum S
正解
容易想到肯定先排序嘛,那么就会得到新数组。
发现用二分找到查询数据在新数组位置时,答案的增加与减少可以用一个后缀和来搞定。(当然,前缀和应该没问题,可是赛时我先想到了后缀和)。
那就很容易做了,二分可以写 lower_bound
,减少码量。因为近期用 set
用得比较多,所以我就干脆丢进了 set
里进行 lower_bound
操作。
代码
好不容易第一次赛时切绿,这里放一下我的赛时代码(无格式化、码风为本人码风)
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
79
80
81
82
|
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=2e5+5,INF=1e9;
int n,ans;
struct node
{
int x,id;
bool operator<(const node &T)const
{
if(x!=T.x)
return x<T.x;
return id<T.id;
}
}a[MAXN],b[MAXN];
int sum[MAXN];
multiset<node> s;
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i].x);
b[i].x=a[i].x;
b[i].id=i;
}
sort(a+1,a+n+1);
for(int i=n;i>=1;i--)
{
ans+=a[i].x*i;
sum[i]=sum[i+1]+a[i].x;
a[i].id=i;
s.insert(a[i]);
}
s.insert({-1,0}),s.insert({INF,0});
int tmp=ans,Q;
scanf("%lld",&Q);
while(Q--)
{
ans=tmp;
int x,y;
scanf("%lld%lld",&x,&y);
auto it=s.lower_bound({b[x].x,0});
node t=*it;
ans-=t.x*t.id;
auto it2=s.upper_bound({y,0});
node t2=*it2;
if(t2.x==INF)
{
ans-=sum[t.id+1];
ans+=y*n;
printf("%lld\n",ans);
continue;
}
if(t2.id==t.id)
{
ans+=y*t.id;
printf("%lld\n",ans);
continue;
}
if(t2.id<t.id)
{
ans+=sum[t2.id]-sum[t.id];
ans+=y*t2.id;
}
else
{
ans+=y*(t2.id-1);
ans-=sum[t.id+1]-sum[t2.id];
}
printf("%lld\n",ans);
}
return 0;
}
|
T2
P9187 [USACO23OPEN] Field Day S
正解
这个一看就是可以转成二进制,比较就是异或操作了。
这是个很好的思路,如果赛时没想到的话那就是能力问题了。
因为他要求找最大不同,那脑海中可以有这样的思路:
$$
最大不同\to 最小相同\to 可以对其一取反\to 最小不同
$$
那就很容易了,我们可以用一个非常简单的dp实现:
设 $f_i$ 为得到 $i$ 的最小不同,最后输出答案时就是
$$
c-f_{(2^c-1) \oplus 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
26
27
28
29
30
31
32
|
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e6 + 5;
int n, c;
int a[MAXN];
int f[MAXN];
signed main() {
scanf("%lld%lld", &c, &n);
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= c; j++) {
char ch;
cin >> ch;
a[i] <<= 1;
a[i] += (ch == 'G');
}
f[a[i]] = 0;
}
for (int j = 1; j <= c; j++) {
for (int i = 1; i <= (1 << c) - 1; i++) f[(1 << (j - 1)) ^ i] = min(f[(1 << (j - 1)) ^ i], f[i] + 1);
}
for (int i = 1; i <= n; i++) {
int ans = c - f[((1 << c) - 1) ^ a[i]];
printf("%lld\n", ans);
}
return 0;
}
|
T3
P9188 [USACO23OPEN] Pareidolia S
正解
考虑DP。
设 $f_i$ 为这样一个 $[1,i]$ 的区间出现 bessie
的个数。
怎么转移呢?可以利用一下辅助数组 $lst_i$,其中 $i\in [1,6]$。
这是什么?
如果出现了一个序列 bessie
,那第 $i$ 位前面的 bessie
的起始位(b
的位置)
例如 bessie
,那么 $lst_i=1 ; (1\leq i\leq 6)$
那么考虑转移 $f$:
提供贡献的只有:
- 前面的转移过来
- 当前贡献了多少
1 情况怎么处理呢?显然是 $f_{lst_6-1}$
2 情况呢?因为当前 bessie
的出现,所以对于 $[l,lst_6] ; (l\in [1,lst_6])$ 这些区间,答案都会 +1,所以转移量就是 $lst_6$
综上,
$$
f_i=f_{lst_6-1}+lst_6
$$
因为答案是所有区间,所以答案就为:
$$
\sum_{i=1}^n f_i
$$
代码
有些许改动:
($dp_i \to f_i$)
($f_i \to lst_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
33
|
#include <cstring>
#include <cstdio>
using namespace std;
#define int long long
const int MAXN = 3e5 + 5;
char s[MAXN];
int f[10],dp[MAXN];
signed main() {
scanf("%s",s+1);
int n=strlen(s+1);
// bessie
for(int i=1;i<=n;i++)
{
if(s[i]=='b')
f[1]=i;
if(s[i]=='e')
f[6]=f[5],f[2]=f[1];
if(s[i]=='s')
f[4]=f[3],f[3]=f[2];
if(s[i]=='i')
f[5]=f[4];
dp[i]=dp[f[6]-1]+f[6];
}
int ans=0;
for(int i=1;i<=n;i++)
ans+=dp[i];
printf("%lld\n",ans);
return 0;
}
|
总结
$100+25+17=142/rk1$
这次 $rk1$ 不只是同级,还是同机房。
再接再厉。