写到一道数位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; }