写到一道数位DP的题目,涉及LIS的nlogn解法,拿道LIS模板题复习一下。
-
BUY LOW, BUY LOWER (记录序列数,DP*2)
VJ链接:https://vjudge.net/problem/POJ-1952
//打算把更新放在上面了
对LIS问题有了新认识。。。原来自己以前YY的贪心思想是错的??nlogn求LIS本质是DP..
dp[len]表示长度为len的上升子序列末端最小值。
比如:
这个题要求最长下降子序列,还有满足这个长度的子序列有多少(去重)。
需要两遍DP。
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<stack>
#include<ctime>
#include<cstdio>
#include<vector>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e5+10;
#define pi acos(-1.0)
#define INF 0x3f3f3f3f
#define mod 1000000009
#define endll printf("\n")
#define s1(a) scanf("%d",&a)
#define lowbit(x) ((x)&(-x))
#define s2(a,b) scanf("%d %d",&a,&b)
#define mem(a,b) memset(a,b,sizeof(a))
#define s3(a,b,c) scanf("%d %d %d",&a,&b,&c)
int n,maxlen;
int a[5555];
int dp[5555];//以i结尾的最长下降长度
int cnt[5555];//到i位置的下降序列个数
int main()
{
while(~s1(n))
{
maxlen=0;
for(int i=1;i<=n;i++) s1(a[i]);
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
if(a[j]>a[i]) dp[i]=max(dp[i],dp[j]+1);
maxlen=max(maxlen,dp[i]);
}
for(int i=1;i<=n;i++)
{
for (int j=1;j<i;j++)
if (a[i] == a[j]) cnt[j] = 0;//去重,将前面的重复的序列的贡献清空
if (dp[i] == 1) cnt[i] = 1;
else
{
for (int j=1;j<i;++j)
if (dp[j]+1 == dp[i] && a[i] < a[j])//如果i可由j转移得到
cnt[i] += cnt[j];
}
}
int sum=0;
for (int i=1;i<=n;++i)
if (dp[i] == maxlen) sum += cnt[i];
printf("%d %d\n",maxlen,sum);
}
return 0;
}
-
Bridging signals
VJ链接:https://vjudge.net/problem/HDU-1950
求一个最长递增子序列,通过贪心维护一个d数组,d[len]代表长度为len的子序列末端最小元素
从前向后扫描原数组a并更新d中的各个d[len]值,注释都在代码里啦。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e5+10;
#define pi acos(-1.0)
#define INF 0x3f3f3f3f
#define mod 1000000
#define endll printf("\n")
#define s1(a) scanf("%d",&a)
#define s2(a,b) scanf("%d %d",&a,&b)
#define mem(a,b) memset(a,b,sizeof(a))
#define s3(a,b,c) scanf("%d %d %d",&a,&b,&c)
int n;
int a[MAXN];//原数组
int d[MAXN];//长度为len的lis末端最小元素//d数组为递增的
int main()
{
int t;s1(t);
while(t--)
{
s1(n);
for(int i=0;i<n;i++)
s1(a[i]);
d[1]=a[0];
int len=1;
for(int i=1;i<n;i++)
{
if(a[i]>d[len])
d[++len]=a[i];
else
{
int pos=lower_bound(d,d+len+1,a[i])-d;
//这里要找第一个满足d[len]>=a[i]的位置(下界)
//例如数组2,4,3,4,如果寻找上界
//扫描到3的时候,d[1]=2,d[2]=4
//如果寻找上界,找到的是d[1],将d[1]更新为3,显然是不对的
d[pos]=a[i];
}
}
printf("%d\n",len);
}
return 0;
} -
XHXJ's LIS (数位DP,LIS)
VJ:https://vjudge.net/problem/HDU-4352
看名字很明显啦。。。
条件:一个数满足(看成字符串后)最长递增子序列长度为k
题意:求一段区间内满足条件的数字个数
对于每一个数,要求最长递增子序列,LIS(nlogn)嘛,对于一个区间,数位DP嘛。
模板数位维护数组为:dp[pos][state],
对于这个题,pos代表到达哪一位,state自然就是此时最长公共子序列情况。
但是对这个题要存一个数组比较困难,所以状压把这个子序列表达为01序列,那么1<<10就可以表达全部状态(或者哈希)
2 3 4就表示为1110(不会出现0123的,0,1,冲突不用管了)。
对于不同的k,再开一维区分不同的k方便记忆话搜索,最后数组就是dp[pos][state][k]。
再复习一下LIS中的贪心思想,,,dfs产生新状态时,维护出一个1.最长2.内部元素字典序最小的子序列。
如果为2545,扫描到4时,发现prestate中为25,此时将5踢出去,维护新状态为24;
如果为234,到4时, prestate为23,新状态234,长度+1;
如果为256345,到3,prestate为256--->state236,
明显此时状态和实际不符,但是长度依然是我们想要的,因为这一步实际在做这个事情
相当于开了一串新序列)
把后面情况一块画出来了丑就丑啦.......)
扫描到后面5的时候就应该知道为什么在这么贪心了。
另外就是找第一个>=x的值进行替换。上一个题强调过了。
//咦写这个题是想练数位DP来着,,好像只有LIS部分想写一写。
#include<cmath> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; typedef long long ll; const int MAXN=1e5+10; #define pi acos(-1.0) #define INF 0x3f3f3f3f #define mod 1000000 #define endll printf("\n") #define s1(a) scanf("%d",&a) #define s2(a,b) scanf("%d %d",&a,&b) #define mem(a,b) memset(a,b,sizeof(a)) #define s3(a,b,c) scanf("%d %d %d",&a,&b,&c)int k; ll l,r; int a[30]; ll dp[30][1<<11][11]; //dp[pos][prestate][k] //到达pos处,前一个状态为prestate,此位置数字取0~9满足限定条件的数字数目和 int getnum(int s) { int num=0; while(s) { num+=(s&1);s>>=1; } return num; } int getnew(int s,int x) { for(int i=x;i<10;i++) { if(s&(1<<i)) { s^=(1<<i); break; } } return s|(1<<x); } ll dfs(int pos,int prest,int limit,int zero) { if(pos==-1) return (getnum(prest)==k); if(!limit&&dp[pos][prest][k]!=-1) return dp[pos][prest][k]; int up=limit?a[pos]:9; ll ans=0; for(int i=0;i<=up;i++) ans+=dfs(pos-1,zero&&i==0?0:getnew(prest,i),limit&&i==up,zero&&i==0); if(!limit) dp[pos][prest][k]=ans; return ans; } ll solve(ll x) { int pos=0; while(x) { a[pos++]=x%10; x/=10; } return dfs(pos-1,0,1,1); } int main() { int t;s1(t); int tt=1; mem(dp,-1); while(t--) { scanf("%lld %lld %d",&l,&r,&k); if(l==r) printf("Case #%d: 0\n",tt++); else printf("Case #%d: %lld\n",tt++,solve(r)-solve(l-1)); } return 0; }

京公网安备 11010502036488号