ABC224

D

BFS好题,看似简单其实很考验搜索理解

9个位置,初始其中8个位置上放着1-8。有一些边连接这9个位置。每次可以和当前空尾相邻的一个位置上的东西,移动到空位上,问把1-8的东西移动到1-8的位置上的最小操作次数

开始写了dfs,但这里的问题是,可能在两个位置上左右横跳,导致无穷递归。即使加上记忆化,也可能先进入一个很复杂的分支,最后很晚才进入最优解的分支,复杂度爆炸。

所以对于这种可能某些分支可以延伸很长,甚至到无穷的搜索问题,求最小步数,就应该bfs,不能dfs。只有必须遍历所有情况的问题才可以dfs,因为无论怎么搜都遍历所有情况,先进入还是后进入复杂分支无所谓。

void solve(void){
	int m;
	cin>>m;
	
	vvi g(10);
	rep(i,1,m){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	string s;
	rep(i,1,10){
		s+='0';
	}
	rep(i,1,8){
		int x;
		cin>>x;
		s[x]='0'+i;
	}
	map<string,int>mp;
	
	queue<string>q;
	q.push(s);
	mp[s]=0;
	
	string tar="0123456780";
	while(q.size()){
		string u=q.front();
		q.pop();
		
		if(u==tar){
			break;	
		}
		rep(i,1,9){
			if(u[i]=='0'){
				for(int j:g[i]){
					string t=u;
					swap(u[i],u[j]);
					
					if(!mp.count(u)){
						mp[u]=1e18;
					}
					if(mp[t]+1<mp[u]){
						mp[u]=mp[t]+1;
						q.push(u);
					}
					swap(u[i],u[j]);
				}
			}
		}
	}
	if(mp.count(tar)){
		cout<<mp[tar];
	}
	else{
		cout<<-1;
	}
}

E

最长路dp,延迟更新

每个点有权值,每次可以从当前点移动到同行或同列的,严格大于当前点的另一个点,问从每个点出发的最长路?

按权值排序,每次从同行同列转移过来,然后在更新所在行列的最值即可。注意球的是以每个点为起点的最长路,那么需要从大到小考虑权值。

dp部分很简单,这题比较麻烦的是权值可能相同,而转移要求必须严格大于,那么对于权值相同的元素,他们之间不能转移。

这里最好的处理方法就是延迟更新,查表转移照常,但是更新先存起来,直到下一个元素和当前元素不相等时再更新。

void solve(void){
	int n,m,q;
	cin>>n>>m>>q;
	
	vvi a;
	rep(i,1,q){
		int x,y,v;
		cin>>x>>y>>v;
		a.push_back({v,x,y,i});
	}	
	sort(a.begin(),a.end());
	
	vi mxx(n+1,-1),mxy(m+1,-1);
	
	vi ans(q+1);
	vvi b;
	rep1(i,q-1,0){
		int v=a[i][0],x=a[i][1],y=a[i][2],id=a[i][3];
		int cur=max(mxx[x],mxy[y])+1;
		
		ans[id]=cur;
		if(i==0||a[i-1][0]!=a[i][0]){
			mxx[x]=max(cur,mxx[x]);
			mxy[y]=max(cur,mxy[y]);
			for(auto &t:b){
				int x=t[0],y=t[1],cur=t[2];
				mxx[x]=max(cur,mxx[x]);
				mxy[y]=max(cur,mxy[y]);
			}
			b.clear();
		}
		else{
			b.push_back({x,y,cur});
		}
	}
	
	rep(i,1,q){
		cout<<ans[i]<<'\n';
	}
}

F

计数dp/贡献法

给n个数字,他们中间的n-1个位置可以随意插入加号,一个方案的值就是形成的表达式的权值,问所有插入方案的权值和。

一种思路是,我们可以维护以 i i i为结尾的,所有不含+的子串的和 f i f_i fi,然后我们每一步都可以加或不加+号。如果加上+号,就把 f i f_i fi加入答案。设 g i g_i gi时考虑前 i i i个的所有方案的权值和,那么考虑 f i f_i fi中,每一个长度 k k k的表达式,都可以和某一个长度的 g j g_j gj拼起来,也就是 k + j = i k+j=i k+j=i,那么 g i = ∑ j = 1 i − 1 g j + f i g_i=\sum_{j=1}^{i-1} g_j+f_i gi=j=1i1gj+fi

f i f_i fi的维护,显然每次增加一个元素 x x x,会接到前面所有子串后面,让他们长度+1,也就是先乘10,再加上等同于前面字串个数的 x x x,前 i i i个有 i − 1 i-1 i1个空位可以放加号,因此有 2 i − 1 2^{i-1} 2i1个子串。

void solve(void){
	string s;
	cin>>s;
	
	int n=s.size();
	s=' '+s;
	int cnt=1;
	vi f(n+1),g(n+1);
	vi sum(n+1);
	rep(i,1,n){
		int x=s[i]-'0';
		f[i]=(f[i-1]*10%M2+cnt*x%M2)%M2;
		cnt=cnt*2%M2;
		g[i]=(sum[i-1]+f[i])%M2;
		sum[i]=(sum[i-1]+g[i])%M2;
	}
	
	cout<<g[n];
}

另一种思路是,考虑每个元素的贡献。这种有指数级别个方案,但是每种方案都是由多项式个基本元素组成的,我们可以考虑每个元素再所有方案里出现多少次,也就是考虑每个方案的贡献,就可以把复杂度从指数降低到多项式级别。

具体来说每个元素 x x x,最后对答案的贡献要考虑两点:他在第几位上,也就是要乘上 1 0 i 10^i 10i;它出现多少次。

注意到它最后是第几位,取决于右侧第一个加号的位置,那么左侧,和右侧第一个加号右侧的空位可以随便插入+号,枚举右侧第一个加号的位置,即可写出一个和式子表示 x x x的贡献。

x x x往右移动一位,观察式子变化,发现可以 O ( 1 ) O(1) O(1)递推,那么可以 O ( n ) O(n) O(n)复杂度算出每个元素的贡献。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值