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;
}
posted @ 2025-02-18 16:47  Hastieyua  阅读(26)  评论(0)    收藏  举报