2022 CCPC 桂林站 (待补题

2022 CCPC 桂林站

Tutorial

A. Lily

直接做。

M. Youth Finale

读题的注意点:给出的是permutation,也就是排列,这点很关键。

首先归并排序求出初始序列的逆序对数目\(tot\_inv\)。下面考虑两种操作:

  1. Reverse:

    Reverse之后的逆序对数目=原来顺序对的数目

    \(tot\_inv=n*(n-1)-tot\_inv\)

  2. 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. 若进行操作 \(1\)

    则新的前缀和之和\(=ans+(ans+sum*len)\)

    理解:\(b'=bb\),则前一个 \(b\) 的答案为 \(ans\),后一个 \(b\) 的答案除了原本的 \(ans\) 以外,每个前缀和都需要加上前面 \(b\) 所有元素之和 \(sum\),所以最终是加上 \(sum*len\)

  2. 若进行操作 \(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;
}

一些掉坑细节&方法小结!

易错点

  1. 在忽略细节地推导出总体答案之后,一定要从头再严谨推一遍,考虑到细节、特殊情况、边界情况。
  2. 对于可能出现负数的取模答案,应该这样:$(x%mod+mod)%mod $ !
  3. 注意数据范围,开 \(long\ long\)

Tips

  1. 在Wrong answer又怎么都找不到错误的时候,重新读题总是没错的!
posted @ 2023-04-16 16:10  DTTTTTTT-  阅读(74)  评论(0)    收藏  举报