D 枚举

题意:每次操作对数组中数-1,相邻数 ∣ x − y ∣ ≤ 1 |x-y|\leq 1 ∣x−y∣≤1,即 R i ≤ R i + 1 + 1 , R i ≤ R i − 1 + 1 R_i\leq R_{i+1}+1, R_i\leq R_{i-1}+1 Ri≤Ri+1+1,Ri≤Ri−1+1
用小的数去把大的数往小拉
用 R i − 1 R_{i-1} Ri−1一侧的数限制后,由于不知道另一侧的情况,再用 R i + 1 R_{i+1} Ri+1限制后, R i + 1 R_{i+1} Ri+1又会被右边的数影响而改变
所以需要循环两次,从前和从后隔一次,来保证每个数被相邻的不会改变的数限制。
cpp
void solve(){
int n;cin>>n;
vector<int>r(n+2);
forr(i,1,n){
cin>>r[i];
}
r[0]=r[2],r[n+1]=r[n-1];
int ans=0,cnt=1,mn=r[1];
forr(i,1,n){
int now=r[i];
if(r[i]>r[i-1]+1)now=min(now,r[i-1]+1);
// if(r[i]>r[i+1]+1)now=min(now,r[i+1]+1);
ans+=r[i]-now;
r[i]=now;
}
reforr(i,1,n){
int now=r[i];
// if(r[i]>r[i-1]+1)now=min(now,r[i-1]+1);
if(r[i]>r[i+1]+1)now=min(now,r[i+1]+1);
ans+=r[i]-now;
r[i]=now;
}
cout<<ans<<endl;
}
E 递归

题意:从第n行开始向上走,对第1行的每个点判断能否到达
- 如果要走的地方是墙,满足该墙下面的行没有别的墙(直通 i ∼ n i\sim n i∼n行),可以破该墙
只是向上走,可以逐行递推可到达的位置
本行可到达的地方:
- 空地
- 下面直通的墙
"直通"的状态也可以逐行递推,我的做法是 i i i本行更新完后给 i − 1 i-1 i−1行用
cpp
void solve(){
int n,c;cin>>n>>c;
vector<string>s(n+1);
vector<vint>vis(n+1,vector<int>(n+1,0));//能否到达
vector<int>all(n+1,0);//"直通"状态
forr(i,1,n){
cin>>s[i];
s[i]=' '+s[i];
}
vis[n][c]=1;
forr(j,1,n)all[j]=(s[n][j]=='.');//初始化"直通状态" 初始位置破不了任何墙 只有空地满足"直通"
auto notin=[&](int x){
return x>n||x<1;
};
reforr(i,1,n-1){
forr(j,1,n){
//本行可到达的地方 找i+1行能否到达
if(s[i][j]=='.'){//空地
forr(k,-1,1){
if(notin(j+k))continue;
vis[i][j]|=vis[i+1][j+k];
}
}else{
if(!all[j])continue;//破不了墙
//可以破墙
forr(k,-1,1){
if(notin(j+k))continue;
vis[i][j]|=vis[i+1][j+k];
}
}
//递推"直通"状态
if(s[i][j]=='#')all[j]&=vis[i][j];//如果本行是墙 必须同时满足这个墙直通+能到达(到不了这墙就破不了,更别提后面的了)
//如果本行是空地 不影响状态
}
}
// forr(i,1,n){forr(j,1,n)cout<<vis[i][j];cout<<endl;}
forr(j,1,n)cout<<vis[1][j];cout<<endl;
}
F BFS 剪枝

参考@枫落 dalao题解 @StelaYuri dalao题解
题意:找到最小的"好整数"(各位数字非递减),使其是给定正整数N的倍数。不存在输出 -1。
(以下分析来自豆包)
核心思路
我们可以用**广度优先搜索(BFS)**来按"数字长度从小到大"的顺序生成所有可能的好整数,并在过程中记录余数,从而快速找到最小的解。
- 状态定义 :每个状态用
(余数 r, 最后一位数字 d)表示,其中:- r r r 是当前数字模 N N N 的余数。
- d d d 是当前数字的最后一位,保证下一位数字 k ≥ d k \ge d k≥d,从而满足"非递减"的条件。
- 剪枝策略 :使用
vis[r][d]数组标记已经访问过的状态。如果同一个(r, d)再次出现,说明可以通过更短的数字得到相同的余数,因此无需再次入队,避免了无效搜索。 - 终止条件 :当某个状态的余数 r = 0 r = 0 r=0 且数字非空时,该数字即为所求的最小好整数。
时间复杂度分析
- 状态总数 :余数 r r r 有 N N N 种可能,最后一位数字 d d d 有 9 种可能(1到9),因此总共有 O ( N × 9 ) O(N \times 9) O(N×9) 个状态。
- 每个状态的操作 :每个状态出队后,会枚举从 d d d 到 9 的数字(最多9次),每次操作是 O ( 1 ) O(1) O(1) 的。
- 总体复杂度 : O ( N × 9 × 9 ) = O ( N ) O(N \times 9 \times 9) = O(N) O(N×9×9)=O(N), ≈ 2.43 × 10 8 \approx 2.43 \times 10^8 ≈2.43×108,需要小心卡常
关键点
- BFS保证最小性:按数字长度从小到大搜索,第一个找到的余数为0的解就是最小的。
- 状态剪枝 :
vis[r][d]确保每个状态只访问一次,避免了指数级的时间爆炸。
cpp
struct node
{
int r,d;
string res;
};
//没有用longlong
int vis[N][10];
inline void solve(){
int n;cin>>n;
if(n<10)return cout<<n<<endl,void();//特判节省时间
queue<node>q;
q.push({0,1,""});
while (q.size())
{
auto [r,d,s]=q.front();q.pop();
if(r==0&&s!="")
return cout<<s<<endl,void();
forr(i,d,9){//下一位>=d
int nr=(r*10+i)%n;//nr和上一个数的r,d有关
//一种(r,d)枚举过一次就可以 重复枚举得到的路径相同但是结果更大 不会更优 vis剪枝
if(vis[nr][i])continue;
vis[nr][i]=1;
string ns=s+char(i+'0');
q.push({nr,i,ns});
}
}
cout<<-1<<endl;
}
G 类欧几里得

请你找出满足 0 ≤ k < N 0≤k<N 0≤k<N且 ( A k + B ) m o d M > k (Ak+B)\mod M>k (Ak+B)modM>k的整数 k k k有多少个。
暴力必超时,关键在于化不等式

我服了手写的类欧几里得有四个点过不去,不知道怎么处理了,对类欧几里得理解还不深,之后慢慢找原因吧,红温了
错误例子:
1
2 3 1 0
cpp
//int 已开longlong
// int floor_sum(int a,int b,int c,int n){//递归
// int res=0;
// if(a>=c){
// res+=((n-1)*n/2)*(a/c);
// a%=c;
// }
// if(b>=c){
// res+=n*(b/c);
// b%=c;
// }
// int m=(a*n+b)/c;
// if(m==0)return res;
// // res+=n*m-floor_sum(c,c-b-1,a,m);
// res+=n*m-floor_sum(c,(a*n+b)%c,a,m);
// return res;
// }
int floor_sum(int a,int b,int c,int n){//迭代
int res=0;
while(1){
if(a>=c){
res+=((n-1)*n/2)*(a/c);
a%=c;
}
if(b>=c){
res+=n*(b/c);
b%=c;
}
int mx=(a*n+b);
if(mx<c)break;
n=mx/c,b=mx%c;
swap(c,a);
}
return res;
}
void solve(){
int n,m,a,b;cin>>n>>m>>a>>b;
int ans;
//是边界值的问题吗? a=-1能拿进去计算吗
// if(a==0&&b==0)ans=1;
// else if(a==0){
// int r=b%m;
// ans=min(n,r);
// }
// else if(b==0){
// if(a==1)ans=0;
// else
// }
// else
ans=n-floor_sum(a,b,m,n)+floor_sum(a-1,b-1,m,n);
cout<<ans<<endl;
}