[提高组集训2021] 模拟赛1
B
题目描述
有一个与辗转相除类似的函数 \(R(a,b)\),定义如下:
给两个整数 \(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);
}