『模拟赛』信友队2024CSP-S第二轮(复赛)模拟赛
Rank
意外地好
A. 坦白
签。
首先对 \(m=0\) 很好求,正着跑一遍就行。接着考虑 \(m\lt 0\) 时什么时候遗忘会更优。发现是 \(\oplus\) 操作,因此答案为偶时(即事件为奇时)遗忘会使答案 +1。为判断是否比原先优,我们提前处理出后缀和即可。这题关键在想出一个性质,\(m=i\) 是由 \(m=i-1\) 时的最优解转移来的,即遗忘的顺序对答案没有影响,那么直接正着跑一遍找最优答案即可。然后容易得出事件数量为奇时 \(m=n\) 的答案为 1,为偶时答案为 0,然后随着 \(m\) 的下降我们跑完从以这两个值为起点倒着不断 +2 知道最大值然后重复即可。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x) ++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x) --)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
lx x = 0, f = 1; char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
return x * f;
}
#undef lx
#define qr qr()
const int Ratio = 0;
const int N = 3e5 + 5;
int n;
int sum[N], mus[N];
string s;
stack<int> st;
namespace Wisadel
{
short main()
{
freopen("confess.in", "r", stdin) , freopen("confess.out", "w", stdout);
int T = qr;
while(T--)
{
cin >> s; n = s.size(); s = " " + s;
mus[n + 1] = 0;
fo(i, 1, n)
if(s[i] == '+') sum[i] = sum[i - 1] + 1;
else sum[i] = sum[i - 1] - 1;
fu(i, n, 1)
if(s[i] == '+') mus[i] = mus[i + 1] + 1;
else mus[i] = mus[i + 1] - 1;
printf("%d ", sum[n]);
int now = 0, tim = 0, fla = 0, maxx = sum[n];
fo(i, 1, n)
{// 跑最大值
if(now % 2 == 0 && now + 1 + mus[i + 1] > maxx)
{
now++, tim++;
maxx = now + mus[i + 1];
if(i == n) fla = 1;
printf("%d ", now + mus[i + 1]);
}
else if(s[i] == '+') now++;
else now--;
}
// 分情况跑完剩下的
if(n % 2 == 0)
{
int zc = 0, mit = 0;
while(now < 0 || zc < now)
{
st.push(zc), mit++;
if(now >= 0) zc += 2;
else now += 2;
}
while(mit + tim < n) st.push(now), mit++;
while(st.size()) printf("%d ", st.top()), st.pop();
}
else
{
int zc = 1, mit = 0;
while(now < 0 || zc < now)
{
st.push(zc), mit++;
if(now >= 0) zc += 2;
else now += 2;
}
while(mit + tim < n) st.push(now), mit++;
while(st.size()) printf("%d ", st.top()), st.pop();
}
puts("");
}
return Ratio;
}
}
int main(){return Wisadel::main();}
B. 秘密
不难,也算是签。
首先比较一眼的是两边往中间跑,拿到线索往两边跑是很优的,那么关键就来到了第一个拿到线索的人如何行动。手模发现站着不动总不比往左往右更优的那一个优,所以就化简成只有两种情况了。首先第一段(只有一个人有线索时)是一定要跑完的,跑完之后对于任意相邻的有线索向无线索人传递的时间都是 \(\frac{dis_{u,v}}{2}\) 的,把全部时间加和后合并,应该是 $\frac{第一个有线索的人到最另一端的距离}{2} $,那么答案就是传递到两侧较大的那一个。用 set 维护每个人的坐标,枚举两种情况的答案取优即可。
记得特殊处理第一个有线索的人是边界的情况。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x) ++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x) --)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
lx x = 0, f = 1; char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
return x * f;
}
#undef lx
#define qr qr()
const int Ratio = 0;
const int N = 2e5 + 5;
int n, m;
set<int> st;
set<int>::iterator it1, it2;
namespace Wisadel
{
short main()
{
freopen("secret.in", "r", stdin) , freopen("secret.out", "w", stdout);
n = qr, m = qr;
fo(i, 1, n)
{
int a = qr;
st.insert(a);
}
fo(i, 1, m)
{
char op; cin >> op; int x = qr;
if(op == '+') st.insert(x);
else if(op == '-') st.erase(x);
else
{
int fir, ed;
it1 = st.begin(); fir = *it1;
it1 = st.end(); it1--; ed = *it1;
it1 = it2 = st.lower_bound(x);
double ans, zc1, zc2;
if(it1 == st.begin())// 最左
ans = 1.0 * (ed - x) / 2;
else
{
it1--, it2++;
if(it2 == st.end())// 最右
ans = 1.0 * (x - fir) / 2;
else
{// 中间,向左向右取优
zc1 = 1.0 * (*it1 - fir) / 2;
zc2 = 1.0 * (ed - x) / 2;
ans = 1.0 * (x - *it1) / 2 + max(zc1, zc2);
zc1 = 1.0 * (x - fir) / 2;
zc2 = 1.0 * (ed - *it2) / 2;
ans = min(ans, 1.0 * (*it2 - x) / 2 + max(zc1, zc2));
}
}
printf("%.2lf\n", ans);
}
}
return Ratio;
}
}
int main(){return Wisadel::main();}
C. 潜力值
逆天 dXqwq csp-s 放 GF 题。
赛时由于边打边摆只打了全排列的 10pts
D. 括号
结论题。
赛时由全部匹配的括号序列启发,发现最多可以配对 \(\lfloor{}\frac{匹配个数}{2}\rfloor{}\) 个,原因是先手玩家首次取一种括号,后手取与之不同的括号,最多可以取 \(\lceil{}\frac{匹配个数}{2}\rceil{}\) 个那种括号,因此只剩 \(\lfloor{}\frac{匹配个数}{2}\rfloor{}\) 个给先手选。而一种括号严格多的序列,如 ()(())))))
,则先手一定能配对 \(\lceil{}\frac{匹配个数}{2}\rceil{}\) 个。那么思路是将整个形如 ()(()))))(())()(((())()
的序列拆成 ()(()))))
,(())()
,(((())()
三段,第一、三段和在一起看,求出最后结果应该是 \(\lceil{}\frac{ans_1+ans_3}{2}\rceil{}+\lfloor{}\frac{ans_2}{2}\rfloor{}\),但是只有 75pts。如果有大蛇能给出反例请评论留言,感激不尽。
点击查看代码(75pts)
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x) ++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x) --)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
lx x = 0, f = 1; char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
return x * f;
}
#undef lx
#define qr qr()
const int Ratio = 0;
const int N = 2e5 + 5;
int n;
string s;
stack<int> st;
namespace Wisadel
{
short main()
{
freopen("bracket.in", "r", stdin) , freopen("bracket.out", "w", stdout);
int T = qr;
while(T--)
{
cin >> s;
n = s.size();
s = " " + s;
while(st.size()) st.pop();
int ans = 0, s1 = 0, s2 = 0, l = 0, r = n + 1;
fo(i, 1, n) if(s[i] != ')'){l = i; break;}
fu(i, n, 1) if(s[i] != '('){r = i; break;}
s = s.substr(l, r - l + 1);
n = s.size();
s = " " + s;
fo(i, 1, n)
{
if(s[i] == '(') st.push(i), s1++;
else
{
s2++;
if(st.size()) st.pop(), ans++;
}
}
while(st.size()) st.pop();
if(s1 == s2) ans = ans / 2;
else
{
int z1 = 0, z2 = 0, now = 0, pzuo = 0, pyou = 0, canl = 1, canr = n;
fo(i, 1, n)
{
if(s[i] == '(') st.push(i), z1++;
else
{
z2++;
if(st.size()) st.pop(), now++;
else canl = i + 1, pzuo = now;
}
}
while(st.size()) st.pop();
z1 = 0, z2 = 0, now = 0;
fu(i, n, 1)
{
if(s[i] == ')') st.push(i), z2++;
else
{
z1++;
if(st.size()) st.pop(), now++;
else canr = i - 1, pyou = now;
}
}
int zc = ans - pzuo - pyou;
ans = zc / 2 + (pzuo + pyou + 1) / 2;
}
printf("%d\n", ans);
}
return Ratio;
}
}
int main(){return Wisadel::main();}
正解需要“足够好的观察力”,结论没什么技术含量,直接挂了。
末
本来想着是打着玩,然后变成了玩着打。
T1 T2 确实是玩着玩着就出来了,然后 T3 直接整不会了,憋了好久决定跳过,然后 T4 一直猜,猜过了大样例,拿了 75pts,还挺好。
感觉没什么很强的人在打信友队啊,这难度大蛇们不应该直接 AK 了?现在还是只有两个人过 T3。
一天打三场模拟赛确实累,下次还打。
完结撒花~
没图了,挂个丁真