2022杭电多校第二场
Static Query on Tree(虚树 树链剖分 线段树 树形DP)
题意
X国有
n
n
n个城市和
n
−
1
n-1
n−1条单向道路,所有的城市都能到达1号城市。有
q
q
q次询问,每次询问给定三个集合
A
A
A、
B
B
B、
C
C
C,问有多少个城市可以从集合A中的至少一个城市到达,并且可以从集合B中的至少一个城市到达,并且可以到达集合C中的至少一个城市。
∑
q
≤
2
×
1
0
5
,
∑
∣
A
∣
+
∑
∣
B
∣
+
∑
∣
C
∣
≤
2
×
1
0
5
\sum q \leq 2 \times 10^5,\sum|A| + \sum |B| + \sum|C| \leq 2 \times 10^5
∑q≤2×105,∑∣A∣+∑∣B∣+∑∣C∣≤2×105。
分析
所有城市都能到达1号城市,说明图是连通的,又因为是
n
−
1
n-1
n−1条边,所以题目给定的是一棵树,并且树上的边是有方向的。观察数据范围发现,询问次数较多,但总共涉及的点数是
O
(
n
)
O(n)
O(n)的,这比较符合虚树处理问题的特征。对于每次询问建立虚树后需要进行树形DP,以1号结点为根结点,A集合和B集合中的结点的信息向上传递,C集合中的结点的信息向下传递,如果一个结点可以被三个集合中的结点的信息标记,这个结点就会被统计到答案中。虚树中的一条边可能对应原树中的一条链,统计答案时需要注意。时间复杂度
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n))。
本题也可以使用树链剖分,将集合A、B中元素到根结点的路径打上标记,再将集合C中元素所在的子树打上标记,最后统计树上带有三种标记的结点的个数。在用线段树维护时,需要用三位二进制表示结点被标记的状态,1表示被A/B/C标记,0表示未被A/B/C标记,总共有8种状态。时间复杂度 O ( n l o g 2 ( n ) ) O(nlog^{2}(n)) O(nlog2(n))。
两种做法的最终目的都是统计有三种标记的结点的个数,只不过在本题中虚树做法的复杂度更优秀。
AC代码
虚树
const int N=2e5+10;
int head[N],e[N],ne[N],tot;
int a[N],d[N],sz[N],dfn[N],stk[N],f[N][19];
vector<int> vec[N];
int g[N][3];
bool h[N][3];
int n,q,t,cnt,num,top,ans;
void add(int x,int y)
{
e[++tot]=y,ne[tot]=head[x],head[x]=tot;
}
bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
void init(int n)
{
for(int i=1;i<=n;i++) g[i][0]=g[i][1]=g[i][2]=0;
for(int i=1;i<=n;i++) h[i][0]=h[i][1]=h[i][2]=0;
for(int i=1;i<=n;i++) head[i]=d[i]=0;
memset(f,0,sizeof(f));
tot=t=num=0;
}
void dfs(int x)
{
dfn[x]=++num;
sz[x]=1;
for(int i=head[x];i;i=ne[i])
{
int y=e[i];
if(!d[y])
{
d[y]=d[x]+1;
f[y][0]=x;
for(int j=1;j<=t;j++) f[y][j]=f[f[y][j-1]][j-1];
dfs(y);
sz[x]+=sz[y];
}
}
}
int getlca(int x,int y)
{
if(d[x]>d[y]) swap(x,y);
for(int i=t;i>=0;i--)
if(d[f[y][i]]>=d[x])
y=f[y][i];
if(x==y) return x;
for(int i=t;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
void dp(int x,int p)
{
g[x][2]|=h[x][2];
for(auto y:vec[x])
{
g[y][2]|=g[x][2];
dp(y,x);
g[x][0]|=g[y][0];
g[x][1]|=g[y][1];
if(g[x][2]&&g[y][0]&&g[y][1]) ans+=d[y]-d[x]-1;
}
g[x][0]|=h[x][0],g[x][1]|=h[x][1];
if(g[x][0]&&g[x][1]&&g[x][2]) ans++;
}
void clear(int x)
{
g[x][0]=g[x][1]=g[x][2]=h[x][0]=h[x][1]=h[x][2]=0;
for(auto y:vec[x])
{
clear(y);
}
}
void build(int cnt)
{
sort(a+1,a+cnt+1,cmp);
cnt=unique(a+1,a+cnt+1)-(a+1);
top=0; stk[++top]=1;
vec[1].clear();
for(int i=1;i<=cnt;i++)
{
int lca=getlca(a[i],stk[top]);
if(lca!=stk[top])
{
while(dfn[lca]<dfn[stk[top-1]])
{
int x=stk[top-1],y=stk[top];
vec[x].push_back(y);
top--;
}
if(dfn[lca]>dfn[stk[top-1]])
{
vec[lca].clear();
vec[lca].push_back(stk[top]);
top--;
stk[++top]=lca;
}
else
{
int x=stk[top-1],y=stk[top];
vec[x].push_back(y);
top--;
}
}
vec[a[i]].clear();
stk[++top]=a[i];
}
while(top>1)
{
int x=stk[top-1],y=stk[top];
vec[x].push_back(y);
top--;
}
dp(1,0);
clear(1);
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n>>q;
init(n);
for(int i=2;i<=n;i++)
{
int x; cin>>x; add(x,i);
}
while((1<<t)<n) t++;
d[1]=1; dfs(1);
while(q--)
{
int c[3];
for(int i=0;i<=2;i++) cin>>c[i];
cnt=0;
for(int i=0;i<=2;i++)
{
for(int j=1;j<=c[i];j++)
{
int x; cin>>x;
if(x!=1) a[++cnt]=x;
h[x][i]=1;
}
}
build(cnt);
cout<<ans<<endl;
ans=0;
}
}
return 0;
}
树链剖分
#define T tr[p]
#define LS tr[p<<1]
#define RS tr[p<<1|1]
const int N=2e5+10;
int dep[N],siz[N],top[N],fa[N],son[N];
int head[N],e[N],ne[N],tot;
int id[N],cnt;
int n,q;
struct tnode {
int sum[8],tag;
bool clear;
}tr[N<<2];
void build(int p,int l,int r)
{
memset(T.sum,0,sizeof(T.sum));
T.sum[0]=r-l+1;
T.tag=T.clear=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
void clear(int p,int l,int r)
{
memset(T.sum,0,sizeof(T.sum));
T.sum[0]=r-l+1;
T.tag=0;
T.clear=1;
}
void update(int p,int l,int r,int tag)
{
static int sum[8];
memset(sum,0,sizeof(sum));
for(int i=0;i<8;i++) sum[i|tag]+=T.sum[i];
memcpy(T.sum,sum,sizeof(sum));
T.tag|=tag;
}
void pushup(int p)
{
for(int i=0;i<8;i++) T.sum[i]=LS.sum[i]+RS.sum[i];
}
void pushdown(int p,int l,int r)
{
if(T.clear)
{
int mid=(l+r)>>1;
clear(p<<1,l,mid);
clear(p<<1|1,mid+1,r);
T.clear=0;
}
if(T.tag)
{
int mid=(l+r)>>1;
update(p<<1,l,mid,T.tag);
update(p<<1|1,mid+1,r,T.tag);
T.tag=0;
}
}
void change(int p,int l,int r,int x,int y,int tag)
{
if(x<=l&&y>=r)
{
update(p,l,r,tag);
return ;
}
pushdown(p,l,r);
int mid=(l+r)>>1;
if(x<=mid) change(p<<1,l,mid,x,y,tag);
if(y>mid) change(p<<1|1,mid+1,r,x,y,tag);
pushup(p);
}
void add(int x,int y)
{
e[++tot]=y,ne[tot]=head[x],head[x]=tot;
}
void dfs1(int x,int f,int depth)
{
dep[x]=depth; fa[x]=f; siz[x]=1;
for(int i=head[x];i;i=ne[i])
{
int y=e[i];
if(y==f) continue;
dfs1(y,x,depth+1);
siz[x]+=siz[y];
if(siz[son[x]]<siz[y]) son[x]=y;
}
}
void dfs2(int x,int t)
{
id[x]=++cnt; top[x]=t;
if(!son[x]) return ;
dfs2(son[x],t);
for(int i=head[x];i;i=ne[i])
{
int y=e[i];
if(y==fa[x]||y==son[x]) continue;
dfs2(y,y);
}
}
void update_path(int x,int y,int k)
{
while(top[x]!=top[y]) {
if(dep[top[x]]<dep[top[y]]) swap(x,y);
change(1,1,n,id[top[x]],id[x],k);
x=fa[top[x]];
}
if(dep[x]<dep[y]) swap(x,y);
change(1,1,n,id[y],id[x],k);
}
void update_tree(int x,int k)
{
change(1,1,n,id[x],id[x]+siz[x]-1,k);
}
int main()
{
int _;
cin>>_;
while(_--)
{
cnt=0;
cin>>n>>q;
for(int i=1;i<=n;i++) head[i]=son[i]=0;
tot=0;
for(int i=2;i<=n;i++)
{
int x; cin>>x; add(x,i);
}
dfs1(1,-1,1);
dfs2(1,1);
build(1,1,n);
while(q--)
{
int A,B,C;
cin>>A>>B>>C;
for(int i=1;i<=A;i++)
{
int x;
cin>>x;
update_path(1,x,1);
}
for(int i=1;i<=B;i++)
{
int x;
cin>>x;
update_path(1,x,2);
}
for(int i=1;i<=C;i++)
{
int x;
cin>>x;
update_tree(x,4);
}
cout<<tr[1].sum[7]<<endl;
clear(1,1,n);
}
}
return 0;
}
注意
本题涉及到的数组和变量较多,而且是多组数据,要注意进行初始化。
C++ to Python(模拟 思维)
题意
将C++代码std::make_tuple
转化为Python代码并输出。
分析
将输入中所有的std::make_tuple
删除,直接模拟即可。
AC代码
int main()
{
int t;
cin>>t;
while(t--)
{
string s;
cin>>s;
string t="std::make_tuple";
for(int i=0;i<s.size();i++)
{
bool flag=true;
for(int j=0;j<t.size();j++)
{
if(s[i]==t[j])
{
flag=false;
break;
}
}
if(flag) cout<<s[i];
}
cout<<endl;
}
return 0;
}
Keychains(计算几何)
题意
给定三维空间中的两个圆,判断这两个圆是否相扣。题目给定两个圆的圆心、半径和法向量,保证两个圆上的两个点之间的距离不小于
0.1
0.1
0.1
题解
首先求两个圆所在平面的交线,由一点和一个法向量可以确定一个平面。得到交线后,再求出交线和圆A的交点,交线和圆A的交点其实就是交线和球A的交点。然后判断交点是否都在圆B内,通过交点和圆心的距离和半径的大小可以判断点是否在圆内。求两个平面的交线以及直线与球的交点要确定模板的正确性。
AC代码
typedef double lf;
struct vec {
lf x, y, z;
vec(){} vec(lf x, lf y, lf z): x(x), y(y), z(z) {}
vec operator-(vec b) { return vec(x - b.x, y - b.y, z - b.z); }
vec operator+(vec b) { return vec(x + b.x, y + b.y, z + b.z); }
vec operator*(lf k) { return vec(k * x, k * y, k * z); }
lf sqr() { return x * x + y * y + z * z; }
lf len() { return sqrt(x * x + y * y + z * z); }
vec trunc(lf k = 1) { return *this * (k / len()); }
friend vec cross(vec a, vec b) {
return vec(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
friend lf dot(vec a, vec b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
void scan() {
scanf("%lf%lf%lf", &x, &y, &z);
}
};
void inter_ff(vec p1, vec dir1, vec p2, vec dir2, vec &res1, vec &res2) {
vec e = cross(dir1, dir2), v = cross(dir1, e);
lf d = dot(dir2, v); if (abs(d) < 1e-9) return;
vec q = p1 + v * (dot(dir2, p2 - p1) / d);
res1 = q;
res2 = q + e;
}
lf dist_pp(vec p1, vec p2) {
return (p2 - p1).len();
}
lf dist_pl(vec p, vec l1, vec l2) {
return cross(l2 - l1, p - l1).len() / (l2 - l1).len();
}
vec perpendicular_pl(vec p, vec l1, vec l2) {
return l1 + (l2 - l1) * (dot(l2 - l1, p - l1) / (l2 - l1).sqr());
}
void solve() {
vec p1, d1, p2, d2;
lf r1, r2;
p1.scan();
d1.scan();
scanf("%lf", &r1);
p2.scan();
d2.scan();
scanf("%lf", &r2);
vec dir = cross(d1, d2);
if (dir.sqr() < 0.1) {
puts("No");
return;
}
vec l1, l2;
inter_ff(p1, d1, p2, d2, l1, l2); //求平面交线
lf dis = dist_pl(p2, l1, l2);
if (dis > r2) {
puts("No");
return;
}
vec delta = (l2 - l1).trunc(sqrt(r2 * r2 - dis * dis));
vec mid = perpendicular_pl(p2, l1, l2);
//a,b是直线与圆的交点
vec a = mid + delta;
vec b = mid - delta;
if ((dist_pp(a, p1) < r1) ^ (dist_pp(b, p1) < r1)) {
puts("Yes");
} else {
puts("No");
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}
Snatch Groceries(贪心 思维)
题意
给定
n
n
n个置信区间,求在两个置信区间重叠(包括端点)前,有多少个置信区间互不重叠。题面TL;DR:
意为 Too Long; Didn’t Read,Problem Description中只需读后两段。
题解
按
e
a
r
l
i
e
s
t
earliest
earliest升序排序,检查相邻两个区间是否重叠。对于第
i
i
i个置信区间,最有可能和它重叠的一定是
e
a
r
l
i
e
s
t
earliest
earliest最小的。
AC代码
const int N=1e5+10;
struct node {
int l,r;
bool operator<(const node &a) {
return l<a.l;
}
}p[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i].l>>p[i].r;
sort(p+1,p+n+1);
int ans=0;
for(int i=1;i<=n;i++)
{
if(i<n&&p[i].r>=p[i+1].l) break;
ans++;
}
cout<<ans<<endl;
}
return 0;
}
ShuanQ(数论 枚举)
题意
给定一个质数模数
M
M
M,设私钥为
P
P
P,对于公钥
Q
Q
Q满足
P
×
Q
≡
1
(
m
o
d
M
)
P \times Q \equiv 1 (mod \ M)
P×Q≡1(mod M),即
Q
Q
Q是
P
P
P模
M
M
M意义下的逆元。
加密公式:
e
n
c
r
y
p
t
e
d
_
d
a
t
a
=
r
a
w
_
d
a
t
a
×
P
m
o
d
M
encrypted\_data = raw\_data \times P \ mod \ M
encrypted_data=raw_data×P mod M
解密公式:
r
a
w
_
d
a
t
a
=
e
n
c
r
y
p
t
e
d
_
d
a
t
a
×
Q
m
o
d
M
raw\_data = encrypted\_data \times Q \ mod \ M
raw_data=encrypted_data×Q mod M
给出
P
P
P、
Q
Q
Q 和
e
n
c
r
y
p
t
e
d
_
d
a
t
a
encrypted\_data
encrypted_data
(
P
,
Q
,
e
n
c
r
y
p
t
e
d
_
d
a
t
a
<
M
)
(P,Q,encrypted\_data<M)
(P,Q,encrypted_data<M),问是否可以求出
r
a
w
_
d
a
t
a
raw\_data
raw_data,如果可以确定
r
a
w
_
d
a
t
a
raw\_data
raw_data,输出
r
a
w
_
d
a
t
a
raw\_data
raw_data,否则输出shuanQ
题解
由
P
×
Q
≡
1
(
m
o
d
M
)
P \times Q \equiv 1 (mod M)
P×Q≡1(modM)可得
k
M
=
P
×
Q
−
1
kM=P \times Q-1
kM=P×Q−1,这说明
M
M
M是
P
×
Q
−
1
P \times Q - 1
P×Q−1的一个比
P
P
P和
Q
Q
Q大的质因子,时间复杂度
s
q
r
t
(
P
×
Q
−
1
)
sqrt(P \times Q - 1)
sqrt(P×Q−1)
AC代码
int main()
{
int t;
cin>>t;
while(t--)
{
ll a,b,c;
cin>>a>>b>>c;
ll n=a*b-1;
ll ans;
int cnt=0;
for(ll i=2;i*i<=n;i++)
{
if(n%i==0)
{
while(n%i==0) n/=i;
if(i>a&&i>b&&a*b%i==1)
{
ans=i;
cnt++;
}
}
}
if(n>1&&n>a&&n>b&&a*b%n==1)
{
ans=n;
cnt++;
}
if(cnt>1||!cnt) cout<<"shuanQ"<<endl;
else cout<<b*c%ans<<endl;
}
return 0;
}
Luxury cruise ship(贪心 DP)
题意
Kayzin有7、31、365三种面值的硬币,给定一个费用,问是否可以用这三种硬币凑出这个费用,如果不存在输出
−
1
-1
−1,否则输出最少需要的硬币数量。
题解
对于
N
N
N较小的询问,用背包预处理出答案。如果
N
N
N较大,先用
365
365
365将
N
N
N减小到背包预处理的范围内,再算出答案。在下面的代码中预处理了
7
×
31
×
365
7 \times 31 \times 365
7×31×365以内的最小值,如果不确定预处理的范围,可以在空间和时间允许的范围内尽可能多预处理。
AC代码
typedef long long ll;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int N=7*31*366;
const int M=7*31*365;
ll f[N];
int main()
{
int t;
cin>>t;
memset(f,0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=M;i++)
{
if(i>=7) f[i]=min(f[i],f[i-7]+1);
if(i>=31) f[i]=min(f[i],f[i-31]+1);
if(i>=365) f[i]=min(f[i],f[i-365]+1);
}
while(t--)
{
ll n;
cin>>n;
if(n<=M)
{
if(f[n]<inf) cout<<f[n]<<endl;
else cout<<-1<<endl;
}
else
{
ll x=n-M;
ll ans=(x+364)/365;
ll y=n-365*ans;
ans+=f[y];
if(ans>=inf) cout<<-1<<endl;
else cout<<ans<<endl;
}
}
return 0;
}