2022 HDU多校2
Static Query on Tree(虚树、数据结构)
Keychains(计算几何、三维、克拉默法则)
Problem
在三维空间中给定两个圆,问这两个圆是否相扣
Solve
回顾一下立体几何相关知识
-
三维叉积
\[A\times B= \left |\begin{array}{cccc} i & j & k \\ x_1 &y_1 & z_1 \\ x_2 &y_2 & z_2 \\ \end{array}\right|=< i \left|\begin{array}{cccc} y_1& z_1\\ y_2& z_2 \end{array}\right|, -j \left|\begin{array}{cccc} x_1& z_1\\ x_2& z_2 \end{array}\right|, k \left|\begin{array}{cccc} x_1& y_1\\ x_2& y_2 \end{array}\right| > \] -
两个平面交线的方向向量=两个平面法向量的叉积=也是这两个法向量所在平面的法向量
-
克莱姆法则
在\(n\)非齐次线性方程组中
\[\begin{cases} a_{11}x_1+a_{12}x_2+\cdots+a_{1n}x_n=b_1,\quad \\ a_{21}x_1+a_{22}x_2+\cdots+a_{2n}x_n=b_2, \quad \\ \vdots \quad \cdots \quad \vdots \quad \cdots \quad \vdots \quad \cdots \quad \vdots \quad \cdots\\ a_{n1}x_1+a_{n2}x_2+\cdots+a_{nn}x_n=b_n \end{cases} \tag{1} \]系数行列式为
\[D= \left |\begin{array}{cccc} a_{11} & a_{12} & \cdots &a_{1n} \\ a_{21} & a_{22} & \cdots &a_{2n} \\ \vdots & \vdots &\ddots &\vdots\\ a_{n1} & a_{n2} & \cdots &a_{nn} \\ \end{array}\right| \]定义\(D_j\)是把\(D\)中第\(j\)列元素对应地换成常数项而其余各列保持不变所得到的行列式,比如
\[D_1= \left |\begin{array}{cccc} b_{1} & a_{12} & \cdots &a_{1n} \\ b_{2} & a_{22} & \cdots &a_{2n} \\ \vdots & \vdots &\ddots &\vdots\\ b_{n} & a_{n2} & \cdots &a_{nn} \\ \end{array}\right| \quad D_2= \left |\begin{array}{cccc} a_{11} & b_{1} & \cdots &a_{1n} \\ a_{21} & b_{2} & \cdots &a_{2n} \\ \vdots & \vdots &\ddots &\vdots\\ a_{n1} & b_{n} & \cdots &a_{nn} \\ \end{array}\right| \quad \]那么有\(x_j=\frac{D_j}{D}\)
在本题中,先求出这两个圆所在平面的交线\(l\),然后求出交线和第一个圆的两个交点\(P_1,P_2\),最后判断\(P_1、P_2\)是否一个在第二个圆里面,一个在另一个圆外面即可。
如何求交线\(l\),首先我们可以得到交线的方向向量\(v\)。记两个圆分别是\(A、B\),圆\(A\)所在平面的法向量是\(v_A\),圆\(B\)所在平面的法向量是\(v_B\),\(H=(x,y,z)\)是圆\(A\)的圆心\(O_A\)在直线\(l\)的垂足,然后用垂径定理求出两个交点\(P_1、P_2\)。实际上我们不需要求出\(l\)的直线方程,只需要求\(H\)即可。可以发现\(v_A\perp HO_A,v_B\perp HO_B,v\perp HO_A\),于是可以列出三个方程
然后用克莱姆法则求\(x,y,z\)即可,之后由于知道了直线上的一个点和直线的方向向量,这条直线也可以唯一确定,假设直线上的一点为\(P\)到已知点\(H\)的距离为\(t\),则\(P=H+t\frac{v}{|v|}\)
Code
#include <bits/stdc++.h>
using namespace std;
const double eps=1e-6;
struct Point{
double x,y,z;
Point operator + (const Point&t)const{
return {x+t.x,y+t.y,z+t.z};
}
Point operator - (const Point&t)const{
return {x-t.x,y-t.y,z-t.z};
}
double operator * (const Point&t)const{
return x*t.x+y*t.y+z*t.z;
}
Point operator ^ (const Point&t)const{
return {y*t.z-z*t.y,-(x*t.z-z*t.x),x*t.y-y*t.x};
}
Point operator * (const double k)const{
return {x*k,y*k,z*k};
}
Point operator / (const double k) const{
return {x/k,y/k,z/k};
}
double len(){
return sqrt((*this) * (*this));
}
};
typedef Point Vector;
double det(Vector a,Vector b,Vector c){
Vector d=b^c;
return a*d;
}
double dist(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));
}
bool check_para(Vector a,Vector b){
Vector c=a^b;
if(fabs(c.x)>eps || fabs(c.y)>eps || fabs(c.z)>eps) return false;
return true;
}
struct Circle{
Point c;
double r;
Vector dir;
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
Circle A,B;
cin>>A.c.x>>A.c.y>>A.c.z;
cin>>A.dir.x>>A.dir.y>>A.dir.z;
cin>>A.r;
cin>>B.c.x>>B.c.y>>B.c.z;
cin>>B.dir.x>>B.dir.y>>B.dir.z;
cin>>B.r;
if(check_para(A.dir,B.dir)) cout<<"No\n";
else{
Vector dir=A.dir^B.dir; //两个平面交线的方向向量=两个平面法向量的叉积=也是这两个法向量所在平面的法向量
Vector r1={A.dir.x,B.dir.x,dir.x};
Vector r2={A.dir.y,B.dir.y,dir.y};
Vector r3={A.dir.z,B.dir.z,dir.z};
Vector r4={A.dir*A.c,B.dir*B.c,dir*A.c};
double D=det(r1,r2,r3);
Point H={det(r4,r2,r3)/D,det(r1,r4,r3)/D,det(r1,r2,r4)/D};
double d=dist(H,A.c);
double t=sqrt(A.r*A.r-d*d);
Point P1=H+dir/dir.len()*t, P2=H-dir/dir.len()*t;
if((dist(P1,B.c)<=B.r) ^ (dist(P2,B.c)<=B.r) )cout<<"Yes\n";
else cout<<"No\n";
}
}
return 0;
}
Copy(bitset、离线)
Problem
给定一个长度为\(n\)的序列\(\{a\}\),给定\(q\)次操作,每次操作有两种类型
- 给定一个区间\([l,r]\)将区间\([l,r]\)复制一份后接到\(a_r\)的后面
- 询问第\(x\)个数是多少
最后要求所有询问的值的异或和
\(1\le n,q\le 10^5\)
Solve
由于最后只需要求异或和,那么每个数出现奇数次相当于出现1次,出现偶数次相当于不出现,所以可以考虑对每一个数字考虑它最终的出现次数。对于一次区间\([l,r]\)复制后,若后面的\(x\)大于\(r\)的,那么这\(x\)的出现相当于\(x-(r-l+1)\)的出现,但考虑到如果顺序做的话会对后面产生影响,所以考虑离线倒序。假设倒序的一个区间时\([l,r]\),那么相当于把\([1,r]\)没有影响,而出现在\([r+1,n]\)的数需要向前平移\((r-l+1)\)的位置,平移后\([1,r]\)和\([r+1-(r-l+1),n-(r-l+1)]\)部分会有重叠,如果重叠部分出现次数和是奇数,说明会贡献1次,出现偶数次,相当于不贡献,因此这样可以用异或操作来解决。这种平移加异或,用bitset
来考虑即可
Code
#include <bits/stdc++.h>
using namespace std;
const int N=100001;
int l[N],r[N],x[N],t[N],a[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=q;i++){
cin>>t[i];
if(t[i]==1) cin>>l[i]>>r[i];
else cin>>x[i];
}
bitset<N>f(0),high,low;
for(int i=q;i>=1;i--){
if(t[i]==1){
low=f&(~bitset<N>(0)>>(N-r[i]-1));
high=f&(~bitset<N>(0)<<(r[i]+1));
f=low^(high>>(r[i]+1-l[i]));
}else f[x[i]].flip();
}
int ans=0;
for(int i=1;i<=n;i++)
if(f[i]) ans^=a[i];
cout<<ans<<'\n';
}
}
ShuanQ(数论)
Problem
给定一种加密方式
其中\(M\)是一个素数,\(c\)是密文,\(m\)是明文,并且满足\(1\lt P,Q,c\lt M\)
现在给定\(P,Q,c\),求\(m\),如果求不出,输出\(\text{shuanQ}\)
\(1\lt P,Q,c\le 2\times 10^6\)
Solve
\(P\times Q \equiv 1\pmod{M}\)等价于\(PQ-1=kM\),说明\(M\)是\(PQ-1\)的素因数,并且大于\(P,Q,c\),并且最多只有一个满足。所以求出最后一个素因数来判断即可。时间复杂度\(O(nlogn)\)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+10;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int c,P,Q;
cin>>P>>Q>>c;
ll n=1LL*P*Q-1;
for(ll i=2;i*i<=n;i++){
if(n%i==0){
while(n%i==0){
n/=i;
}
}
}
if(n<=c) cout<<"shuanQ\n";
else cout<<1LL*c*Q%n<<'\n';
}
}
DOS Card(线段树)
Problem
给定一个长度为\(n\)的序列\(\{a\}\),定义一个合法的匹配为\(i\lt j\),这个匹配的贡献为\((a_i+a_j)\times(a_i-a_j)\)。现在有若干个询问,每个询问给定一个区间\([l,r]\),在这个区间内可以选择\(4\)个数组成\(2\)个匹配,问最大可能的匹配贡献和是多少。
\(1\le n,m\le 2\times 10^5\),\(1\le a_i\le 10^8\)
Solve
初中就知道\((a_i+a_j)\times(a_i-a_j)\)可以变成\(a_i^2-a_j^2\),所以为题就变成了在给定区间中任选\(4\)个数组成匹配,满足\(i\lt j\lt k\lt l\)且\(a_i^2-a_k^2+a_j^2-a_l^2\)或者\(a_i^2-a_j^2+a_k^2-a_l^2\)最大。因为有每个匹配下标满足偏序关系,所以任意给定\(4\)个从小到大的下标,可能的组合只有这\(2\)种。考虑怎么求,假设我们按照从小到大的下标关系为每个数赋予正负,那么情况就是++--
或者+-+-
。考虑++--
,假设有两个区间,怎么得到++--
,发现可能的组合有+ +--
、++ --
、++- -
、++--
、 ++--
,所以就可以用线段树暴力维护每一种情况,+-+-
也是一样分析,具体参考代码
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
const int N=2e5+10;
/*
1 ++--
2 +-+-
3 +
4 -
5 ++
6 --
7 +-
8 -+
9 ++-
10 +--
11 +-+
12 -+-
*/
ll a[N];
struct node{
ll w[15];
void init(){
for(int i=1;i<=12;i++) w[i]=-inf;
}
}tr[N*4];
void pushup(node &rt,node ls,node rs){
rt.w[1]=max(ls.w[1],rs.w[1]);
rt.w[2]=max(ls.w[2],rs.w[2]);
rt.w[3]=max(ls.w[3],rs.w[3]);
rt.w[4]=max(ls.w[4],rs.w[4]);
rt.w[5]=max(ls.w[5],rs.w[5]);
rt.w[6]=max(ls.w[6],rs.w[6]);
rt.w[7]=max(ls.w[7],rs.w[7]);
rt.w[8]=max(ls.w[8],rs.w[8]);
rt.w[9]=max(ls.w[9],rs.w[9]);
rt.w[10]=max(ls.w[10],rs.w[10]);
rt.w[11]=max(ls.w[11],rs.w[11]);
rt.w[12]=max(ls.w[12],rs.w[12]);
rt.w[1]=max(rt.w[1],ls.w[5]+rs.w[6]); //++ --
rt.w[1]=max(rt.w[1],ls.w[9]+rs.w[4]); //++- -
rt.w[1]=max(rt.w[1],ls.w[3]+rs.w[10]);//+ +--
rt.w[2]=max(rt.w[2],ls.w[3]+rs.w[12]);//+ -+-
rt.w[2]=max(rt.w[2],ls.w[7]+rs.w[7]); //+- +-
rt.w[2]=max(rt.w[2],ls.w[11]+rs.w[4]);//+-+ -
rt.w[5]=max(rt.w[5],ls.w[3]+rs.w[3]); //++: + +
rt.w[6]=max(rt.w[6],ls.w[4]+rs.w[4]); //--: - -
rt.w[7]=max(rt.w[7],ls.w[3]+rs.w[4]); //+-: + -
rt.w[8]=max(rt.w[8],ls.w[4]+rs.w[3]); //-+: - +
rt.w[9]=max(rt.w[9],ls.w[5]+rs.w[4]); //++-: ++ -
rt.w[9]=max(rt.w[9],ls.w[3]+rs.w[7]); //++-: + +-
rt.w[10]=max(rt.w[10],ls.w[3]+rs.w[6]);//+--: + --
rt.w[10]=max(rt.w[10],ls.w[7]+rs.w[4]);//+--: +- -
rt.w[11]=max(rt.w[11],ls.w[7]+rs.w[3]);//+-+: +- +
rt.w[11]=max(rt.w[11],ls.w[3]+rs.w[8]);//+-+: + -+
rt.w[12]=max(rt.w[12],ls.w[4]+rs.w[7]);//-+-: - +-
rt.w[12]=max(rt.w[12],ls.w[8]+rs.w[4]);//-+-: -+ -
}
void build(int rt,int l,int r){
tr[rt].init();
if(l==r){
tr[rt].w[3]=a[l];
tr[rt].w[4]=-a[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(tr[rt],tr[rt<<1],tr[rt<<1|1]);
}
node query(int rt,int L,int R,int l,int r){
if(l<=L&&R<=r){
return tr[rt];
}
int mid=(L+R)>>1;
if(r<=mid) return query(rt<<1,L,mid,l,r);
else if(l>mid) return query(rt<<1|1,mid+1,R,l,r);
else{
node res;
res.init();
pushup(res,query(rt<<1,L,mid,l,mid),query(rt<<1|1,mid+1,R,mid+1,r));
return res;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],a[i]=a[i]*a[i];
build(1,1,n);
while(m--){
int l,r;
cin>>l>>r;
node res=query(1,1,n,l,r);
cout<<max(res.w[1],res.w[2])<<'\n';
}
}
}
Luxury cruise ship(背包、数学、结论)
Problem
有\(Q\)个询问,每个询问给定一个面值\(x\),问一开始你有\(0\)元,现在每天可以从\(7,31,365\)这三个数字中选一个来加入自己的面值,问能否刚好得到\(x\),如果可以,输出最少的天数,否则输出\(-1\)
\(1\le x\le 10^{18}\)
Solve
由于\(x\)很大,显然无法直接背包,注意到\(\text{gcd}(7,31)=1\),所以可以用\(7,31\)来组成\(365\),且\(365=5\times 31+30\times 7\)。小范围背包,大范围贪心后背包,小范围选取\(10^6\)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10000,M=1e6;
const int inf=0x3f3f3f3f;
ll dp[N];
int w[3]={7,31,365};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
for(int i=1;i<N;i++) dp[i]=inf;
dp[0]=0;
for(int i=0;i<3;i++)
for(int j=w[i];j<N;j++)
dp[j]=min(dp[j],dp[j-w[i]]+1);
int T;
cin>>T;
while(T--){
ll x;
cin>>x;
ll res=0;
if(x>M){
res+=(x-M)/365;
x-=res*365;
}
if(dp[x]==inf) cout<<"-1\n";
else cout<<res+dp[x]<<'\n';
}
}