CF1549
Codeforces Round #736 (Div. 2)
D. Integers Have Friends
题意
给定一个正整数数列 ,它的连续子数列为
求一个最长的连续子数列,,使 ,也就是它们在模 的意义下同余
思路
可以从同余的定义上思考,
于是我们就可以写出上面同余式的等价式:
进一步有:
我们可以将 直接取成它们的最大公约数
则当 时,一定存在 满足题意
令 ,下面讨论针对数列 进行
那么问题转换为查找一个最长区间,使得这个区间的数的最大公约数大于等于
我们可以想想如何快速求解一个区间的最小公约数,类比求区间最小值
我们发现一个区间的最小公约数也是具有可拆分性的,
也就是说 ,
于是可以利用线段树,ST表在 下求解任意区间的
那么我们就可以直接枚举左端点 ,然后二分最大区间长度 ,使 中的数
答案就是全局最大的区间长度
如果是线段树实现chk函数:时间复杂度 ,二分复杂度一个 ,chk函数又一个
如果是ST表实现chk函数:时间复杂度 ,第一项是二分的复杂度,后一项是预处理ST表的复杂度
下面代码我是用ST表实现的
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
const int N = 2e5 + 10;
int n;
int f[N][20], Log[N];
int a[N];
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
void pre() {
Log[0] = -1;
for (int i = 1; i < N; i++) {
Log[i] = Log[i/2] + 1;
}
}
bool chk(int l, int r) {
int t = Log[r - l + 1];
return gcd(f[l][t], f[r - (1 << t) + 1][t]) > 1;
}
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
pre();
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i < n; i++) {
f[i][0] = a[i+1] - a[i];
f[i][0] = abs(f[i][0]);
}
for (int j = 1; j < 20; j++) {
for (int i = 1; i + (1 << j) - 1 < n; i++) {
f[i][j] = gcd(f[i][j-1], f[i+(1<<(j-1))][j-1]);
}
}
int ans = 1;
for (int i = 1; i < n; i++) {
int l = 1, r = n - i;
while (l <= r) {
int mid = (l+r) >> 1;
if (chk(i, i + mid - 1)) l = mid+1;
else r = mid-1;
}
ans = max(ans, r+1);
}
cout << ans << '\n';
for (int i = 0; i <= n; i++) {
for (int j = 0; j < 20; j++) {
f[i][j] = 0;
}
a[i] = 0;
}
}
return 0;
}
E. The Three Little Pigs
题意
给定 ,有 次询问
每次询问给出一个 ,对于每个 求解的值
注:默认当 时,
思路
法一
由于 中 是间断的,所以我们考虑先把它补全,以充满 整个区间,这里补全的技巧很高,通过模 的最小剩余系中的元素将它补全
令
那么对于询问 ,
则有第一个方程:
这里使用了曲棍球恒等式(Hockey Stick Identity),由于这个数字选择在杨辉三角中的形状像一个曲棍球,也应此得名


然后我们又可以通过二项式递推式 得到另外两个方程:
通过这三个方程组的联立,解得
于是就可以愉快的递推了
注意:一定要 预处理组合数, 也要预处理,不然 这个常数可不小
预处理方法可以见代码
大致思路是先求 ,再求 ,最后通过 线性求出每个阶乘的逆元
总复杂度
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 1e9 + 7;
const int N = 3e6 + 10;
int n, Q;
int jie[N], invjie[N];
int ksm(int a, int b) {
int ret = 1;
while (b) {
if (b & 1) ret = (ret * a) % P;
a = (a * a) % P;
b >>= 1;
}
return ret;
}
int inv(int x) {
return ksm(x, P-2);
}
int inv3;
void pre() {
inv3 = inv(3);
jie[0] = 1;
for (int i = 1; i < N; i++) {
jie[i] = (jie[i-1] * i) % P;
}
invjie[N-1] = inv(jie[N-1]);
for (int i = N-2; i >= 0; i--) {
invjie[i] = (invjie[i+1] * (i+1)) % P;
}
}
int C(int n, int m) {
if (n < m) return 0;
return jie[n] * invjie[m] % P * invjie[n-m] % P;
}
int f[N][2];
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
memset(f, -1, sizeof(f));
pre();
cin >> n >> Q;
for (int i = 0; i <= 3*n; i++) {
if (i == 0) {
f[i][0] = f[i][1] = n;
} else {
f[i][0] = (((C(3*n, i+1) - 2*f[i-1][0] - f[i-1][1]) % P) + P) % P * inv3 % P;
f[i][1] = (f[i][0] + f[i-1][0]) % P;
}
}
while (Q--) {
int x;
cin >> x;
cout << (f[x][0] + C(3*n, x)) % P << '\n';
}
return 0;
}
法二
利用多项式系数,直接构造答案
令多项式
那么对于询问 , 就是 的系数
问题转换为解救 多项式,如果用FFT还是会超时,再观察可以看出来它是一个等比数列求和
利用等比数列求和公式,先消去常数项,然后上下项同时除以 ,得
那么问题转换为:多项式长除法,直接模拟这个过程即可,同样需要预处理组合数,降低复杂度
时间复杂度
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 1e9 + 7;
const int N = 3e6 + 10;
int n, Q;
int jie[N], invjie[N];
int ksm(int a, int b) {
int ret = 1;
while (b) {
if (b & 1) ret = (ret * a) % P;
a = (a * a) % P;
b >>= 1;
}
return ret;
}
int inv(int x) {
return ksm(x, P-2);
}
int inv3;
void pre() {
inv3 = inv(3);
jie[0] = 1;
for (int i = 1; i < N; i++) {
jie[i] = (jie[i-1] * i) % P;
}
invjie[N-1] = inv(jie[N-1]);
for (int i = N-2; i >= 0; i--) {
invjie[i] = (invjie[i+1] * (i+1)) % P;
}
}
int C(int n, int m) {
if (n < m) return 0;
return jie[n] * invjie[m] % P * invjie[n-m] % P;
}
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
pre();
cin >> n >> Q;
vi num(3*n+3);
//消去常数项,上下同除k
for (int i = 0; i < 3*n+3; i++) {
num[i] = C(3*n+3, i+1);
if (i <= 2) {
num[i] = (num[i] - C(3, i+1) + P) % P;
}
}
vi ans(3*n+1);
//ans[i]就是P(k)中k^i的系数,下面做长除法
for (int i = 3*n; i >= 0; i--) {
ans[i] = num[i+2];
num[i+1] = (num[i+1] - 3*ans[i] + 3*P) % P;
num[i] = (num[i] - 3*ans[i] + 3*P) % P;
}
while (Q--) {
int x;
cin >> x;
cout << ans[x] << '\n';
}
return 0;
}
F1. Gregor and the Odd Cows (Easy)
题意
给出 个栅栏柱,每个栅栏柱的坐标都是整数并且保证是偶数,三个栅栏柱可以围成一个封闭三角形,被封闭三角形所包围的节点数为奇数个,并且要求三角形的面积为整数,求一共有多少种这样的三角形。
前置芝士
Pick定理
在网格图上的简单多边形的面积 有如下公式
其中, 为网格中在多边形内部的节点数, 为多边形边上的格点数
Shoelace公式 (鞋带公式)
令简单多边形的顶点坐标分别为
则,该简单多边形的面积为:
这个我是用外积理解的(内部的行列式),具体证明:维基百科 Shoelace formula
它就像的计算关系就像“系鞋带”一样
思路
如果没有Pick定理真的一点思路都没有
由于题目保证了坐标都是偶数,则由Shoelace公式(外积求三角形面积)知,三角形的面积一定是整数,并且是偶数
题目又要求内部点的个数为奇数,再通过Pick定理 ,则 为奇数,故 为偶数
进一步有:
于是问题就转换为求解三角形的边上格点数目为4的倍数
我们考虑 这条边,
则这条边上的格点数目一定为 ,可以通过画简单的示意图得出
则
于是,问题可以转换为 意义下,将所有的 坐标都对4取模,然后求满足上式的方案数
又由于 都是偶数,所以
所以模4意义下的坐标一共也就4种,可以直接枚举出3个坐标,然后判断是否满足上式,统计答案,就OK了
时间复杂度
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
int to[4][2] = {
{0, 0}, {2, 0}, {0, 2}, {2, 2}
};
int calc(int i, int j) {
if (abs(to[i][0] - to[j][0]) == 2 || abs(to[i][1] - to[j][1]) == 2) {
return 2;
}
return 0;
}
int C(int n, int m) {
if (m == 2) {
return n * (n-1) / 2;
} else {
return n * (n-1) * (n-2) / 6;
}
}
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vii cnt(4, vi(4));
for (int i = 0; i < n; i++) {
int x, y;
cin >> x >> y;
cnt[x%4][y%4]++;
}
int ans = 0;
for (int i = 0; i < 4; i++) {
for (int j = i; j < 4; j++) {
for (int k = j; k < 4; k++) {
int b = (calc(i, j) + calc(j, k) + calc(k, i)) % 4;
if (b == 0) {
if (i == j && j == k) {
ans += C(cnt[to[i][0]][to[i][1]], 3);
} else if (i == j || j == k) {
int same = (i == j) ? i : j;
int diff = i + j + k - same*2;
ans += C(cnt[to[same][0]][to[same][1]], 2) * cnt[to[diff][0]][to[diff][1]];
} else {
ans += cnt[to[i][0]][to[i][1]] * cnt[to[j][0]][to[j][1]] * cnt[to[k][0]][to[k][1]];
}
}
}
}
}
cout << ans << '\n';
return 0;
}