逼近法是一种很奇妙的算法,以为“逼近”这一种思想在很多的算法中都有体现。诸如:像我们的二分答案,不断地排除决策集合的一半以接近我们的最终答案;我们的树上倍增求 $ LCA $ 算法,一次次的减小我们跳的距离以确定祖先的准确位置;我们的模拟退火需要用一个温度,每次操作后这个温度一定会下降,逼近正确答案;再如我们大多数函数的收敛性,在多次计算后会趋向某一定值;还有我们DP的拼凑与试填的思想也有逼近的味道。
我们逼近的前提就在于我们是否可以在每一次操作后排除一些决策集合,并且是高效的排除。就像:银河系 -> 太阳系 -> 太阳系 -> 地球 -> 中国 -> 湖南 -> 长沙。还有我做出来的第一道逼近题:存在很多长度为 $ n $ 的有26个字母组成的字符串,我们将他们按字典序排序,现在给出序号或字符串,要求输出它对应的序号或字符串。这是一道傻逼题,我们可以试填第一个字母,然后它后面剩余的位置有很多种填法,我们看这么多填法是否大于我们的序号,然后我们从小到大填,这样可以确定第一个字母,紧接着可以推出第二个第三个.....然后就是答案。
虽然写的都是些浅显的东西,但如果这个思想和其他东西串联起来就不见得简单了。好了说白了就是来写题解的,不管这么多了。。。。。。。。。。。。。。。。。。。。
Poj 1037 A decorative fence
大致题意:我们要构建长度为 $ N $ 的排列,使得排列里的每一个元素同时大于或小于旁边两个数。可以预见这样的数很多,我们将这些排列按字典序排序,现在给出序号求对应的排列。
$ solution: $
就是我举的那个例子的一个变形,只不过多了一个限制:(每一个元素同时大于或小于旁边两个数)。这样我们只需要在逼近的时候特判第一个数是大于或小于第二个数(当然不能忘记我们DP第一维始终得是排列长度,这样才符合逼近思想),然后在逼近的同时维护大于和小于即可!
$ code: $
#include<iostream> #include<cstdio> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdlib> #include<ctime> #include<cmath> #include<vector> #include<queue> #include<map> #include<set> #define ll long long #define db double #define inf 0x7fffffff #define rg register int using namespace std; ll m; int t,n,l; ll f[25][25][2]; bool use[25]; inline ll qr(){ register char ch; register bool sign=0; ll res=0; while(!isdigit(ch=getchar())) if(ch=='-')sign=1; while(isdigit(ch)) res=res*10+(ch^48),ch=getchar(); return sign?-res:res; } int main(){ //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=20; f[1][1][0]=f[1][1][1]=1; for(rg i=2;i<=n;++i){ for(rg j=1;j<=i;++j){ for(rg k=1;k<j;++k) f[i][j][1]+=f[i-1][k][0]; for(rg k=j;k<i;++k) f[i][j][0]+=f[i-1][k][1]; } } t=qr(); while(t--){ n=qr(); m=qr(); register bool k=0; for(rg i=1;i<=n;++i) use[i]=0; for(rg i=1;i<=n;++i){ if(f[n][i][1]>=m){ k=1; l=i; break; } else m-=f[n][i][1]; if(f[n][i][0]>=m){ k=0; l=i; break; } else m-=f[n][i][0]; }use[l]=1; printf("%d",l); for(rg i=n-1;i>=1;--i){ k^=1; rg s=0; for(rg j=1;j<=n;++j){ if(use[j])continue;; ++s; if((!k&&j<l)||(k&&j>l)){ if(f[i][s][k]>=m){ l=j; break; } else m-=f[i][s][k]; } }printf(" %d",l); use[l]=1; }puts(""); } return 0; }
Poj 3208 Apocalypse Someday
大致题意:我们规定所有含有连续三个数字6的自然数都是“神秘数”。我们将这些数按字典序排序(及时从小到大),现在给出序号,让你求出对应的“神秘数”。
$ solution: $
又是一道变形题,但这一次改的比较难了一点,因为它需要连续的三个六,这给我们的逼近带来了后效性,如果我们确定的上一个数是6,那么这次逼近里面,开头只带有两个数字6的数也会加如决策集合。所以我们DP维护数字长度为i,含有连续的j个六的数字有多少。然后在逼近时另取一个变量来存已经连续确定了几个六然后找准决策集合进行逼近!
$ code: $
#include<iostream> #include<cstdio> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdlib> #include<ctime> #include<cmath> #include<vector> #include<queue> #include<map> #include<set> #define ll long long #define db double #define inf 0x7fffffff #define rg register int using namespace std; int t,n,m; ll f[25][4]; inline int qr(){ register char ch; register bool sign=0; rg res=0; while(!isdigit(ch=getchar())) if(ch=='-')sign=1; while(isdigit(ch)) res=res*10+(ch^48),ch=getchar(); return sign?-res:res; } int main(){ //freopen(".in","r",stdin); //freopen(".out","w",stdout); f[0][0]=1; for(rg i=0;i<20;++i){ for(rg j=0;j<3;++j){ f[i+1][j+1]+=f[i][j]; f[i+1][0]+=f[i][j]*9; }f[i+1][3]+=f[i][3]*10; } t=qr(); while(t--){ n=qr(); rg k=3; for(m=3;f[m][3]<n;++m); for(rg i=m;i>=1;--i){ for(rg j=0;j<=9;++j){ ll tot=f[i-1][3]; if(j==6||!k) for(rg o=max(k-(j==6),0);o<3;++o) tot+=f[i-1][o]; if(tot<n)n-=tot; else{ if(k)j==6?--k:k=3; printf("%d",j); break; } } }puts(""); } return 0; }
来源:https://www.cnblogs.com/812-xiao-wen/p/11006202.html