2022NOIPA层联测9

A. 泰山压顶

不会叉积,考场死活写不对 check 还干到 11:30,心态异常炸裂。。。

考虑把成龙当做原点,以阿福为极坐标轴方向,建立极坐标系

发现选择的点集一旦确定,那么他们的连接顺序必然是在极坐标系上转了一圈,于是 DP 转移就没有环了

按照极坐标系的角度排序

考虑 fi,j 表示最后选择 i, 倒数第二个选择 j 的方案数

转移考虑三个限制

  1. 阿福必选

于是初始状态设在他那里

  1. 选出的点围成的图形需要包住原点

发现只要相邻的转移点的极角差小于 π 即可

  1. 泰山顶点角度需要小于π

您需要简单看看叉积

我不会那玩意,于是考场写不出正确的 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 维护极角,从最小角开始跳到逆时针角度差小于 π 的最后一个点,直到跳不动为止,判断从该点是否能够跳回最小极角即可

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. 一虎杀二羊

ai=aibi

于是等价于求 x,p 使得 aixi=0

假设存在 xd1modp

那么 akd+iai 所乘的系数可以视作相同,直接把他们合并成一项可以较快判断

因为费马小定理 xp11modp, 所以当 d|(p1)时,肯定满足上面的条件

于是我们考虑枚举 d, 每次预处理 akd+i 方便后面计算

去枚举一个 k, 令 p=kd+1 为质数

那么有 akd1modp

那么x=ak就是我们需要和 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. 逆序对

暴力 fi,j 考虑前 i 个数,有 j 个逆序对的方案数

fi,j=k=0j1fi1,jk

于是把他看成多项式,我们可以写成 fi=(1xi)fi1/(1x)

就是 1/(1x)=x0+x1+x2+..., 那么乘上他相当于前缀和,乘上 xk 相当于平移 k 步,于是前缀和相减得到答案

我们要求

fn(x)Πi=1n(1xi)/(1x)

分开两部分求 f(x)=Πi=1n(1xi) g(x)=Πi=1n1/(1x)

第一个是五边形数,有公式 f(x)=j=0inf(1)jxj(3j+1)/2+(1)jxj(3j1)/2

我们 mod2,所以枚举 j 去对应位置的系数异或一下 1 即可,这个大约是 n

g(x)=Πi=1n1/(1x)=(x0+x1+x2+...)n 相当于求可重复的选取 n 个数的方案,那么 g(x)=i=0inf(n1i+n1)xi

根据卢卡斯定理 (mn)mod2(m/2n/2)(mmod2nmod2)

那么不停展开,只要存在 (10)那么一定为 0 否则为 1

于是 (n1i+n1)=[(n1)&(i+n1)==(n1)]=[(n1)&i==0]

然后

g(x)=Π2t&(n1)==0(1+x2t)

最后只需要把 f(x)g(x)乘起来,发现 g(x) 实际上让 f(x)左移 2t再自加(mod2 为异或)

于是枚举 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;
}
posted @   Chen_jr  阅读(33)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示