2022 杭电多校赛 Day 2 Solution Set

杭电的题是不是越来越水,没活整了吗。

A. Static Query on Tree

\(A\) 中所有点往根打上标记 \(1\)\(B\) 中所有点往根打上标记 \(2\)\(C\) 中所有点的子树打上标记 \(3\),同时有三个标记的点可以作为答案。处理方法很多,比如直接树剖区间或区间最大值及其个数,或者是虚树。下面用虚树实现,标记 \(1,2\) 遍历树即可,标记 \(3\) 采用树状数组,注意要把根加入虚树。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<18],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int MOD=998244353;
inline int Add(int x,int y){return x+y>=MOD?x+y-MOD:x+y;}
inline int Sub(int x,int y){return x<y?x-y+MOD:x-y;}
inline int Mul(int x,int y){return 1ll*x*y%MOD;}
inline int add(int &x,int y){return x=Add(x,y);}
inline int sub(int &x,int y){return x=Sub(x,y);}
inline int mul(int &x,int y){return x=Mul(x,y);}
int QuickPow(int x,int p=MOD-2)
{
	int ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
/*
 建出虚树。
 然后呢?
 C 子树打上 C 标记。
 哦,A B 也跟着打,三个标记都有的就是答案捏。
 这个 07 是真的蠢。浪费时间。
*/
void Solve();
int main(){
	int T=read();
	while(T-->0)	Solve();
	return 0;
}
int n,q;
vector<int> G[200005];
int fa[20][200005];
int dfn[200005],sjc,siz[200005],dep[200005];
void dfs(int u)
{
	dfn[u]=++sjc,siz[u]=1;
	for(auto v:G[u])	dep[v]=dep[u]+1,dfs(v),siz[u]+=siz[v];
}
inline int lowbit(int x){return x&(-x);}
struct BinaryIndexedTree{
	int tr[200005];
	void clear(){memset(tr,0,sizeof tr);}
	void modify(int x,int v){for(int i=x;i<=n;i+=lowbit(i))	tr[i]+=v;}
	int query(int x){int ans=0;for(int i=x;i;i^=lowbit(i))	ans+=tr[i];return ans;}
}bit;
int lgs[200005];
int LCA(int u,int v)
{
	if(dep[u]>dep[v])	swap(u,v);
	while(dep[u]<dep[v])	v=fa[lgs[dep[v]-dep[u]]][v];
	if(u==v)	return u;
	for(int i=19;~i;--i)	if(fa[i][v]^fa[i][u])	u=fa[i][u],v=fa[i][v];
	return fa[0][u];
}
int tag[200005][2];
int sr,seq[600005],A[200005],B[200005],C[200005];
int ans;
void dfs0(int u,int pre)
{
	for(auto v:G[u])	dfs0(v,u),tag[u][0]+=tag[v][0],tag[u][1]+=tag[v][1];
	if(tag[u][0] && tag[u][1] && bit.query(u))	ans+=dep[u]-dep[pre];
}
void Solve()
{
	n=read(),q=read();
	for(int i=1;i<=n;++i)	G[i].clear();
	for(int i=2;i<=n;++i)	G[fa[0][i]=read()].push_back(i);
	for(int i=1;i<=19;++i)	for(int j=1;j<=n;++j)	fa[i][j]=fa[i-1][fa[i-1][j]];
	for(int i=2;i<=n;++i)	lgs[i]=lgs[i>>1]+1;
	sjc=0;
	dep[1]=1;
	dfs(1);
	for(int i=1;i<=n;++i)	G[i].clear();
	while(q-->0)
	{
		sr=0;
		int a=read(),b=read(),c=read();
		for(int i=1;i<=a;++i)	A[i]=read(),seq[++sr]=A[i],tag[A[i]][0]=1;
		for(int i=1;i<=b;++i)	B[i]=read(),seq[++sr]=B[i],tag[B[i]][1]=1;
		for(int i=1;i<=c;++i)	C[i]=read(),seq[++sr]=C[i];
		seq[++sr]=1;
		for(int i=1;i<=c;++i)	bit.modify(dfn[C[i]],1),bit.modify(dfn[C[i]]+siz[C[i]],-1);
		auto cmp=[&](int x,int y){return dfn[x]<dfn[y];};
		sort(seq+1,seq+1+sr,cmp);
		sr=unique(seq+1,seq+1+sr)-seq-1;
		for(int i=sr;i>=2;--i)	seq[++sr]=LCA(seq[i],seq[i-1]);
		sort(seq+1,seq+1+sr,cmp);
		sr=unique(seq+1,seq+1+sr)-seq-1;
		for(int i=2;i<=sr;++i)	G[LCA(seq[i-1],seq[i])].push_back(seq[i]);
		ans=0;
		dfs0(seq[1],0);
		write(ans),puts("");
		for(int i=1;i<=sr;++i)	G[seq[i]].clear(),tag[seq[i]][0]=tag[seq[i]][1]=0;
		for(int i=1;i<=c;++i)	bit.modify(dfn[C[i]],-1),bit.modify(dfn[C[i]]+siz[C[i]],1);
	}
}

B. C++ to Python

模拟,保留括号逗号负号和数字就好。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
void Solve();
int main(){
	int T;
	scanf("%d",&T);
	while(T-->0)	Solve();
	return 0;
}
char s[1005];
void Solve()
{
	scanf("%s",s+1);
	int n=strlen(s+1);
	for(int i=1;i<=n;++i)	if(s[i]=='-' || s[i]=='(' || s[i]==')' || isdigit(s[i]) || s[i]==',')	putchar(s[i]);
	puts("");
}

C. Copy

考虑倒序,这样复制并插在后面一段就只会影响查询的位置。

注意到只需要回答异或和,我们只需要留下每个位置有没有被问到过奇数次就好了,同时影响查询的位置是一段全部左移,可以用 bitset 维护。时间复杂度是 \(O(\frac{nq}{\omega})\) 的。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<18],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
void Solve();
int main(){
	int T=read();
	while(T-->0)	Solve();
	return 0;
}
bitset<100005> Ans,tmp;
int a[100005];
int n,m;
int op[100005],l[100005],r[100005];
void Solve()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i)	a[i]=read();
	Ans.reset();
	for(int i=1;i<=m;++i)
	{
		op[i]=read(),l[i]=read();
		if(op[i]==1)	r[i]=read();
	}
	for(int i=m;i;--i)
	{
		if(op[i]==2)	Ans.flip(l[i]);
		else
		{
			int d=r[i]-l[i]+1;
			l[i]+=d,r[i]+=d;
			if(l[i]>n)	continue;
			tmp=Ans;
			Ans=(Ans<<(100005-l[i]))>>(100005-l[i]);
			tmp=(tmp>>l[i])<<(l[i]-d);
			Ans^=tmp;
		}
	}
	int ans=0;
	for(int i=1;i<=n;++i)	if(Ans.test(i))	ans^=a[i];
	write(ans),puts("");
}

D. Keychains

先两个圆所在平面求交线,然后让交线交球 \(A\)(也就是半径坐标和圆 \(A\) 相等),这样得到两个交点,比较到圆心的距离就好了(如果一个在外一个在内就寄了)。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
char buf[1<<18],*P1=buf,*P2=buf;
#define getchar() (P1==P2 && (P2=(P1=buf)+fread(buf,1,1<<18,stdin),P1==P2)?EOF:*P1++)
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9')	f=(c=='-'?-1:f),c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x*f;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
void Solve();
int main(){
	int T=read();
	while(T-->0)	Solve();
	return 0;
}
struct Point{
	DB x,y,z;
	Point(DB X=0,DB Y=0,DB Z=0){x=X,y=Y,z=Z;}
	inline void Scan(){x=read(),y=read(),z=read();}
	DB& operator [] (int id){if(id==0)	return x;if(id==1)	return y;return z;}
	Point operator + (Point ano) const {return Point(x+ano[0],y+ano[1],z+ano[2]);}
	Point operator - (Point ano) const {return Point(x-ano[0],y-ano[1],z-ano[2]);}
	Point operator * (DB ano) const {return Point(x*ano,y*ano,z*ano);}
	Point operator / (DB ano) const {return Point(x/ano,y/ano,z/ano);}
	DB dis(){return sqrt(x*x+y*y+z*z);}
	Point basic(){DB d=dis();return Point(x/d,y/d,z/d);}
};
Point chaji(Point p,Point q){return Point(p.y*q.z-p.z*q.y,p.z*q.x-p.x*q.z,p.x*q.y-p.y*q.x);} // 叉积 - 垂直 p,q 张成的平面。
DB dianji(Point p,Point q){return p.x*q.x+p.y*q.y+p.z*q.z;}
/*
 litijihe !!!
 平面求交,再求交线与球的交。
 飘飘蛋牛逼!
*/
DB pointToLine(Point p,Point c,Point d){return (chaji(d-c,p-c).dis())/((d-c).dis());} // 面积除以底
Point LineCrossLine(Point p,Point c,Point d){return c+(d-c)*(dianji(d-c,p-c)/((d-c).dis()*(d-c).dis()));} // xiangbi dongde doudong
DB pointToPoint(Point p,Point q){return (q-p).dis();}
void Solve()
{
	Point p1,q1,p2,q2;
	DB r1,r2;
	p1.Scan(),q1.Scan(),r1=read();
	p2.Scan(),q2.Scan(),r2=read();
	Point sec1,sec2;
	{
	Point tmp1=chaji(q1,q2),tmp2=chaji(q1,tmp1);
	DB d=dianji(q2,tmp2);
	Point tmp=p1+tmp2*(dianji(q2,p2-p1)/d);
	sec1=tmp,sec2=tmp+tmp1;
	}
	DB dist=pointToLine(p2,sec1,sec2);
	Point crossP=LineCrossLine(p2,sec1,sec2);
	Point seg=((sec2-sec1).basic())*sqrt(r2*r2-dist*dist);
	Point c1=crossP-seg,c2=crossP+seg;
	bool f=pointToPoint(p1,c1)<r1,g=pointToPoint(p1,c2)<r1;
	puts((f^g)?"Yes":"No");
}

E. Slayers Come

一个技能显然会爆掉一个区间的怪物,那么先排序并查集求出这个区间,问题变成了区间覆盖,有多少个区间选法能够炸掉所有怪物。

直接 DP,线段树优化。想必这个问题也是典中典。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<18],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9')	f=(c=='-'?-1:f),c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x*f;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int MOD=998244353;
inline int Add(int x,int y){return x+y>=MOD?x+y-MOD:x+y;}
inline int Sub(int x,int y){return x<y?x-y+MOD:x-y;}
inline int Mul(int x,int y){return 1ll*x*y%MOD;}
inline int add(int &x,int y){return x=Add(x,y);}
inline int sub(int &x,int y){return x=Sub(x,y);}
inline int mul(int &x,int y){return x=Mul(x,y);}
int QuickPow(int x,int p=MOD-2)
{
	int ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
/*
 先算搞死一个怪物的区间。
 然后就是区间覆盖 DP,可以直接线段树。
*/
void Solve();
int main(){
	int T=read();
	while(T-->0)	Solve();
	return 0;
}
int n,m;
int a[100005],b[100005];
struct unionFindSet{
	int fa[100005];
	void makeSet(int up){for(int i=0;i<=up;++i)	fa[i]=i;}
	int findSet(int x){return x==fa[x]?x:fa[x]=findSet(fa[x]);}
	int& operator [] (int x){return fa[x];}
}ufs;
struct node{
	int w,id;
	node(){}
	node(int W,int I){w=W,id=I;}
	bool operator < (node ano) const {return w<ano.w;}
}d[100005];
struct skill{
	int p,L,R;
	int l,r;
	inline void Scan(){p=read(),L=read(),R=read();}
	inline bool operator < (skill ano) const {return R>ano.R;}
}sk[100005];
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define Mm int mid=(l+r)>>1
int sum[400005],t1[400005],t2[400005];
void build(int l,int r,int now)
{
	sum[now]=t1[now]=0,t2[now]=1;
	if(l==r)	return ;
	Mm;
	build(l,mid,lc(now)),build(mid+1,r,rc(now));
}
inline void push_up(int now){sum[now]=Add(sum[lc(now)],sum[rc(now)]);}
inline void push_down(int now,int l,int r)
{
	if(t2[now]^1)
	{
		int &c=t2[now];
		mul(t2[lc(now)],c);
		mul(t1[lc(now)],c);
		mul(t2[rc(now)],c);
		mul(t1[rc(now)],c);
		mul(sum[lc(now)],c);
		mul(sum[rc(now)],c);
		c=1;
	}
	if(t1[now])
	{
		int &c=t1[now];
		Mm;
		add(t1[lc(now)],c);
		add(t1[rc(now)],c);
		add(sum[lc(now)],Mul(c,mid-l+1));
		add(sum[rc(now)],Mul(c,r-mid));
		c=0;
	}
}
void modify(int l,int r,int now,int x,int y,int w1,int w2) // w1 mul w2 add
{
	if(x>y || r<x || l>y)	return ;
	if(x<=l && r<=y)
	{
		if(w1^1)
		{
			mul(t1[now],w1);
			mul(t2[now],w1);
			mul(sum[now],w1);
		}
		if(w2)
		{
			add(t1[now],w2);
			add(sum[now],Mul(w2,r-l+1));
		}
		return ;
	}
	push_down(now,l,r);
	Mm;
	if(x<=mid)	modify(l,mid,lc(now),x,y,w1,w2);
	if(mid<y)	modify(mid+1,r,rc(now),x,y,w1,w2);
	push_up(now);
}
int query(int l,int r,int now,int x,int y)
{
	if(x>y || r<x || l>y)	return 0;
	if(x<=l && r<=y)	return sum[now];
	push_down(now,l,r);
	Mm,ret=0;
	if(x<=mid)	add(ret,query(l,mid,lc(now),x,y));
	if(mid<y)	add(ret,query(mid+1,r,rc(now),x,y));
	return ret;
}
int l[100005],r[100005];
void Solve()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i)	a[i]=read(),b[i]=read();
	for(int i=1;i<=m;++i)	sk[i].Scan();
	ufs.makeSet(n);
	for(int i=1;i<n;++i)	d[i]=node(a[i]-b[i+1],i);
	sort(d+1,d+n);
	sort(sk+1,sk+1+m);
	{
	int p=n-1;
	for(int i=1;i<=m;++i)
	{
		while(p && d[p].w>=sk[i].R)	ufs[d[p].id]=d[p].id+1,--p;
		sk[i].r=ufs.findSet(sk[i].p);
	}
	}
	ufs.makeSet(n);
	for(int i=1;i<n;++i)	d[i]=node(a[i+1]-b[i],i);
	sort(d+1,d+n);
	sort(sk+1,sk+1+m,[&](skill P,skill Q){return P.L>Q.L;});
	{
	int p=n-1;
	for(int i=1;i<=m;++i)
	{
		while(p && d[p].w>=sk[i].L)	ufs[d[p].id+1]=d[p].id,--p;
		sk[i].l=ufs.findSet(sk[i].p);
	}
	}
	sort(sk+1,sk+1+m,[&](skill P,skill Q){return P.r<Q.r;});
	for(int i=1;i<=m;++i)	l[i]=sk[i].l,r[i]=sk[i].r;
	build(0,n,1);
	modify(0,n,1,0,0,1,1);
	for(int i=1;i<=m;++i)
	{
		int c=query(0,n,1,l[i]-1,r[i]);
		modify(0,n,1,r[i],r[i],1,c);
		modify(0,n,1,0,l[i]-2,2,0);
	}
	write(query(0,n,1,n,n)),puts("");
}

F. Bowcraft

注意到不同的书很少,支持 \(O(KAB)\) 的做法,那就先把所有书拿出来。

题目中提到一个“最优策略”,我们考虑一个最优策略。

wozenme haimeixiewan gugule

posted @ 2022-07-23 12:26  SyadouHayami  阅读(178)  评论(3编辑  收藏  举报

My Castle Town.