@Asuna
2017-05-22T20:36:53.000000Z
字数 5633
阅读 730
题解
题意:就是现在对于每个正整数可以将其每位视为一个数形成一个串, 那么这一组数就存在一个最长上升子序列, 对于每组给出的L, R 求出在区间[L, R]中有多少个数在视作这样的一组数后最长上升子序列的长度是K。
题解:用dp[bit][num][LIS]表示当前填到了第bit位, 填了num, 最长上升子序列的状况为LIS的情况有多少种。先预处理出LIS的转移trans[LIS][0~9]=?
注意这里LIS状态压缩的含义:
* 这个数的二进制第i个1的位置表示在当前的序列中, 长度为i的上升子序列结尾的数最小是多少
* 例如0110110010就表示当前LIS以8结尾, 当前长度为1, 2, 3, 4, 5的上升子序列最小的结尾分别是1, 2, 3, 4, 8
* 而这个二进制中的1的总个数就是LIS的长度
* 于是可以预处理LIS的转移
参考代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,k,t,a[20],pos,cas;
long long dp[20][1<<10][11],p,q;
long long dfs(int pos,int x,int y,bool limit,bool lead)
{
if (pos==-1) return y==0;
if (y<0) return 0;
if (limit && lead && dp[pos][x][y]!=-1)
return dp[pos][x][y];
int top=limit?9:a[pos];
int i,j;
long long ans=0;
for (i=0; i<=top; i++)
{
for ( j=i; j<=9; j++)
if(x>>j&1) break;
if (!i && !lead) ans+=dfs(pos-1,x,y,limit||i<a[pos],lead||i);
else if (j==10) ans+=dfs(pos-1,x+(1<<i),y-1,limit||i<a[pos],lead||i);
else ans+=dfs(pos-1,x-(1<<j)^(1<<i),y,limit||i<a[pos],lead||i);
}
return limit && lead?dp[pos][x][y]=ans:ans;
}
long long solve(long long x)
{
pos=0;
while(x)
{
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,0,k,0,0);
}
int main()
{
memset(dp,-1,sizeof(dp));
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%d",&p,&q,&k);
printf("Case #%d: %lld\n",++cas,solve(q)-solve(p-1));
}
return 0;
}
题意: 给出一个由(、)、[、]组成的字符串,添加最少的括号使得所有的括号匹配,输出最后得到的字符串。
题解:区间dp,dp[i][j]表示 区间 i 到j之间的匹配数,区间两端的 字符是否可以刚好匹配,若可以匹配 状态转移就多了一个 dp[i][j] = max(dp[i][k]+dp[k+1][j],dp[i+1][j-1]+1),若不能匹配就是dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]);若是两端可以匹配的,而且两端匹配了导致的dp值最大那么就标记一下,a[i][j]=-1,否则 就a[i][j]=k,这样把所有区间都dp一遍,回头再用DFS寻找,若是两端匹配导致值最大的 那么就直接输出这个字符标记一下,继续往更小的区间去搜索,否则 就分开两个区间搜索 [i,k],[k+1,j]
参考代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstring>
using namespace std;
char str[510];
int dp[510][510],a[510][510],pr[510];
void dfs(int i ,int j)
{
if (i>=j) return ;
if (a[i][j]==-1)
dfs(i+1,j);
if (a[i][j]>0)
{
pr[i]=1;
pr[a[i][j]]=1;
dfs(i+1,a[i][j]-1);
dfs(a[i][j],j);
}
}
int main()
{
int len;
while(gets(str+1))
{
len=strlen(str+1);
memset(dp,0,sizeof(dp));
memset(a,-1,sizeof(a));
memset(pr,0,sizeof(pr));
for (int i=0; i<=len; i++)
{
dp[i][i]=1;
}
for (int i=len-1; i>=1; i--)
{
for (int j=i+1; j<=len; j++)
{
dp[i][j]=dp[i+1][j]+1;
a[i][j]=-1;
if (str[i]=='(' || str[i]=='[')
{
for (int k=i+1; k<=j; k++)
{
if (str[k]==')' && str[i]=='(')
{
if (dp[i][j]>dp[i+1][k-1]+dp[k][j]-1)
{
a[i][j]=k;
dp[i][j]=dp[i+1][k-1]+dp[k][j]-1;
}
}
if (str[k]==']' && str[i]=='[')
{
if (dp[i][j]>dp[i+1][k-1]+dp[k][j]-1)
{
a[i][j]=k;
dp[i][j]=dp[i+1][k-1]+dp[k][j]-1;
}
}
}
}
}
}
dfs(1,len);
for (int i=1; i<=len; i++)
{
if (pr[i]==1) printf("%c",str[i]);
else
{
if (str[i]=='(' || str[i]==')')
printf("()");
else if (str[i]=='[' || str[i]==']')
printf("[]");
}
}
printf("\n");
}
return 0;
}
题意:规定4,7为lucky数。给一个数m,求1~m中的七个数字的排列中,满足下列条件的排列有多少个:第七个数中含有的lucky数的数量大于前六个含有的lucky数的总和。
题解:先 数位dp计算出包含n个7或4的数个有多少个,然后dfs一边找出来就行了。dp[cur][S][K]表示处理到当前为止,有S个7或者4,一共有K个7或者4的有多少个。
参考代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dig[20],len,m;
long long dp[20][20][20],sum[20];
long long dfs(int now,int e,int S,int K)
{
if (now<0) return (S==K);
if (!e && dp[now][S][K]!=-1) return dp[now][S][K];
int top=(e?dig[now]:9); //上界
long long ans=0;
for (int i=0; i<=top; i++)
{
ans+=dfs(now-1,e && i==top,S+(i==4 || i==7),K)%1000000007;
}
if (!e) dp[now][S][K]=ans;
return ans;
}
long long solve(int num,int pos)
{
if (pos<=0) return 1;
long long ans=0;
for (int i=0; i<=num; i++)
{
if (sum[i]<=0) continue;
sum[i]--;
ans=(ans+(1+sum[i])*solve(num-i,pos-1))%1000000007;
sum[i]++;
}
return ans;
}
int main()
{
cin>>m;
len=0;
memset(dp,-1,sizeof(dp));
while(m)
{
dig[len++]=m%10;
m/=10;
}
for (int i=0; i<=len; i++)
sum[i]=dfs(len-1,1,0,i);
sum[0]--; //去掉0
long long ans=0;
for (int i=1; i<=len; i++)
ans=(ans+sum[i]*solve(i-1,6))%1000000007;
cout<<ans<<endl;
return 0;
}
题意:输入n和m表示一个n*m的矩形,用1*2的方块进行覆盖,不能重叠,不能越出矩形边界,问完全覆盖完整个矩形有多少种不同的方案
题解:
最上面的为第1行,最下面为第n行,从上到下按行DP,其中一行的状态我们用一个二进制表示,0表示没有被覆盖,1表示被覆盖了,最后得到一个01串,这个串变回十进制就是一个状态。
定义状态dp[i][s],表示前i-1行已经放满,第i行的状态为s的方案数
状态转移方程为 dp[i][s]=sum{ dp[i-1][ss] } ,其中状态s与状态ss兼容
这个状态转移方程的内涵在于理解s和ss何为兼容
首先我们约定一个放置方法,就是竖着放的时候,我们暂且将其称为“上凸型摆放”
因为竖放必然占据第i-1行和第i行,我们约定这个方块是属于第i行的,也就是说它凸上去了
那么要在第i行的第j列竖放一个方块的话,第i-1行第j列必须没有方块
也就是说,第i行的放置是受到第i-1行的限制的,反过来说在第i行竖放了方块,也会影响第i-1行的状态
所以这样就可以讲解一下状态转移方程了,前i-2行已经放满了,第i-1行的状态为ss(dp[i-1][ss])
此时在第i行开始放一些方块,放的方法不定,可能横放可能竖放,但是按这个方案放完后
第i-1行刚好被填满,且第i行的状态变为了s,所以不难想到第i-1行的状态ss到第i行的状态s这个转移是唯一的
所以有 dp[i][s]=sum{ dp[i-1][ss] }
最后我们详细讨论一下s和ss在什么情况下是兼容的
那么目标状态是什么,就是dp[n][maxs],maxs表示全部是1的串,即第n-1行以上全部覆盖满,第n行的状态为maxs,即没有空着的格子,也全部覆盖满了,即整个矩形全部被覆盖满了的状态。
最后是第1行的初始化问题,因为约定了“上凸型摆放”,所以第1行是不能竖放方格的,只能横放方格,
每横放一个必定占据两个格子,所以在判断一个状态(那个01串)的时候,连着的1的个数必定为偶数,如果出现了单独的1,说明不合法。
参考代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
int n,m;
long long dp[20][100010];
bool init(int s)
{
int j=0;
while(j<m)
{
if (s & (1<<j))
{
if (j==m-1)
return false;
if (s & (1<<(j+1)))
j+=2;
else
return false;
}
else
j++;
}
return true;
}
bool check(int ns,int ps)
{
int j=0;
while (j<m)
{
if (ns & (1<<j))
{
if (ps & (1<<j))
{
if (j==m-1 || !(ns & 1<<(j+1)) || !(ps & 1<<(j+1)))
return false;
else
j+=2;
}
else
j++;
}
else
{
if (ps & (1<<j))
j++;
else
return false;
}
}
return true;
}
int main()
{
while (scanf("%d%d",&n,&m))
{
if (n==0 && m==0) break;
if (n&1 && m&1)
{
printf("0\n");
continue;
}
if (n<m) swap(n,m);
int tot=(1<<m)-1;
memset(dp, 0, sizeof(dp));
for (int s=0; s<=tot; s++)
if (init(s)) dp[1][s]=1;
for (int i=2; i<=n; i++)
{
for (int j=0; j<=tot; j++)
{
for (int k=0; k<=tot; k++)
{
if (check(j,k))
dp[i][j]+=dp[i-1][k];
}
}
}
printf("%lld\n",dp[n][tot]);
}
return 0;
}