2022 CCPC 桂林站 (待补题
2022 CCPC 桂林站
A. Lily
直接做。
M. Youth Finale
读题的注意点:给出的是permutation,也就是排列,这点很关键。
首先归并排序求出初始序列的逆序对数目\(tot\_inv\)。下面考虑两种操作:
-
Reverse:
Reverse之后的逆序对数目=原来顺序对的数目
\(tot\_inv=n*(n-1)-tot\_inv\)
-
Shift
将第一个元素移到末尾。设当前的第一个元素为 \(x\),则去除 \(x\) 减少的逆序对数目为 \(x-1\);在末尾加上 \(x\) 产生的新的逆序对数目为 \(n-x\)
\(tot\_inv=tot\_inv-(x-1)+(n-x)\)
实现:考虑用双端队列维护数列(只涉及两端的增删),用一个变量记录当前将哪一端作为开始端或者开两个队列维护(具体见代码)。
复杂度:\(O(nlogn+m)\)。
E. Draw a triangle【exgcd】
Solution1:
设\(w=x_2-x_1,h=y_2-y_1\)
则给定的两个点连线的方程式为:\(y=\frac{h}{w}x+b\),(注意\(w=0\)时斜率不存在,单独讨论)
注意到 \(b=y_1-\frac{h}{w}x_1\),代入得:
\(y=\frac{h}{w}x+(y_1-\frac{h}{w}x_1)\)
经过一番观察之后发现的结论是:将\(h,w\)处理为互质的\(h',w'\)后,答案点所在的一条直线为 \(y=\frac{h'}{w'}x+b+\frac{1}{w'}\)
等号两边同乘\(w’*gcd(h,w)\)得:\(wy=hx+(wy_1-hx_1)+gcd(w,h)\)
整理得:\(w(y-y_1)+h(-x+x_1)=gcd(w,h)\)
换元\(x_3=-x+x_1,y_3=y-y_1\),
则:\(wy_3+hx_3=gcd(w,h)\),求 \(x3,y3\) 的任意解即可。
这是一个标准的\(exgcd\),代入板子就可以求啦。
注意:这里需要考虑 \(w\) 和 \(h\) 的符号,我们让它们都取绝对值,把符号转移到 \(x_3\) 和 \(y_3\) 上。
Solution2:
是tutorial的做法!简洁明了(躺
假设三个点为 \(A,B,C\),其中 \(A,B\) 坐标已知。三角形的面积可以用叉积表示!如下:
\(S=\frac{1}{2}|\vec{AB}\times \vec{AC}|\)
设 \(\vec{AB}=(x,y),\vec{AC}=(u,v)\),则 \(S=\frac{1}{2}|xv-yu|\)(这个式子的证明见末尾
发现绝对值内的\((xv-yu)\)是一个经典的\(exgcd\)的式子,它有解的最小值为\(gcd(x,y)\)。
那么直接用 \(exgcd\) 计算一组特解即可。
对于特殊情况还有符号的处理同 \(Solution1\)。
下面是对\(|(x,y)\times(u,v)|=|xv-yu|\)的证明:
\(|(x,y)\times(u,v)|\)
\(=|\sqrt{x^2+y^2}\sqrt{u^2+v^2}sin\theta|\)
\(=|\sqrt{x^2+y^2}\sqrt{u^2+v^2}\sqrt{1-cos^2\theta}|\)
\(=|\sqrt{x^2+y^2}\sqrt{u^2+v^2}\sqrt{1-(\frac{(x,y)·(u,v)}{\sqrt{x^2+y^2}\sqrt{u^2+v^2}})^2}|\)
\(=\sqrt{(x^2+y^2)(u^2+v^2)-(xu+yu)^2}\)
\(=\sqrt{x^2v^2+y^2u^2-2xyuv}\)
\(=|xv-yu|\)
以后要记得啦!\(|(x,y)\times(u,v)|=|xv-yu|\)
复杂度:\(gcd\) 的复杂度是\(O(logN)\),此处 \(N=10^{18}\)
总复杂度为 \(O(TlogN)=O(5*10^{4}*log10^{18})\approx10^{6}\)
C. Array Concatenation
假设当前序列 \(b\) 的长度为 \(len\),所有元素之和为 \(sum\),前缀和之和为 \(ans\)。
-
若进行操作 \(1\):
则新的前缀和之和\(=ans+(ans+sum*len)\)
理解:\(b'=bb\),则前一个 \(b\) 的答案为 \(ans\),后一个 \(b\) 的答案除了原本的 \(ans\) 以外,每个前缀和都需要加上前面 \(b\) 所有元素之和 \(sum\),所以最终是加上 \(sum*len\)。
-
若进行操作 \(2\):
则新的前缀和之和\(=(sum*(len+1)-ans)+(ans+sum*len)=sum*(2len+1)\)
理解:\(b'=db\)(意思是 \(b\) 倒过来是 \(d\) 哈哈哈哈哈哈哈)
后面一个 \(b\) 的前缀和之和与前一种情况是一样的,为 \(ans+sum*len\)
前面一个\(d\)的前缀和之和为\(sum*(len+1)-ans\),这是因为原本 \(b\) 的每个元素是计算 \(len,len-1,...,1\)次,但是reverse之后变成计算\(1,2,..,len\)次了,发现新的次数就是\(len+1-\)原次数。
一个错误的贪心:注意到上面两个式子,发现\(ans\)越大越好,并且现在的\(ans\)越大,以后的\(ans\)也越大。所以每次看两种操作哪种结果大就选择哪种操作。
错误原因:注意到我们求的\(max\)是模意义下的!所以当前最大的\(max\)并不能保证推出后面的\(max\)。
需要发现新的性质!
注意到,如果做了一次操作\(2\),那么序列就会变成回文的,此后再做操作\(1\)或操作\(2\)都是等价的。
从式子上看,操作\(2\)的答案为\(sum*(2len+1)\),根本与此前的\(ans\)无关。因此,在\(m\)次中的任何一次操作做\(2\),最后都会得到一样的答案。综上所述,最后只有两种可能的答案:①全做操作\(1\)的答案,②至少做了一次操作\(2\)的答案。
计算这两个答案并且选大的作为最终答案即可。
Code
A. Lily
//by dttttttt 2023/4/16
#include<iostream>
#include<cstring>
using namespace std;
int n;
string s;
bool check(int i) {
if (i < n - 1 && s[i + 1] == 'L') return 0;
if (i > 0 && s[i - 1] == 'L') return 0;
return 1;
/*if (i == 0 && n > 1 && s[i + 1] != 'L') return 1;
if (i == n - 1 && n > 1 && s[i - 1] != 'L') return 1;
if (n == 1) return 1;
return 0;*/
}
int main() {
cin >> n >> s;
for (int i = 0;i < n;++i) {
if (s[i] == 'L') continue;
if (check(i))
s[i] = 'C';
}
cout << s << endl;
return 0;
}
M. Youth Finale
//by dttttttt 2023/4/16
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<deque>
#define ll long long
using namespace std;
const int N = 3e5 + 5, mod = 10;
int n, m, a[N], tmp[N];
ll tot_inv;
deque<int> q[2];
string s;
void merge_sort(int l, int r) {
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int i = l, j = mid + 1;
for (int k = l;k <= r;++k) {
if (i > mid || (j <= r && a[i] > a[j]))
tot_inv = tot_inv + mid - i + 1, tmp[k] = a[j++];
else
tmp[k] = a[i++];
}
for (int k = l;k <= r;++k)
a[k] = tmp[k];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1;i <= n;++i)
scanf("%d", &a[i]), q[0].push_back(a[i]), q[1].push_front(a[i]);
merge_sort(1, n);
printf("%lld\n",tot_inv);
tot_inv %= 10;
cin >> s;
int t = 0;
for (int i = 0;i < m;++i) {
if (s[i] == 'S') {
int x = q[t].front();
tot_inv = ((tot_inv - (x - 1) + n - 1 - (x - 1)) % mod + mod) % mod;
q[t].pop_front();
q[t].push_back(x);
q[1 - t].pop_back();
q[1 - t].push_front(x);
}
else {
tot_inv = ((1ll * n * (n - 1) % mod - tot_inv) % mod + mod) % mod;
t = 1 - t;
}
printf("%lld", tot_inv);
}
return 0;
}
E. Draw a triangle【exgcd】
//by dttttttt 2023/4/16
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<deque>
#define ll long long
using namespace std;
ll exgcd(ll a, ll b, ll& x, ll& y)
{
if (!b) { x = 1;y = 0;return a; }
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
ll x1, x2, y1, y2;
scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
ll x3 = 0, y3 = 0;
if (x1 == x2) {
printf("%lld %lld\n", x1 + 1, y1);
continue;
}
if (y1 == y2) {
printf("%lld %lld\n", x1, y1 + 1);
continue;
}
exgcd(abs(x1 - x2), abs(y1 - y2), y3, x3);
int tag1 = x2 - x1 < 0 ? (-1) : 1;
int tag2 = y2 - y1 < 0 ? (-1) : 1;
printf("%lld %lld\n", -1 * tag1 * x3 + x1, tag2 * y3 + y1);
}
return 0;
}
C. Array Concatenation
//by dttttttt 2023/4/16
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int N = 1e5 + 5, mod = 1e9 + 7;
int n, m, a[N], sum[N], sum_sum, len, tot, ans;
int main() {
scanf("%d%d", &n, &m);
len = n;
for (int i = 1;i <= n;++i) {
scanf("%d", &a[i]);
sum[i] = (sum[i - 1] + a[i]) % mod;
sum_sum = (sum_sum + sum[i]) % mod;
}
tot = sum[n];
int x1;
while (m--) {
x1 = 1ll * tot * (2 * len + 1) % mod;
sum_sum = (1ll * sum_sum * 2 % mod + 1ll * tot * len % mod) % mod;
tot = 2ll * tot % mod;
len = 2ll * len % mod;
}
printf("%d\n", max(x1, sum_sum));
return 0;
}
一些掉坑细节&方法小结!
易错点
- 在忽略细节地推导出总体答案之后,一定要从头再严谨推一遍,考虑到细节、特殊情况、边界情况。
- 对于可能出现负数的取模答案,应该这样:$(x%mod+mod)%mod $ !
- 注意数据范围,开 \(long\ long\)
Tips
- 在Wrong answer又怎么都找不到错误的时候,重新读题总是没错的!