• 逼近法(例 poj3208、poj1037)


    ​ 逼近法是一种很奇妙的算法,以为“逼近”这一种思想在很多的算法中都有体现。诸如:像我们的二分答案,不断地排除决策集合的一半以接近我们的最终答案;我们的树上倍增求 $ 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;
    }
    
  • 相关阅读:
    怎么查看当前进程?怎么执行退出?怎么查看当前路径?
    简述正则表达式及其用途?
    Java 中,抽象类与接口之间有什么不同?
    哪个命令专门用来查看后台任务?
    什么是线程池?有哪几种创建方式?
    什么是多线程的上下文切换?
    WebApplicationContext?
    synchronized、volatile、CAS 比较?
    使用 Spring 有哪些方式?
    线程池的优点?
  • 原文地址:https://www.cnblogs.com/812-xiao-wen/p/11006202.html
Copyright © 2020-2023  润新知