2022NOIPA层联测9
A. 泰山压顶
不会叉积,考场死活写不对 \(check\) 还干到 \(11:30\),心态异常炸裂。。。
考虑把成龙当做原点,以阿福为极坐标轴方向,建立极坐标系
发现选择的点集一旦确定,那么他们的连接顺序必然是在极坐标系上转了一圈,于是 \(DP\) 转移就没有环了
按照极坐标系的角度排序
考虑 \(f_{i, j}\) 表示最后选择 \(i\), 倒数第二个选择 \(j\) 的方案数
转移考虑三个限制
- 阿福必选
于是初始状态设在他那里
- 选出的点围成的图形需要包住原点
发现只要相邻的转移点的极角差小于 \(\pi\) 即可
- 泰山顶点角度需要小于\(\pi\)
您需要简单看看叉积
我不会那玩意,于是考场写不出正确的 \(check\),直线解析式没有出路
于是就能够转移了。
code
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; bool f = 0; char c = getchar();
while(!isdigit(c))f = c == '-', c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
const int maxn = 305;
const int mod = 1e9 + 7;
const double PI = 3.14159265358979;
const double eps = 1e-10;
int xa, ya, xb, yb, n, p;
struct dir{
ll a, b;
friend ll operator * (const dir &x, const dir &y){return x.a * y.a + x.b * y.b; }
friend dir operator - (const dir &x, const dir &y){return {x.a - y.a, x.b - y.b}; }
}d[maxn], e;
double abs(const dir &x){return sqrt(x.a * x.a + x.b * x.b);}
double eg(const dir &x, const dir &y){
double now = (x * y) / abs(x) / abs(y);
return acos(now);
}
int f[maxn][maxn];
struct zb{
double the;int id;
friend bool operator < (const zb &x, const zb &y){return x.the < y.the;}
}z[maxn];
void add(int &x, int y){x += y; x = x >= mod ? x - mod : x;}
double Dis(double x, double y){return (x * x + y * y);}
bool check(int ii, int jj, int kk){
if(jj == 1)return true;
int i = z[ii].id, k = z[kk].id, j = z[jj].id;
// dir a = d[i] - d[k], b = d[j] - d[k];
dir a = d[i] - d[j], b = d[k] - d[j];
double cj = a.a * b.b - a.b * b.a;
return cj <= -eps;
}
int main(){
n = read();
xa = read(), ya = read(), xb = read(), yb = read();
d[1] = {xa - xb, ya - yb};
for(int i = 1; i <= n; ++i){d[i + 1].a = read() - xb; d[i + 1].b = read() - yb;}
e = {1, 0};
for(int i = 1; i <= n + 1; ++i){
z[i].id = i;
if(d[i].b >= 0) z[i].the = eg(d[i], e);
else z[i].the = PI * 2 - eg(d[i], e);
}
for(int i = 2; i <= n + 1; ++i){
z[i].the -= z[1].the;
if(z[i].the <= 0 + eps)z[i].the = PI + PI + z[i].the;
}
z[1].the = 0;
sort(z + 1, z + n + 2);
f[1][1] = 1;
int ans = 0;
for(int i = 2; i <= n + 1; ++i){
for(int j = i - 1; j >= 1; --j){
if(z[i].the - z[j].the > PI - eps)break;
for(int k = j; k >= 1; --k){
if(z[j].the - z[k].the > PI - eps)break;
if(check(k, j, i))add(f[i][j], f[j][k]);
}
if(z[i].the > PI + eps && check(j, i, 1))add(ans, f[i][j]);
}
}
printf("%d\n",ans);
return 0;
}
B. 调料瓶
发现我们只关注比值,于是三元组 \((x, y, z)\) 可以变成二元组 \((x / (x + y + z), y / (x + y + z))\)
把他看成点,于是两个点可以凑出他们连成的直线,多个点可以凑出他们围成的图形
转化到以目标状态为原点的极坐标系
答案为 \(1\) ,那么存在与原点重合的点,开个变量记录一下即可
答案为 \(2\) 那么存在两条方向相反向量,在放入以及删除某个点时候判断一下,也开变量存即可
答案为 \(3\) 或者 \(0\) , 那么就跟 \(T1\) 的条件很像,我们用 \(multiset\) 维护极角,从最小角开始跳到逆时针角度差小于 \(\pi\) 的最后一个点,直到跳不动为止,判断从该点是否能够跳回最小极角即可
code
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1);
const double eps = 1e-10;
const int maxn = 100005;
int n;
struct dir{
double x, y, the, mod;
dir(){}
friend bool operator == (const dir x, const dir y){return x.x + eps >= y.x && x.x - eps <= y.x && x.y + eps >= y.y && x.y - eps <= y.y;}
friend double operator * (const dir &x, const dir &y){return x.x * y.x + x.y * y.y; }
friend dir operator - (const dir &x, const dir &y){return {x.x - y.x, x.y - y.y}; }
dir(double _x, double _y){x = _x; y = _y; mod = sqrt(x * x + y * y);}
}d[maxn];
int cnt, equ, a2;
multiset<double>s;
double dx, dy, ds;
char c[2];
void get_the(dir &x){
dir e = {1, 0};
x.the = acos((x * e) / x.mod / e.mod);
if(x.y < 0)x.the = 2 * PI - x.the;
}
bool check3(){
if(s.empty())return false;
double now = *s.begin();
auto it = --s.upper_bound(now + PI);
while(1){
if(now + eps >= (*it) && now - eps <= (*it))break;
now = (*it);
it = --s.upper_bound(now + PI);
}
now = now - PI + eps;
if(now >= (*s.begin()))return true;
return false;
}
bool check2(int x){
if(s.empty())return false;
double now = fmod(d[x].the + PI, 2 * PI);
auto it = s.lower_bound(now);
if(it != s.end() && (*it) - eps <= now && (*it) + eps >= now)return true;
return false;
}
int main(){
scanf("%lf%lf%lf",&dx, &dy, &ds);
scanf("%d",&n); ds += dx + dy;
dx = dx / ds; dy = dy / ds;
d[0] = dir(0, 0);
for(int i = 1; i <= n; ++i){
scanf("%s",c);
if(c[0] == 'A'){
++cnt;
double rx, ry, rz;
scanf("%lf%lf%lf",&rx,&ry,&rz);
rz += rx + ry; rx /= rz; ry /= rz;
rx = rx - dx; ry = ry - dy;
d[cnt] = dir(rx, ry);
if(d[cnt] == d[0]){
++equ;
}else{
get_the(d[cnt]);
a2 += check2(cnt);
s.insert(d[cnt].the);
}
}else{
int id; scanf("%d",&id);
if(d[id] == d[0]){
--equ;
}else{
s.erase(s.lower_bound(d[id].the));
a2 -= check2(id);
}
}
if(equ)printf("1\n");
else if(a2)printf("2\n");
else if(check3())printf("3\n");
else printf("0\n");
}
return 0;
}
C. 一虎杀二羊
令 \(a_i = a_i - b_i\)
于是等价于求 \(x, p\) 使得 \(\sum a_ix^{i} = 0\)
假设存在 \(x^d \equiv 1 \mod p\)
那么 \(a_{kd + i}\) 与 \(a_{i}\) 所乘的系数可以视作相同,直接把他们合并成一项可以较快判断
因为费马小定理 \(x^{p - 1} \equiv 1 \mod p\), 所以当 \(d|(p - 1)\)时,肯定满足上面的条件
于是我们考虑枚举 \(d\), 每次预处理 \(\sum a_{kd + i}\) 方便后面计算
去枚举一个 \(k\), 令 \(p = kd +1\) 为质数
那么有 \(a^{kd} \equiv 1 \mod p\)
那么\(x = a^k\)就是我们需要和 \(p\) 一起 \(check\) 的
为了提高出解率,我们随机一个质数作为 \(a\)
然后因为哈希随机啥的,于是我们从小大大暴力枚举 \(d\) , \(k\) ,每个合法的 \(p\) 随机 \(check\) 若干次,就能出解
code
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<map>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; bool f = 0; char c = getchar();
while(!isdigit(c))f = c == '-', c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
const int maxn = 100005;
ll a[maxn], b[maxn];
int n, m;
ll prime[500005], cnt, d;
bool flag[2000005];
ll qpow(ll x, ll y, ll mod){
ll ans = 1;
for(; y; y >>= 1, x = x * x % mod)if(y & 1)ans = ans * x % mod;
return ans;
}
bool check(ll x, ll p){
ll now = 0;
for(int i = d - 1; i >= 0; --i){
now = (now * x % p + b[i]) % p;
}
return now == 0;
}
int main(){
n = read(), m = read();
for(int i = 0; i < n; ++i)a[i] = read(), b[i] = read();
for(int i = 0; i < n; ++i) a[i] -= b[i];
for(int i = 2; i <= 2000000; ++i){
if(!flag[i])prime[++cnt] = i;
for(int j = 1; j <= cnt && i * prime[j] <= 2000000; ++j){
flag[i * prime[j]] = 1;
if(i % prime[j] == 0)break;
}
}
for(d = 2; d <= 1000000; ++d){
for(int i = 0; i < d; ++i){
b[i] = 0;
for(int j = i; j <= n; j += d)b[i] += a[j];
}
for(ll k = ((m - 1) / d + 1); k * d <= 2000000; ++k){
ll now = d * k + 1;
if(flag[now])continue;
int up = rand() % 5 + 1;
for(int t = 1; t <= up; ++t){
ll x = qpow(prime[rand() % cnt + 1], k, now);
if(x > now - 2 || x < 2)continue;
if(check(x, now)){
printf("%lld %lld\n",now, x);
return 0;
}
}
}
}
return 0;
}
D. 逆序对
暴力 $f_{i, j} $ 考虑前 \(i\) 个数,有 \(j\) 个逆序对的方案数
有 \(f_{i, j} = \sum_{k = 0}^{j - 1}f_{i - 1, j - k}\)
于是把他看成多项式,我们可以写成 $f_i = (1- x^i) f_{i - 1} / (1 - x) $
就是 \(1 / (1 - x) = x^0 + x^1 + x^2 + ...\), 那么乘上他相当于前缀和,乘上 \(x^k\) 相当于平移 \(k\) 步,于是前缀和相减得到答案
我们要求
\(\displaystyle f_n(x) \Pi_{i =1}^{n}(1 - x^i)/(1 - x)\)
分开两部分求 \(f(x) = \Pi_{i =1}^{n}(1 - x^i)\) \(g(x) = \Pi_{i =1}^{n} 1/(1 - x)\)
第一个是五边形数,有公式 \(f (x) = \sum_{j = 0}^{\inf}(-1)^jx^{j(3j + 1)/ 2} +(-1)^jx^{j(3j - 1)/ 2}\)
我们 \(\mod 2\),所以枚举 \(j\) 去对应位置的系数异或一下 \(1\) 即可,这个大约是 \(\sqrt n\) 的
\(g(x) = \Pi_{i =1}^{n} 1/(1 - x) = (x^0 + x^1 + x^2 +...)^n\) 相当于求可重复的选取 \(n\) 个数的方案,那么 \(g(x) = \sum_{i = 0}^{inf} (^{i + n - 1}_{n- 1})x^i\)
根据卢卡斯定理 \((^{n}_{m}) \mod 2 \equiv (^{n/2}_{m/2})(^{n\mod 2}_{m\mod 2})\)
那么不停展开,只要存在 \((^0_1)\)那么一定为 \(0\) 否则为 \(1\)
于是 \((^{i + n - 1}_{n- 1}) = [(n - 1) \&(i + n - 1) == (n - 1)] = [(n - 1) \& i == 0]\)
然后
\(\large g(x) = \Pi_{2^t \& (n - 1) == 0}(1 + x^{2^t})\)
最后只需要把 \(f(x) g(x)\)乘起来,发现 \(g(x)\) 实际上让 \(f(x)\)左移 \(2^t\)再自加(\(\mod 2\) 为异或)
于是枚举 \(t\) 操作 \(f\) 即可,上述过程用 \(bitset\) 进行优化
code
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#include<bitset>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; bool f = 0; char c = getchar();
while(!isdigit(c))f = c == '-', c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
int n, m;
bitset<100000001>f;
int main(){
n = read(), m = read();
f[0] = 1;
for(int j = 1; j <= n; ++j){
ll p1 = 1ll * j * (3 * j + 1) / 2;
ll p2 = 1ll * j * (3 * j - 1) / 2;
if(p2 > n)break;
f[p1] = f[p1] ^ 1, f[p2] = f[p2] ^ 1;
}
for(int i = 0; (1ll << i) <= n; ++i)if(((1ll << i) & (n - 1)) == 0){
f ^= (f << (1ll << i));
}
for(int i = 1; i <= m; ++i){
int x = read();
printf("%d",(int)f[x]);
}
return 0;
}

浙公网安备 33010602011771号