[Lucas定理 数位DP 容斥原理] 2015 计蒜之道 复赛 360的产品试用体验

52 篇文章 0 订阅
35 篇文章 0 订阅

直接上官网题解吧:http://blog.jisuanke.com/?p=146


题意即求

q1

其中

q2

q3

因为 47 是质数,如果把ai, li, ri, xi 写作 47 进制数

q4.1

q4.2

q4.3

q4.4

因为 47 是质数,根据Lucas定理 ,题目转换为求

q5

的值,同时满足:

q6.1 字典序不小于 q6.2 不大于 q6.3
x1 + x2 + x3 n.

形象地说,题目相当于要填一个 3 行 m 列的矩阵,每一个格子是一个 [0, 47) 的整数。我们使用动态规划解决这个问题,其中涉及基本的数位型动态规划和HNOI 2007《梦幻岛宝珠》一题中的技巧。

我们用 f(i,j, mask, r) 记录一个状态,表示从高位往低位填,现在要填第 行第 列这个格子,mask 是一个 6 位二进制数,分别表示每一行是否已经大于下界(小于上界),r 表示现在总量还剩余r × 47j。需要注意 r ≤ 3 × 47。转移比较简单,只要枚举这个格子填什么,对应转移即可。


我这里用的是容斥 一个边界的数位DP比两个边界的好打多了

然后么 一定要注意r<=3*47 不能是3*46 WA了好久

Lucas定理出数位DP题 新idea get!!


#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
typedef long long ll;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}

inline void read(ll &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=55;
const int P=47;
const int M=15;

int C[N][N];

void Pre(){
  C[0][0]=1;
  for (int i=1;i<=47;i++){
    C[i][0]=1;
    for (int j=1;j<=i;j++)
      C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
  }
}

ll maxn;
ll L[5],R[5],A[5];

const int n=3;
int m;
int w[M],l[5][M],a[5][M];

int Mac(ll x,int *w,int len=0){
  int tem=0;
  while (x) w[++tem]=x%47,x/=47;
  if (len) 
    while (tem<len) w[++tem]=0;
  reverse(w+1,w+tem+1); return tem;
}

int f[5][M][2][2][2][47*3+5];

void add(int &x,int y){
  x+=y; if (x>=P) x-=P;
}

int Solve(ll lim1,ll lim2,ll lim3){
  cl(f);
  Mac(lim1,l[1],m); Mac(lim2,l[2],m); Mac(lim3,l[3],m);
  f[1][1][1][1][1][w[1]]=1;
  int k[10],si,sj,val,tem; int ret=0;
  for (int j=1;j<=m;j++){
    for (int i=1;i<=3;i++){
      for (int o=0;o<8;o++){
	k[1]=o&1; k[2]=o>>1&1; k[3]=o>>2&1;
	
	for (int r=0;r<=47*3;r++){
	  val=f[i][j][k[1]][k[2]][k[3]][r];
	  if (!val) continue;
	  if (i==3) {
	    si=1,sj=j+1;
	    if (k[i]==1){
	      tem=min(l[i][j],min(r,46));
	      for (int v=0;v<=tem;v++){
		k[i]=v==l[i][j];
		add(f[si][sj][k[1]][k[2]][k[3]][min(47*3,(r-v)*47+w[sj])],val*C[a[i][j]][v]%P);
	      }
	      k[i]=1;
	    }else{
	      tem=min(r,46);
	      for (int v=0;v<=tem;v++)
		add(f[si][sj][k[1]][k[2]][k[3]][min(47*3,(r-v)*47+w[sj])],val*C[a[i][j]][v]%P);
	    }
	  }else{
	    si=i+1,sj=j;
	    if (k[i]==1){
	      tem=min(l[i][j],min(r,46));
	      for (int v=0;v<=tem;v++){
		k[i]=v==l[i][j];
		add(f[si][sj][k[1]][k[2]][k[3]][r-v],val*C[a[i][j]][v]%P);
	      }
	      k[i]=1;
	    }else{
	      tem=min(r,46);
	      for (int v=0;v<=tem;v++)
		add(f[si][sj][k[1]][k[2]][k[3]][r-v],val*C[a[i][j]][v]%P);
	    }
	  }
	}
	
      }
    }
  }
  for (k[1]=0;k[1]<2;k[1]++)
    for (k[2]=0;k[2]<2;k[2]++)
      for (k[3]=0;k[3]<2;k[3]++)
	for (int r=0;r<=47*3;r++)
	  add(ret,f[1][m+1][k[1]][k[2]][k[3]][r]);
  return ret; 
}

int main(){
  ll T; int Ans=0;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(T); Pre();
  while (T--){
    for (int i=1;i<=n;i++) read(A[i]),read(L[i]),read(R[i]); read(maxn);
    maxn=min(maxn,A[1]+A[2]+A[3]);
    m=max(Mac(A[1],a[1]),max(Mac(A[2],a[2]),Mac(A[3],a[3])));
    m++;
    for (int i=1;i<=n;i++) Mac(A[i],a[i],m);
    Mac(maxn,w,m);
    Ans=0;
    Ans+=Solve(R[1],R[2],R[3]); Ans=(Ans+P)%P;
    Ans-=Solve(R[1],R[2],L[3]-1); Ans=(Ans+P)%P;
    Ans-=Solve(R[1],L[2]-1,R[3]); Ans=(Ans+P)%P;
    Ans-=Solve(L[1]-1,R[2],R[3]); Ans=(Ans+P)%P;
    Ans+=Solve(L[1]-1,L[2]-1,R[3]); Ans=(Ans+P)%P;
    Ans+=Solve(R[1],L[2]-1,L[3]-1); Ans=(Ans+P)%P;
    Ans+=Solve(L[1]-1,R[2],L[3]-1); Ans=(Ans+P)%P; 
    Ans-=Solve(L[1]-1,L[2]-1,L[3]-1); Ans=(Ans+P)%P;
    printf("%d\n",Ans);
  }
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值