1. 程式人生 > 實用技巧 >UVA12558 埃及分數 Egyptian Fractions

UVA12558 埃及分數 Egyptian Fractions

題意描述

題目描述的翻譯挺清楚的了。

和原題的區別是多了禁用的分母。(還有毒瘤輸入輸出)

演算法分析

顯然這道題沒有什麼很好的數學方法來解決,所以可以使用搜索。

由於不確定深度,深搜顯然無窮無盡。

所以一開始考慮使用廣搜,如果不加改變空間複雜度顯然呈指數級增長。

  1. 使用啟發式搜尋來實現,但是此題顯然沒有必要。(有興趣的可以自行嘗試實驗)
  2. 使用迭代加深(ID)實現,程式碼較上一方法更容易實現。

不熟悉 ID 的同學可以先找別的題目瞭解一下。

假設當前已經到了 \(dep\) 個數,上一次使用的分母是 \(last\),目前迭代的最深次數是 \(d\)

目前得到的分數和的為 \(\frac{a}{b}\)

,題目要求的總和是 \(\frac{x}{y}\),當前選的分母是 \(num\)

那麼我們可以確定當前分母的上下界:

  1. \(num\) 的最小值:\(\max\{last+1,\frac{1}{\frac{x}{y}+\frac{a}{b}}\}=\max\{last+1,\frac{b\times y}{b\times x-a\times y}\}\)
  2. \(num\) 的最大值:\(\frac{\frac{x}{y}-\frac{a}{b}}{d-dep}\)

解釋一下:

對於分母的最小值,由於分母單調遞增,所以至少是上一個分母 \(+1\)

但是由於顯然 \(\frac{a}{b}+\frac{1}{num}\leq \frac{x}{y}\)

,所以還要取 \(\min\)

對於最大值,顯然至少 \((d-dep)\times \frac{1}{num}+\frac{a}{b}\geq \frac{x}{y}\)

因為分母單調遞增,如果即使全部使用當前分母還是不能達到 \(\frac{x}{y}\) 就剪枝。

當然為了精度問題,我們可以將其轉化為:

\[b\times y\times (d-dep)+a\times num\times y>=x\times b\times num \]

注意一下這裡 \(dep\) 指的是已經進行的個數(當前的未統計,也就是從 \(0\) 開始)。

到這裡讀者就可以開始嘗試程式碼實現了,但是還要注意兩點:

  1. 分數要約分。
  2. 十年 OI 一場空。

程式碼實現

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set> 
#define int long long
using namespace std;

int x,y,q,T,dep,tot=0;
bool vis[1010];
vector<int>now,ans;
set<int>s;

int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
	while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
	return x*f; 
}

int gcd(int x,int y){//最大公約數用來約分。
	while(y^=x^=y^=x%=y);
	return x;
}

void chck(){//更新答案。
	if(ans.empty() || ans.size()>now.size()){ans=now;return;}
	if(now.size()>ans.size()) return;
	for(int i=(int)now.size()-1;i>=0;i--){
		if(now[i]<ans[i]){
			ans=now;return;
		} else if(now[i]>ans[i])
			return;
	}
	return;
}

void dfs(int d,int last,int a,int b){
	//printf("%d %d %d %d\n",d,last,a,b);
	if(a*y>b*x) return;//貌似沒什麼用的小剪枝,避免出現 a/b>x/y 的現象。
	if(d>=dep){
		if(a*y==b*x) chck();
		return;
	}
	int p=gcd(a,b);//約分。
	a/=p,b/=p;
	int head=max(last,y*b/(x*b-y*a));//最小值。
	for(int i=head;b*y*(dep-d)+a*i*y>=x*b*i;i++){//最大值。
		if(s.count(i)) continue;//禁用的不用。
		now.push_back(i);
		dfs(d+1,i+1,a*i+b,b*i);//加上 1/i 繼續 dfs。
		now.pop_back();//回溯。
	}
	return;
}

void init(){//多組資料記得清零。
	now.clear();ans.clear();
	memset(vis,false,sizeof(vis));
	s.clear();
	return;
}

void work(){
	init();
	x=read(),y=read(),q=read();
	int k;
	while(q--){k=read();s.insert(k);}//判斷是否禁用。
	int p=gcd(x,y);x/=p,y/=p;//記得約分。
	for(dep=1;ans.empty();dep++)
		dfs(0,2,0,1);//注意初值。
	printf("Case %d: %d/%d=",++tot,x,y);//毒瘤輸出。
	printf("1/%d",ans[0]);
	for(int i=1;i<(int)ans.size();i++)
		printf("+1/%d",ans[i]);
	puts("");
	return;
}

signed main(){
	T=read();
	while(T--) work();
	return 0;
}

完結撒❀。