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\),于是可以列出三个方程

\[\begin{cases} x_{v_A}x+y_{v_A}y+z_{v_A}z=v_A\cdot O_A,\quad \\ x_{v_B}x+y_{v_B}y+z_{v_B}z=v_B\cdot O_B, \quad \\ x_{v}x+y_{v}y+z_{v}z=v\cdot O_A \end{cases} \tag{1} \]

然后用克莱姆法则求\(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

给定一种加密方式

\[P\times Q \equiv 1\pmod{M}\\ c\equiv m\times P \pmod{M}\\ m\equiv c\times Q \pmod{M} \]

其中\(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';

    }

}
posted @ 2022-08-21 16:44  Arashimu  阅读(27)  评论(0编辑  收藏  举报