ABC251 环dp dfs bfs

D

思维 k进制

从一个不超过300个元素的集合,选最多三个数加起来,能拼出 [ 1 , 1 e 6 ] [1,1e6] [1,1e6]中的所有数字,求这个集合?

集合里可用的不同数字很少,但是要拼出很大范围的数字,显然考虑k进制。

注意到需要表示 1 e 6 1e6 1e6的数字,并且只能用三个数字,也就是在 k k k进制下,只用三位就能表示 1 e 6 1e6 1e6,那么显然可以 100 100 100进制。然而问题是100进制每一位不只是0或1,每一位元素,可能要重复多次才能拼出这一位的数字,比如十进制下,9确实可以只用 1 0 0 = 1 10^0=1 100=1拼出来,但是需要9个1,而我们这里只能用三个数字。也就是每一位只能用一个数字。

再考虑到集合元素可以有300个,并不是只允许 [ 1 , 100 , 10000 ] 三个元素 [1,100,10000]三个元素 [1,100,10000]三个元素,可以保存 100 , 200 , . . . 100,200,... 100,200,...所有元素,这样100进制下每一位的每个数字,都可以用一个元素表示。正好用完300个的限制

void solve(){
	cout<<300<<'\n';
	
	rep(i,1,100){
		cout<<i<<' ';
	}
	rep(i,1,100){
		cout<<i*100<<' ';
	}
	rep(i,1,100){
		
		cout<<i*10000<<' ';
	}
}

E

环dp

一个环,每次可以付出一个代价选中条边,会把边两端的点选中,问选中所有点至少一次的最小代价

如果这只是个序列,显然状态机dp, d p ( i , 0 / 1 ) dp(i,0/1) dp(i,0/1)表示前i个点都已经选中,且 ( i , i + 1 ) (i,i+1) (i,i+1)这条边是否选中的最小代价

但这是个环,那么可以枚举点 ( n , 1 ) (n,1) (n,1)这条边是否被选中,对这两种情况,环的性质被破坏了,分别跑链上的dp,去最值就是答案

void solve(){
	int n;
	cin>>n;
	vvi dp(n+1,vi(2,1e18));
	vi a(n+1);
	rep(i,1,n){
		cin>>a[i];
	}
	
	dp[1][1]=a[1];
	rep(i,2,n){
		dp[i][0]=dp[i-1][1];
		dp[i][1]=min(dp[i-1][0],dp[i-1][1])+a[i];
	}
	int ans=min(dp[n][1],dp[n][0]);
//	cout<<ans<<' ';
	
	dp[1][1]=1e18;
	dp[1][0]=0;
	rep(i,2,n){
		dp[i][0]=dp[i-1][1];
		dp[i][1]=min(dp[i-1][0],dp[i-1][1])+a[i];
	}
	
	ans=min(ans,dp[n][1]);
	
	cout<<ans;
}

F

bfs dfs性质

给一张图,构造一个以1为根的生成树,要求所有非树边(原图中不在生成树中的边)都是/不是连接一个点和它在树里的祖先的。对这两种要求分别构造两棵树

其实就是dfs和bfs形成的两棵树。dfs会一直走一个分支,那么如果遇到了已访问过的点,这个以访问过的点一定是这个分支路径上的祖先,也就是返祖边。

bfs的非树边不会存在返祖边,假设存在,这相当于存在一个点,和两个深度不同的点同时有边,但是选择了深度较深的点作为树上的父亲,这在bfs里是不对的,那个深度较浅的点在跑bfs时会先遇到当前点,那么当前点在树里的父亲会是这个深度较浅的点。

实现时,跑dfs,bfs,如果遇到一个点时没访问过的,这条边就要选中,可以用map<pair>记录一下,最后检查所有边,看有哪些边在map里

void solve() {
	int n,m;
	cin>>n>>m;
	vvi g(n+1);
	rep(i,1,m){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	vvi ans1,ans2;
	
	vi vis0(n+1);
	auto &&dfs=[&](auto &&dfs,int u,int f)->void{
		vis0[u]=1;
		for(int v:g[u]){
			if(v==f||vis0[v])continue;
			ans1.push_back({u,v});
			dfs(dfs,v,u);
		}
	};
	dfs(dfs,1,-1);
	
	queue<int>q;
	q.push(1);
	vi vis(n+1);
	vis[1]=1;
	while(q.size()){
		int u=q.front();
		q.pop();
		
		for(int v:g[u]){
			if(!vis[v]){
				vis[v]=1;
				ans2.push_back({u,v});
				q.push(v);
			}
		}
	}
//	cout<<"!\n";
	for(auto &t:ans1){
		cout<<t[0]<<' '<<t[1]<<'\n';
	}
//	cout<<"!\n";
	for(auto &t:ans2){
		cout<<t[0]<<' '<<t[1]<<'\n';
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值