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\n",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("\n");
	}
};
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('\n');
	}
    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('\n');
	}
    return 0;
}


f.[√] 同类分布

题目传送

sol:

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

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

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

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

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

然后发现\(mod\)\(sum\)是不断变化的,所以考虑到枚举所有的\(mod\in[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\n",getans(r)-getans(l-1));
    return 0;
}
posted @ 2019-06-13 11:17  薄荷凉了夏  阅读(588)  评论(0编辑  收藏  举报