[提高組集訓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,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);
}