1. 程式人生 > 其它 >[提高組集訓2021] 模擬賽1

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