2025 寒假集训 第四期
2025 寒假集训 第四期
F - windy 数
数位dp板题
数位dp显著特点是按数位dp,也就是说按个位十位百位。通常解决 位与位之间有限制,或与位上的数相关的问题。还有个特点就是输入少
显然的结论为:区间\([l,r]\)的答案为\((区间[1,r]-区间[1,l-1])\)
问题转化为求区间\([1,x]\)的答案
求答案的方法即为按数位去dp计算答案。
……
\(f[i]\)表示在第\(i\)位时的答案,从\(f[i-1]\)转移而来
好像没什么好讲的
讲一下细节:
1.实现使用记忆化搜索会更简易
2.一般从高位开始记忆化搜索,方便处理超出上界的情况
例如:求\([1,21]\)的数,十位不能取大于\(2\)的数,如果十位是\(2\),个位不能取大于\(1\)的数,否则个位取啥都行。
实现时用\(limit\)记录\(0或1\)表示到当前状态之前数位有没有触碰上界
3.看题目对前导\(0\)有没有什么限制,不一定每个题目都有
至于这道题,详见代码注释:
#include<bits/stdc++.h>
#define maxn 20
using namespace std;
long long a[maxn],n,m,len;
long long f[maxn][maxn];
//f[x][y]为第x位,数值为y的答案
int dfs(int x,int y,int st,int limit){ //st代表有无前导0,limit为是否顶到上限
if(!x)return 1; //到底了
if(y!=-2&&f[x][y]!=-1&&limit==0)return f[x][y]; //记忆化过了且没有限制
int maxx=9;long long sum=0;
if(limit==1)maxx=a[x]; //更新数位值上限
for(int i=0;i<=maxx;i++){
if(abs(y-i)<2)continue; //不符合限制
if(st==1&&i==0)sum+=dfs(x-1,-2,1,0); //前导0处理
else sum+=dfs(x-1,i,0,limit&&i==a[x]); //正常处理
}
if(!limit&&!st) f[x][y]=sum; //没有限制记忆化
return sum;
}
int solve(long long x){
memset(f,-1,sizeof(f));len=0;
while(x)a[++len]=x%10,x/=10;
return dfs(len,-2,1,1);
}
int main(){
scanf("%lld%lld",&n,&m);
printf("%d\n",solve(m)-solve(n-1));
return 0;
}
J - Strange Dance
题目大意:有 \(3^n\) 个人排成圆圈,人和位置编号从 \(0\) 到 \(3^n-1\) .有两种操作
1.每个人向后走一个位置
2.第 i 个人走向第 j 个位置当且仅当他们的三进制表示,1 对应 2,2 对应 1。
给出操作序列,求最后每个人位置
思路:有意思的一道题
首先可以很容易想到要拆位考虑,之后我们要考虑三进制拆位下每个操作的含义
先考虑第二个操作,相当于将所有人的位置三进制下的 \(1\) 和 \(2\) 交换,\(0\) 位则不受任何影响 。
这里我们可以想到利用 \(0-2 Trie\) 来维护这个操作,叶子节点表示位置上人的编号
每次操作二相当于将 \(1,2\) 两儿子进行一个交换
那么操作一该如何表示呢?
如果我们正常按Trie从高位向地位存确实想不到什么方法
但我们可以反一下,从低位向高位存储,这样操作一不受影响,操作二则有了解决方案
低位向高位下,当前层所表示的位加 \(1\) 的操作对只相当于当前位对 \(0,1,2\) 三个儿子进行轮换操作
而 \(2\) 儿子会产生进位,进入下一层,需要递归去处理,其他儿子则不需要,时间复杂度 \(O(n)\) .
两个操作同时进行维护,只需对操作一打一个翻转标记即可,具体和维护线段树差不太多
代码:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
#define maxn 1000010
#define L(node) (trie[node].ch[0])
#define M(node) (trie[node].ch[1])
#define R(node) (trie[node].ch[2])
struct kkk{
int ch[3],val,tag;
}trie[maxn];
int n,p[20],tot,rt,ans[maxn];
string s;
void build(int &node,int d,int val){
if(!node)node=++tot;
if(d==n){trie[node].val=val;return;}
build(L(node),d+1,val);
build(M(node),d+1,val+p[d]);
build(R(node),d+1,val+p[d]*2);
}
void pushdown(int node){
if(trie[node].tag){
swap(trie[node].ch[1],trie[node].ch[2]);
for(int i=0;i<=2;i++)trie[trie[node].ch[i]].tag^=1;
trie[node].tag=0;
}
}
void modify(int node){
if(!L(node))return;
pushdown(node);
int t=trie[node].ch[0];
trie[node].ch[0]=trie[node].ch[2];
trie[node].ch[2]=trie[node].ch[1];
trie[node].ch[1]=t;
modify(L(node));
}
void query(int node,int d,int val){
if(d==n){
ans[trie[node].val]=val;
return;
}
pushdown(node);
query(L(node),d+1,val);
query(M(node),d+1,val+p[d]);
query(R(node),d+1,val+p[d]*2);
}
void debug(){
query(rt,0,0);
for(int i=0;i<p[n];i++)cout<<ans[i]<<' ';cout<<endl;
}
void solve(){
cin>>n;
p[0]=1;for(int i=1;i<=n;i++)p[i]=p[i-1]*3;
build(rt,0,0);
cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]=='S'){
trie[rt].tag^=1;
}else{
modify(rt);
}
//debug();
}
query(rt,0,0);
for(int i=0;i<p[n];i++)cout<<ans[i]<<' ';cout<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}
K - Omkar and Landslide
题目大意:现在有一些山发生了山体滑坡,这些山从左到右依次排布,第 \(i\) 座山的高度为 \(h_i\) , 且满足 $ 1 \leq i \leq n - 1 $ , $ h_i < h_{i + 1} $
每一时刻,假如 $ h_i + 2 \leq h_{i + 1} $,那么两座山的高度会发生变化,即 \(h_i+1,h_{i+1}-1\) 。
问山体滑坡进行完后,即没有任何泥土能流动时,所有山的高度是多少。
思路:这题挺直觉的,想到在题目的条件下最终一定会形成一个单调上升的序列(非严格),且上升不超过 \(2\) .
但是这个序列有些位置可能是平的,形状我们不能去确定
考虑这个最终 \(i\) 处为平台,如何形成,一定是存在 $ h_i + 2 = h_{i + 1} $ 这个情况,且左右都动不了即 \(h_{i-1} =h_i\) 且 \(h_{i+2}=h_{i+1}\)
也就是说,两平台会合成一个固定平台,然而平台会不断移动或形成阶梯,不能形成合并的形式
当平台从右一致最左侧时则会形成更高一阶的阶梯
模拟这个过程,可以发现,最终只可能形成一个平台
得出这个结论后就好做了
我们先用所有高度构成可以上升 \(1\) 的阶梯,剩余高度从左往右铺完为止,这样即可得到答案。
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
int n,sum,a[1001000];
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];sum+=a[i];
}
sum-=(a[1]+a[1]+n-1)*n/2;
int mid=sum%n;sum/=n;
for(int i=1;i<=mid;i++)cout<<a[1]+i+sum<<' ';
for(int i=mid+1;i<=n;i++)cout<<a[1]+i+sum-1<<' ';
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}

浙公网安备 33010602011771号