zoukankan      html  css  js  c++  java
  • 0x5C~0x5D

    0x5C~0x5D

    0x5C 计数类DP

    a.[√] Gerald and Giant Chess

    题目传送

    sol:

    发现格子数很大,但是黑色格子数很小,所以考虑往黑色格子上靠。

    所以考虑到容斥一下,即 不经过黑格子的路径条数=路径总条数-至少经过一个黑格子的路径条数。

    从点((1,1))到点((x,y))的路径总条数应该为(C_{x+y-2}^{x-1}) ,只需求经过了黑格子的路径条数。

    容易想到对把这些黑格子拿出来,同时按照距离点((1,1))的远近(或按行列)排序,转为序列问题。

    (f[i])表示不经过其他黑格子,从((1,1))(序列上)第i个黑格子((x_i,y_i))的路径条数。

    转移应该为:

    [f[i]=C_{x_i+y_i-2}^{x_i-1}-sum_{j=0}^{i-1}f[j]*C_{x_i+y_i-x_j+y_j}^{x_i-y_i} (x_i≥x_j , y_i≥y_j) ]

    注意到答案实际上即为(f[n+1])

    code:

    #include<cmath>
    #include<string>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define RG register
    #define IL inline
    #define LL long long
    #define DB double
    using namespace std;
    
    IL int gi() {
       RG int x=0,w=0; char ch=getchar();
       while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
       while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
       return w?-x:x;
    }
    
    const int N=2003;
    const int M=2e5+3;
    const int mod=1e9+7;
    
    int h,w,n,fc[M],fcn[M];
    LL f[N];
    
    struct Black{int x,y;}b[N];
    IL bool cmp(Black A,Black B) {return A.x+A.y<B.x+B.y;}
    
    IL int qpow(int x,int p) {
        RG int ans=1;
        for(;p;p>>=1,x=1ll*x*x%mod)
            if(p&1) ans=1ll*ans*x%mod;
        return ans;
    }
    
    IL int C(int a,int b) {return 1ll*fc[a]*fcn[b]%mod*fcn[a-b]%mod;}
    
    int main()
    {
        RG int i,j;
       	h=gi(),w=gi(),n=gi();
        for(i=1;i<=n;++i) b[i].x=gi(),b[i].y=gi();
        b[0]=(Black){1,1},b[n+1]=(Black){h,w};
        sort(b+1,b+n+2,cmp);
        fc[0]=fcn[0]=1;
        for(i=1;i<=h+w;++i)
            fc[i]=1ll*fc[i-1]*i%mod,fcn[i]=qpow(fc[i],mod-2);
        for(i=1;i<=n+1;++i) {
            RG LL tmp=0;
            RG int now=b[i].x+b[i].y;
    		for(j=1;j<i;++j)
    			if(b[i].x>=b[j].x&&b[i].y>=b[j].y)
    				tmp=(tmp+1ll*f[j]*C(now-b[j].x-b[j].y,b[i].x-b[j].x)%mod)%mod;
            f[i]=((C(b[i].x+b[i].y-2,b[i].x-1)-tmp)%mod+mod)%mod;						 
        }
        printf("%lld
    ",f[n+1]);
        return 0;
    }
    

    b.[√] Connected Graph

    题目传送

    sol:

    吐槽:E心的高精!!!code交poj上SE(System Error)什么鬼,,,

    进入正题,

    同上面的那一个题,连通无向图数=图总数-不连通无向图数。

    x个点的图的总数显然为(2^{(frac {x*(x-1)}{2})})

    假设现在已经求出了i-1个点构成的连通无向图数,记为(f[i-1])

    现在考虑i个点构成的图的生成,只需考虑不连通的无向图数。

    可以这样考虑,整个图可以分为一个连通块(A)+若干连通块(B)。

    只要保证A部分和B部分之间没有连边就好了。

    所以考虑维护A,让A部分逐渐变化,可以保证不重不漏。

    用f表示出来,则(f[i])的求法应该为:

    [f[i]=2^{(frac {i*(i-1)}{2})}-sum_{j=1}^{i-1}f[j]*C_{i-1}^{j-1}*2^{(frac {(i-j)*(i-j-1)}{2})} ]

    code(不是我自己的(╯▽╰)):

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    struct Int {
    	int a[800],len;
    	Int(int x=0) {
    		len=0;
    		memset(a,0,sizeof(a));
    		do {
    			a[++len]=x%10;
    			x/=10;
    		}while(x);
    	}
    	inline void update(int n) {
    		int k;
    		for(k=1;k<=n||a[k];++k)
    			if(a[k]>=10) a[k+1]+=a[k]/10,a[k]%=10;
    		len=max(k-1,1);
    		while(!a[len]&&len>1)--len;
    	}
    	int& operator [](int x){return a[x];}
    	const int& operator [](int x)const{return a[x];}
    	inline void print() {
    		for(register int i=len;i;--i)printf("%d",a[i]);
    		printf("
    ");
    	}
    };
    inline Int operator +(const Int& a,const Int& b) { 
    	Int ans;int n=max(a.len,b.len);
    	for(register int i=1;i<=n;++i) ans[i]+=a[i]+b[i];
    	ans.update(n);
    	return ans;
    }
    inline Int operator -(const Int& a,const Int& b) {
    	Int ans;int n=max(a.len,b.len);
    	for(register int i=1;i<=n;++i) {
    		ans[i]+=a[i]-b[i];
    		if(ans[i]<0) ans[i]+=10,--ans[i+1];
    	}
    	ans.update(n);
    	return ans;
    }
    inline Int operator *(const Int& a,const Int& b) {
    	Int ans;
    	for(register int i=1;i<=a.len;++i)
    		for(register int j=1;j<=b.len;++j)
    			ans[i+j-1]+=a[i]*b[j];
    	ans.update(a.len+b.len-1);
    	return ans;
    }
    Int f[51],c[51][51],qua[51*51];
    inline void init() {
    	c[0][0]=Int(1);
    	for(register int i=1;i<=50;++i) {
    		c[i][0]=Int(1);
    		for(register int j=1;j<=i;++j)
    			c[i][j]=c[i-1][j-1]+c[i-1][j];
    	}
    	qua[0]=Int(1);Int base(2);
    	for(register int i=1;i<=50*50;++i)qua[i]=qua[i-1]*base;
    	f[1]=Int(1);
    	for(register int i=2;i<=50;++i) {
    		f[i]=qua[i*(i-1)/2];
    		for(register int j=1;j<i;++j)f[i]=f[i]-f[j]*c[i-1][j-1]*qua[(i-j)*(i-j-1)/2];
    	}
    }
    int main()
    {
    	init();int x;
    	while(~scanf("%d",&x)&&x)f[x].print();
    	return 0;
    }
    

    c.[√] A decorative fence

    题目传送

    sol:

    考虑一块一块板子的补,都从左边补上。

    假设当前是第i块板子,在已放的板子中从小到大排第j大,记为(f[i,j,0/1])

    注意上述放板子不一定顺序取,i不是代表的前i块板子(当然事实上二者等价),0/1代表低(高)位。

    那么转移应该为:

    [f[i,j,0]=sum_{k=j}^{i-1}f[i-1,k,1]\ f[i,j,1]=sum_{k=1}^{j-1}f[i-1,k,0] ]

    DP完成后,考虑把答案拼凑出来。

    可以发现第一块板子需单独考虑,剩下的板子的选择都会受之前的板子的影响。

    记录一下用过的板子,上一块板子,和当前的状态(高低位)模拟即可。

    code:

    #include<cmath>
    #include<string>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define RG register
    #define IL inline
    #define LL long long
    #define DB double
    using namespace std;
    
    const int N=23;
    
    LL m,f[N][N][2];
    int n,last,state,use[N];
    
    IL void DP() {
    	RG int i,j,k;
    	f[1][1][0]=f[1][1][1]=1;
    	for(i=2;i<=20;++i) 
    		for(j=1;j<=i;++j) {
    			for(k=j;k<i;++k) f[i][j][0]+=f[i-1][k][1];
    			for(k=1;k<j;++k) f[i][j][1]+=f[i-1][k][0];
    		}
    }
    
    int main()
    {
       	RG int i,j,k,T;
    	for(DP(),scanf("%d",&T);T;--T) {
    		scanf("%d%lld",&n,&m);
    		memset(use,0,sizeof(use));
    		for(i=1;i<=n;++i) {
    			if(f[n][i][1]>=m) {
    				last=i,state=1;
    				break;
    			}
    			m-=f[n][i][1];
    			if(f[n][i][0]>=m) {
    				last=i,state=0;
    				break;
    			}
    			m-=f[n][i][0];
    		}
    		use[last]=1;
    		printf("%d ",last);
    		for(i=2;i<=n;++i) {
    			state^=1;
    			for(j=1,k=0;j<=n;++j) {
    				if(use[j]) continue;
    				++k;
    				if(!state&&last>j||state&&j>last)
    					if(f[n-i+1][k][state]>=m) {last=j;break;}
    					else m-=f[n-i+1][k][state];
    			}
    			use[last]=1;
    			printf("%d ",last);
    		}
    		putchar('
    ');
    	}
        return 0;
    }
    

    0x5D 数位统计DP

    d.[√] Apocalypse Someday

    题目传送

    sol:

    容易考虑到设(g[i])表示由i位构成的魔鬼数个数,(f[i,0/1/2])表示i位构成的末尾又连续的0/1/2个6的非魔鬼数个数。

    那么考虑每次从末尾填一个新的数,则转移应该为:

    [f[i,2]=f[i-1,0],f[i,1]=f[i-1,0]\ f[i,0]=9*(f[i-1,0]+f[i-1,1]+f[i-1,2])\ g[i]=f[i-1,2]+g[i-1]*10 ]

    和上面的题目类似,考虑把答案拼凑出来。

    注意,由g的转移方式可知,g中的方案数包括含有前导0的。

    首先利用g确定位数。

    然后从低位到高位依次填数,枚举当前位填的数,

    然后计算出填了它之后能有多少个魔鬼数(还需特别考虑已经是魔鬼数和当前填了6的情况,详见代码)。

    如果小于总数,则当前为应该填一个更大的,否则就填它。

    code:

    #include<cmath>
    #include<string>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define RG register
    #define IL inline
    #define LL long long
    #define DB double
    using namespace std;
    
    IL int gi() {
       RG int x=0,w=0; char ch=getchar();
       while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
       while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
       return w?-x:x;
    }
    
    const int N=17;
    
    int n,m,ans[N];
    LL g[N],f[N][3];
    
    IL void DP() {
    	RG int i;
    	f[0][0]=1,f[1][0]=9,f[1][1]=1;
    	for(i=2;i<=15;++i) {
    		g[i]=f[i-1][2]+10*g[i-1];
    		f[i][2]=f[i-1][1],f[i][1]=f[i-1][0];
    		f[i][0]=9*(f[i-1][2]+f[i-1][1]+f[i-1][0]);
    	}
    }
    
    int main()
    {
       	RG int i,j,k,p,T;
    	for(DP(),T=gi();T;--T) {
    		n=gi();
    		for(m=3;g[m]<n;++m);
    		for(i=m,k=0;i;--i) {
              //k用于记录末尾已经有几个连续的6,k=3代表已经是魔鬼数。
    			for(j=0;j<=9;++j) {
    				RG LL cnt=g[i-1];
    				if(j==6||k==3)
    					for(p=max(3-k-(j==6),0);p<=2;++p) cnt+=f[i-1][p];
    				if(cnt<n) n-=cnt;
    				else {
    					if(k<3) k=(j==6)?k+1:0;
    					printf("%d",j);
    					break;
    				}
    			}
    		}
    		putchar('
    ');
    	}
        return 0;
    }
    
    
    

    f.[√] 同类分布

    题目传送

    sol:

    首先考虑需要记录的东西,记(pos)表示填到的位数,(sum)表示各位之和,(res)表示填出来数的数值。

    如果存在(sum|res)那么就可以贡献1的答案。

    但是发现(res)过于大,无法实现直接记录,那么考虑取模。

    可以发现最好的模数就是(sum)本身了。

    故此时若存在(res==0&&sum==mod)即可贡献1的答案。

    然后发现(mod)(sum)是不断变化的,所以考虑到枚举所有的(modin[1,len*9])(len)为上界数的长度。

    最后用记忆化搜索的方式实现即可。

    注意当前位数的取值范围有可能被上一位限制了,不一定为([0,9])

    code:

    #include<cmath>
    #include<string>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define RG register
    #define IL inline
    #define LL long long
    #define DB double
    using namespace std;
    
    int mod,len,a[23];
    LL l,r,f[23][203][203];
    
    LL dfs(int pos,int sum,int res,int lim) {
    	if(pos>len) return sum==mod&&!res?1:0;
    	if(!lim&&f[pos][sum][res]!=-1) return f[pos][sum][res];
    	RG LL ans=0;
    	RG int i,upl=lim?a[len-pos+1]:9;
    	for(i=0;i<=upl;++i) 
    		ans+=dfs(pos+1,sum+i,(10*res+i)%mod,lim&&i==upl);
    	return lim?ans:f[pos][sum][res]=ans;
    }
    
    IL LL getans(LL x) {
    	RG LL ans=0;
    	for(len=0;x;x/=10) a[++len]=x%10;
    	for(mod=1;mod<=len*9;++mod) {
    		memset(f,-1,sizeof(f));
    		ans+=dfs(1,0,0,1);
    	}
    	return ans;
    }
    
    int main()
    {
       	scanf("%lld%lld",&l,&r);
    	printf("%lld
    ",getans(r)-getans(l-1));
        return 0;
    }
    
  • 相关阅读:
    Drawing points
    Drawing text
    Drag & drop a button widget
    Simple drag and drop
    QtGui.QComboBox
    QtGui.QSplitter
    QtGui.QLineEdit
    Java-java中的有符号,无符号操作以及DataInputStream
    Android Studio-设置override及getter/setter方法
    Android Studio-设置switch/case代码块自动补齐
  • 原文地址:https://www.cnblogs.com/Bhllx/p/11015077.html
Copyright © 2011-2022 走看看