UVA12558 埃及分數 Egyptian Fractions
阿新 • • 發佈:2020-08-14
題意描述
題目描述的翻譯挺清楚的了。
和原題的區別是多了禁用的分母。(還有毒瘤輸入輸出)
演算法分析
顯然這道題沒有什麼很好的數學方法來解決,所以可以使用搜索。
由於不確定深度,深搜顯然無窮無盡。
所以一開始考慮使用廣搜,如果不加改變空間複雜度顯然呈指數級增長。
- 使用啟發式搜尋來實現,但是此題顯然沒有必要。(有興趣的可以自行嘗試實驗)
- 使用迭代加深(ID)實現,程式碼較上一方法更容易實現。
不熟悉 ID 的同學可以先找別的題目瞭解一下。
假設當前已經到了 \(dep\) 個數,上一次使用的分母是 \(last\),目前迭代的最深次數是 \(d\)。
目前得到的分數和的為 \(\frac{a}{b}\)
那麼我們可以確定當前分母的上下界:
- \(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}\}\)。
- \(num\) 的最大值:\(\frac{\frac{x}{y}-\frac{a}{b}}{d-dep}\)。
解釋一下:
對於分母的最小值,由於分母單調遞增,所以至少是上一個分母 \(+1\)。
但是由於顯然 \(\frac{a}{b}+\frac{1}{num}\leq \frac{x}{y}\)
對於最大值,顯然至少 \((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\) 開始)。
到這裡讀者就可以開始嘗試程式碼實現了,但是還要注意兩點:
- 分數要約分。
- 十年 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;
}
完結撒❀。