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=1i−1gj+fi
而 f i f_i fi的维护,显然每次增加一个元素 x x x,会接到前面所有子串后面,让他们长度+1,也就是先乘10,再加上等同于前面字串个数的 x x x,前 i i i个有 i − 1 i-1 i−1个空位可以放加号,因此有 2 i − 1 2^{i-1} 2i−1个子串。
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)复杂度算出每个元素的贡献。