ABC235 离线 数位dp求数字和

D

bfs

每次可以给当前数字乘a,或者把最后一位移动到第一位(前提是最后一位不为0).问能不能把1变成n?能的话求最少操作次数

对于一个分支不确定能不能走到头,也不确定深度的问题,还是求最小代价的话,显然不能dfs,应该bfs。

需要注意的是记忆化,用数组就可以,大小开到n∗10n*10n10即可,然后还需要判断乘a后会不会越界

void solve(){
	int a,n;
	cin>>a>>n;
	
	queue<int>q;
	vi d(n*11,1e18);
	d[1]=0;
	q.push(1);
	while(q.size()){
		int x=q.front();
		q.pop();
//		cout<<x<<'\n';
		if(x==n)break;
		if(x*a<n*10){
			if(d[x*a]>d[x]+1){
				d[x*a]=d[x]+1;
				q.push(x*a);
			}
		}
		string s=to_string(x);
		if(s.back()=='0')continue;
		char c=s.back();
		s.pop_back();
		s=c+s;
		int y=stoi(s);
		if(y>=10*n)continue;
		if(d[y]>d[x]+1){
			d[y]=d[x]+1;
			q.push(y);
		}
	}
	if(d[n]<1e18){
		cout<<d[n];
	}
	else{
		cout<<-1;
	}
}

E

离线 最小生成树

给一个图,给一堆额外的边,问每条边如果加入这张图,会不会在最小生成树中

把询问的边也加入边集里,然后跑最小生成树,访问到询问的边时,只记录是否要加入最小生成树,并不真的加入。

int f[N];
int find(int x){
	if(f[x]==x)return x;
	return f[x]=find(f[x]);
}
void solve(){
	int n,m,q;
	cin>>n>>m>>q;
	
	vvi e;
	rep(i,1,m){
		int u,v,w;
		cin>>u>>v>>w;
		e.push_back({w,u,v,0});
	}
	rep(i,1,q){
		int u,v,w;
		cin>>u>>v>>w;
		e.push_back({w,u,v,i});
	}
	rep(i,1,n){
		f[i]=i;
	}
	
	sort(e.begin(),e.end());
	vi ans(q+1);
	for(auto &t:e){
		int w=t[0],u=t[1],v=t[2],id=t[3];
		u=find(u),v=find(v);
		if(id){
			if(u!=v){
				ans[id]=1;
			}	
		}
		else{
			f[u]=v;
		}
	}
	rep(i,1,q){
		if(ans[i]){
			cout<<"Yes\n";
		}
		else{
			cout<<"No\n";
		}
	}
}

F

数位dp变形,求合法数字和

求包含所有给定数位的,不超过n的所有数字之和。首先如果求数字个数很简单,参数里增加一个mask记录当前已有的数位即可

求和的话,我们在枚举当前位填什么数字时,考虑这一位的数字的贡献,也就是考虑它出现在从低往高的第几位,以及出现多少次。

第几位可以通过当前dp到第几位算出来,预处理一下10的幂即可;出现多少次就是看往后dp的方案数,直接利用求合法数字个数的dp的结果即可。

所以我们让dp返回两个值,一个是后面的合法数字个数,一个是后面的合法数字之和。每一层计算合法数字之和时,除了计算当前放的数字的贡献,还要累加后面的和

复杂度不要看数字很大就害怕,观察dp数组的状态数,以及每一层dfs里面的复杂度,其实只有O(nm2m)O(nm2^m)O(nm2m)

int f[N][1<<10][2][2],g[N][1<<10][2][2];
void solve(){
	string n;
	int m;
	cin>>n>>m;
	vi c(m+1);
	rep(i,1,m){
		cin>>c[i];
	}	
	int len=n.size();
	vi pw(len+1);
	pw[0]=1;
	rep(i,1,len){
		pw[i]=pw[i-1]*10%M2;
	}
	memset(f,-1,sizeof f);
	memset(g,-1,sizeof g);
	
	auto &&dfs=[&](auto &&dfs,int i,int mask,int lim,int lead)->pii{
		if(i==len){
			rep(j,1,m){
				if((mask>>c[j]&1)==0){
					return {0,0};
				}
			}
			return {1,0};
		}	
		if(f[i][mask][lim][lead]!=-1){
			return {f[i][mask][lim][lead],g[i][mask][lim][lead]};
		}
		int up;
		if(lim)up=n[i]-'0';
		else up=9;
		
		int cnt=0,sum=0;
		rep(d,0,up){
			pii res;
			if(lead){
				if(d==0){
					res=dfs(dfs,i+1,mask,lim&&d==up,1);
				}
				else{
					res=dfs(dfs,i+1,mask|(1<<d),lim&&d==up,0);
				}
			}
			else{
				res=dfs(dfs,i+1,mask|(1<<d),lim&&d==up,0);
			}
			cnt+=res.fi;
			sum+=res.se+d*res.fi%M2*pw[len-i-1]%M2;
			
			cnt%=M2;
			sum%=M2;
		}
		return {f[i][mask][lim][lead]=cnt,g[i][mask][lim][lead]=sum};
	};
	pii res=dfs(dfs,0,0,1,1);
	cout<<res.se;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值