2022杭电多校第二场题解

2022杭电多校第二场

在这里插入图片描述

Static Query on Tree(虚树 树链剖分 线段树 树形DP)

题意
X国有 n n n个城市和 n − 1 n-1 n1条单向道路,所有的城市都能到达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 q2×105A+B+C2×105

分析
所有城市都能到达1号城市,说明图是连通的,又因为是 n − 1 n-1 n1条边,所以题目给定的是一棵树,并且树上的边是有方向的。观察数据范围发现,询问次数较多,但总共涉及的点数是 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×Q1(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×Q1(modM)可得 k M = P × Q − 1 kM=P \times Q-1 kM=P×Q1,这说明 M M M P × Q − 1 P \times Q - 1 P×Q1的一个比 P P P Q Q Q大的质因子,时间复杂度 s q r t ( P × Q − 1 ) sqrt(P \times Q - 1) sqrt(P×Q1)

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值