[提高组集训2021] 模拟赛1

B

题目描述

有一个与辗转相除类似的函数 \(R(a,b)\),定义如下:

\[R(a,b)=\begin{cases}R(b,a)&a<b\\R(\lfloor\frac{a}{b}\rfloor,b)&a\geq b>1\\a&b=1\end{cases} \]

给两个整数 \(g,h\),尝试构造 \(a,b\) 使得 \(\gcd(a,b)=g,R(a,b)=h\)

\(1\leq g\leq 2\cdot 10^5,2\leq h\leq 2\cdot 10^5\)

解法

看数据范围还以为是枚举,结果是构造。

难满足的条件是 \(R(a,b)=h\),我们不妨从末状态开始构造,设 \(z\in[h^{\lceil\log_hg\rceil},2\cdot h^{\lceil\log_hg\rceil})\)

\(R(1,h)=R(z,h)=R(hz+g,z)\)

\(z\)\(g\) 的倍数即可,\(z=g\cdot\lceil\frac{h^{\lceil\log_hg\rceil}}{g}\rceil\)

#include <cstdio>
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T;
signed main()
{
	T=read();
	while(T--)
	{
		int g=read(),h=read(),hk=1;
		while(1)
		{
			hk*=h;
			if(hk>g)
			{
				int b=(hk+g-1)/g*g,a=b*h+g;
				printf("%lld %lld\n",a,b);
				break;
			}
		}
	}
}

C

题目描述

有一棵 \(n\) 个顶点的树,要求把每条边都断开,断开的代价是两边的最大权值之和,最小化代价。

\(n\leq 10^5\)

解法

不难发现一个结论:每次都删除最大点所连的边是最优的。

删点不如加点,我们按权值从小到大加入点,如果两个点都被加入那么恢复这条边,可以用并查集维护。

D

题目描述

点此看题

解法

这道题要求一个复杂路径,基本上就只能用 \(dp\) 解决了,但发现 \(dp\) 不动,这时候枚举起点会好做一些。

然后就可以简单 \(dp\) 了,设 \(dp[u][0/1][0/1]\) 表示解决 \(u\) 子树内的所有问题,\(u\) 现在的状态是什么,是否需要返回点 \(u\),转移就考虑合并子树,可以两个返回点 \(u\) 的状态合并,或者一个返回一个不返回的状态合并。

在转移的时候考虑一小步,也就是我们可以通过 \(u,v\) 之间多走一次来改变两个点的点亮情况。

其实不需要枚举起点,直接换根就行了,这道题的转移是满足加法性质的,所以可以预处理前后缀最小值就可以走到儿子去了,时间复杂度 \(O(n)\)

总结

在直接做困难之时,可以适当的枚举简化问题。

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 500005;
const int inf = 1e9;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,ans,a[M],tag[M],dp[M][2][2];vector<int> g[M];
void init(int u)
{
	tag[u]=a[u];
	for(int i=0;i<2;i++)
		for(int j=0;j<2;j++)
			dp[u][i][j]=(j^a[u])*inf;
}
void add(int x,int y)
{
	tag[x]&=tag[y];
	if(tag[y]) return ;
	int tmp[2][2]={};
	for(int d=0;d<2;d++)
		tmp[0][d]=min(
		2+min(dp[x][0][d^1]+dp[y][1][0],dp[x][0][d]+dp[y][1][1]+2),
		1+min(dp[x][1][d]+dp[y][0][0],dp[x][1][d^1]+dp[y][0][1]+2)
		);
	for(int d=0;d<2;d++)
		tmp[1][d]=2+min(dp[x][1][d^1]+dp[y][1][0]
		,dp[x][1][d]+dp[y][1][1]+2);
	memcpy(dp[x],tmp,sizeof tmp);
}
void dfs(int u,int fa)
{
	init(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		dfs(v,u);
		add(u,v);
	}
}
void fuck(int u,int fa)
{
	init(u);
	int len=g[u].size();
	bool *fl=new bool[len];
	bool *fr=new bool[len];
	int ***L=new int**[len];
	int ***R=new int**[len];
	for(int i=0;i<len;i++)
	{
		L[i]=new int*[2];
		for(int j=0;j<2;j++)
		{
			L[i][j]=new int[2];
			for(int k=0;k<2;k++)
				L[i][j][k]=dp[u][j][k];
		}
		fl[i]=tag[u];
		add(u,g[u][i]);
	}
	init(u);
	for(int i=len-1;i>=0;i--)
	{
		R[i]=new int*[2];
		for(int j=0;j<2;j++)
		{
			R[i][j]=new int[2];
			for(int k=0;k<2;k++)
				R[i][j][k]=dp[u][j][k];
		}
		fr[i]=tag[u];
		add(u,g[u][i]);
	}
	ans=min(ans,dp[u][0][1]);
	for(int i=0;i<len;i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		memset(dp[u],0x3f,sizeof dp[u]);
		for(int i1=0;i1<2;i1++) for(int i2=i1^1;i2<2;i2++)
			for(int j1=0;j1<2;j1++) for(int j2=0;j2<2;j2++)
				dp[u][i1&i2][j1^j2^a[u]]=min(
				dp[u][i1&i2][j1^j2^a[u]],
				L[i][i1][j1]+R[i][i2][j2]);
		tag[u]=fl[i]&fr[i]; 
		fuck(v,u);
	}
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		scanf("%1d",&a[i]);
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		g[u].push_back(v);
		g[v].push_back(u);
	}
	ans=1e9;
	dfs(1,0);
	fuck(1,0);
	printf("%d\n",ans);
}
posted @ 2021-06-26 15:26  C202044zxy  阅读(203)  评论(0编辑  收藏  举报