2021.10.29 数位dp

1.数字计数

我们先设数字为ABCD

看A000,如果我们要求出它所有数位之和,我们会怎么求?

鉴于我们其实已经求出了0到9,0到99,0到999。。。上所有数字个数(f[i],且没有考虑前导0)我们何不把这个A000看成0000到1000到2000...A000对于不考虑首位每一个式子的数字的出现个数为 \(A*f[3]\)。加上首位出现也就是小于A每一个数都出现了\(10^3\)次,再加上,我们就把A000处理完了。

这样你以为就把第一位处理完了?不不不,首位A还出现了BCD+1次呢,也就是从A000~ABCD,这个A还出现了BCD+1次,所以再加上这些才行呢。那么你发现,我们成功把首位代表的所有数字个数求出来了,剩下的求解与A完全没有任何关系,只是BCD的求解,于是我们发现我们已经把一个大问题,化成了一个个小问题,也即是,对于一个这样n位的数,把他一位位的分离开来。

当然你还需要处理前导0你会发现前导0一定是\(0001,0002\cdots0012,0013\cdots0101,0102\cdots0999\)这样的数,一共出现了\(10*(i-1)+10*(i-2)+\cdots+10\) (i表示数字位数),让0的统计减去这个值,那么恭喜你这道题做完了。

来自[题解 P2602 【ZJOI2010]数字计数】 - moye到碗里来 的博客 - 洛谷博客 (luogu.com.cn)

练习题:

[P2602 ZJOI2010]数字计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

运用前缀和的想法,把从a到b拆分为从1到b减去从1到a-1的结果

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=15;
int a,b,f[N],ten[N],numa[N],numb[N],cnt[N];

inline void solve(int x,int *num){
	int top=0;
	while(x){
		cnt[++top]=x%10;
		x/=10;
	}
	for(int i=top;i>=1;i--){
		for(int j=0;j<10;j++)num[j]+=f[i-1]*cnt[i];
		for(int j=0;j<cnt[i];j++)num[j]+=ten[i-1];
		int js=0;
		for(int j=i-1;j>=1;j--)js=js*10+cnt[j];
		num[cnt[i]]+=js+1;
		num[0]-=ten[i-1];
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin>>a>>b;
	ten[0]=1;
	for(int i=1;i<=12;i++)
	f[i]=f[i-1]*10+ten[i-1],ten[i]=ten[i-1]*10;
	solve(a-1,numa);solve(b,numb);
	for(int i=0;i<10;i++)cout<<numb[i]-numa[i]<<" ";
	return 0;
}

PS:数位dp是对每一位上的数字分开进行处理。如数字ABCD,先处理010010002000……A000,然后处理BCD,把BCD拆开处理,先处理0B00,再处理0C0,再处理D,完结撒花

[P2657 SCOI2009] windy 数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

// https://www.luogu.com.cn/discuss/246065 
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int N=15;
int a,b,f[N][N],num[N],len;

inline int calc(int x){
	memset(num,0,sizeof(num));
	len=0;
	while(x){
		num[++len]=x%10;
		x/=10;
	}
	int ans=0;
	for(int i=1;i<len;i++)
	for(int j=1;j<10;j++)//这里不能有前导零,因为是每次枚举不同长度的最高位 
	ans+=f[i][j];
	for(int i=1;i<num[len];i++)ans+=f[len][i];
	for(int i=len-1;i>=1;i--){
		for(int j=0;j<num[i];j++)//这里可以有前导零,因为这里是枚举在已经有前面固定的几位后剩下长度的为高位,而不是原数的最高位,可以有前导零 
		if(abs(num[i+1]-j)>=2)ans+=f[i][j];
		if(abs(num[i]-num[i+1])<2)break;
	}
	return ans;
}

int main(){
	ios::sync_with_stdio(false);
	for(int i=0;i<10;i++)f[1][i]=1;
	for(int i=2;i<=10;i++)
	for(int j=0;j<10;j++)
	for(int k=0;k<10;k++)
	if(abs(j-k)>=2)f[i][j]+=f[i-1][k];
	cin>>a>>b;
	cout<<calc(b+1)-calc(a);
	return 0;
}

区间DP

[P2890 USACO07OPEN]Cheapest Palindrome G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

开始把区间dp写成了求lcs+删字母或加字母,于是10分……其余RE,why?!

10分代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;

const int N=2010;
//string s,t;
char s[N],t[N];
int m,n,f[N],id[N][N],fin[N],top,a[N],val[N],minn;
map<int,char>mapi;

inline int find(int x){
	int L=1,R=top,mid;
	while(L<R){
		mid=(L+R)>>1;
		if(fin[mid]<=x)L=mid+1;
		else R=mid;
	}
	return L;
}

int main(){
	ios::sync_with_stdio(false);
	cin>>m>>n;
	//cin>>s;
	//t=s;reverse(t.begin(),t.end());
	scanf("%s",s);
	memcpy(t,s,sizeof(s));
	reverse(t,t+strlen(t));
	//cout<<t<<endl;
	for(int i=1;i<=n;i++){
		int x=s[i-1]-'a'+1;
		++id[x][0];
		id[x][id[x][0]]=i;
		//cout<<id[x][id[x][0]]<<" ";
		mapi[i]=s[i-1];
	}
	//cout<<endl;
	for(int i=1;i<=m;i++)id[i][0]=0;
	for(int i=1;i<=n;i++){
		int x=t[i-1]-'a'+1;
		++id[x][0];
		a[i]=id[x][id[x][0]];
		//cout<<a[i]<<" ";
	}
	//cout<<endl;
	for(int i=1;i<=m;i++)id[i][0]=0;
	cout<<"    Case 1"<<endl;
	fin[1]=a[1];top=1;++id[mapi[a[1]]-'a'+1][0];
	cout<<"    Case 2"<<endl;
	for(int i=2;i<=n;i++)
	if(fin[top]<=a[i])fin[++top]=a[i],++id[mapi[a[i]]-'a'+1][0],cout<<i<<endl;
	else{
		int x=find(a[i]);
		cout<<i<<" "<<x<<" "<<mapi[fin[x]]<<endl;
		--id[mapi[fin[x]]-'a'+1][0];
		fin[x]=a[i];
		++id[mapi[a[i]]-'a'+1][0];
	}
	for(int i=1;i<=top;i++)cout<<fin[i]<<" ";cout<<endl;
	//for(int i=1;i<=top;i++)cout<<mapi[fin[i]]<<" ";cout<<endl;
	for(int i=1,j=1;i<=m;i++){
		char x;
		int u,v;
		cin>>x>>u>>v;
		int xi=x-'a'+1;
		if(mapi[fin[j]]==x)minn+=u*id[xi][0],++j;
		val[xi]=min(u,v);//0 add 1 delete
	}
	//cout<<"minn "<<minn<<endl;
	int ans=0;
	for(int i=1,j=1;i<=n;){
		//cout<<i<<" "<<a[i]<<" "<<j<<" "<<fin[j]<<" "<<ans<<endl;
		if(a[i]==fin[j])++i,++j;
		else ans+=val[mapi[a[i]]-'a'+1],++i;
	}
	//cout<<"ans "<<ans<<endl;
	cout<<min(minn,ans);
	return 0;
}

100分代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;

const int N=2010;
int n,m,f[N][N],val[N];
char s[N];

int main(){
	//ios::sync_with_stdio(false);
	//呵呵了,洛谷上加上这一行直接挂了
	cin>>m>>n;
	scanf("%s",s+1);
	for(int i=1;i<=m;i++){
		char x;
		int u,v;
		cin>>x>>u>>v;
		val[x-'a']=min(u,v);
	}
	memset(f,0x3f3f3f3f,sizeof(f));
	for(int i=1;i<=n;i++){
		f[i][i]=0;
		if(s[i]==s[i+1])f[i][i+1]=0;
	}
	for(int len=0;len<=n;len++)
	for(int i=1;i+len<=n;i++){
		int j=i+len;
		int u=s[i-1]-'a',v=s[j+1]-'a';
		if(u==v)f[i-1][j+1]=min(f[i][j],f[i-1][j+1]);
		f[i-1][j]=min(f[i-1][j],f[i][j]+val[u]);
		f[i][j+1]=min(f[i][j+1],f[i][j]+val[v]);
	}
	cout<<f[1][n];
	return 0;
}

普通DP

[P1310 NOIP2011 普及组] 表达式的值 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:

运算的优先级是:

  1. 先计算括号内的,再计算括号外的。
  2. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算。例如:计算表达式A⊕B × C时,先计算 B × C,其结果再与 A 做⊕运算。

现给定一个未完成的表达式,例如_+(*),请你在横线处填入数字00或者11 ,请问有多少种填法可以使得表达式的值为00。

分析:

思路不会,只会状态转移。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
using namespace std;

#define int long long
const int N=2e5+10;
const int mod=1e4+7;
int n,m,top=1;
string si;
stack<char>s;
struct node{
	int zero,one;
}ans[N];

inline void solve(char ch,node &a,node &b){
	if(ch=='+'){
		a.one=(a.one*b.one%mod+a.zero*b.one%mod+a.one*b.zero%mod)%mod;
		a.zero=a.zero*b.zero%mod;
	}else{
		a.zero=(a.zero*b.zero%mod+a.one*b.zero%mod+a.zero*b.one%mod)%mod;
	    a.one=(a.one*b.one)%mod;
	}
}

signed main(){
	cin>>n;
	cin>>si;
	si+=')';
	//cout<<si<<endl;
	ans[1].one=ans[1].zero=1;
	s.push('(');
	for(int i=0;i<=n;i++){
		//cout<<i<<endl;
		if(si[i]=='(')s.push('(');
		else if(si[i]==')'){
			for(;s.top()!='(';s.pop(),top--)
			solve(s.top(),ans[top-1],ans[top]);//,cout<<s.top()<<" ";cout<<endl;
			s.pop(); 
		}else{
			for(;s.top()<=si[i]&&s.top()!='(';s.pop(),top--)
			solve(s.top(),ans[top-1],ans[top]);//,cout<<s.top()<<" ";cout<<endl;
			s.push(si[i]);
			++top;
			ans[top].zero=ans[top].one=1;
		}
	}
	cout<<ans[1].zero;
	return 0;
}
 posted on 2021-11-03 16:06  eleveni  阅读(49)  评论(0)    收藏  举报