2022 HDU多校9
Arithmetic Subsequence(二进制、思维、分治)
Problem
给定一个长度为的序列,问是否可以对它重新排序使得重排后的序列中不存在等差子序列
Solve
- 如果一个数出现了次及以上,一定无解
- 若成等差数列,那么和奇偶性相同,所以如果把偶数放到左边,奇数放到右边,那么不会有横跨两边的等差数列。所以只需递归地去考虑一边
- 递归时,把偶数除以,把奇数减再除以递归即可
- 一种优化二进制写法:考虑从
0 1
开始,左边是整体乘,右边是整体乘加,得到0 2 1 3
,继续0 4 2 6 1 5 3 7
。写成而二进制的形式000 100 010 110 001 101 011 111
,把这个二进制翻转,得到000 001 010 011 100 101 110 111
,即0 1 2 3 4 5 6 7
,所以我们可以把每个数字二进制序列翻转后排序即可构造一个合法的序列
Solve
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n;
cin>>n;
map<int,int>cnt;
vector<pair<int,int>>a(n);
auto rev=[&](int x)->int{
int ans=0;
for(int i=30;i>=0;i--,x>>=1){
ans|=(x&1)<<i;
}
return ans;
};
for(int i=0;i<n;i++){
cin>>a[i].second;
cnt[a[i].second]++;
a[i].first=rev(a[i].second);
}
bool ok=1;
for(auto t:cnt){
if(t.second>=3){
ok=0;
break;
}
}
if(!ok) cout<<"NO\n";
else{
cout<<"YES\n";
sort(a.begin(), a.end());
for(auto x:a) cout<<x.second<<" ";
cout<<'\n';
}
}
}
Fast Bubble Sort(单调栈、倍增)
Problem
给定长度为的排列,次询问,每次给出一个区间,问要对该区间进行多少次子区间的循环平移(或者),变成,其中的操作是
int* B(int *p, int x, int y)
{
for(int i=x;i<y;i++)
if(p[i]>p[i+1])
swap(p[i],p[i+1]);
return p;
}
Solve
首先判断的作用是什么:假设当前位置是,假设右边第一个比大的数的位置是,那么把移动到和之间的位置,然后从开始继续这样的交换操作。
找右边第一个比大的数,很容易想到使用单调栈来操作,记作,如果,那么就不需要操作。问题变成了现在询问给定一个区间,有多少段,并且两端断点之间的差值大于。考虑表示在位置经过次跳跃可以到达的位置,并且我们倒序转移,维护一个表示从到有多少个可以跳跃的,然后对于给定的询问,倍增找出最后一个小于的的位置,然后利用一次后缀和作差即可求出需要跳跃的次数。
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,q;
cin>>n>>q;
vector<int>stk(n+1),p(n+1),r(n+1);
for(int i=1;i<=n;i++) cin>>p[i];
int top=0;
for(int i=n;i>=1;i--){
while(top && p[i]>p[stk[top]]) top--;
if(!top) r[i]=n+1;
else r[i]=stk[top];
stk[++top]=i;
}
vector<vector<int>>f(n+3,vector<int>(25,n+1));
vector<int>val(n+2);
for(int i=n;i>=1;i--){
val[i]=val[r[i]]+(r[i]>i+1);
f[i][0]=r[i];
for(int j=1;j<=20;j++) f[i][j]=f[f[i][j-1]][j-1];
}
while(q--){
int l,r;
cin>>l>>r;
int i=l;
for(int j=20;j>=0;j--){
if(f[i][j]<=r){
i=f[i][j];
}
}
int ans=val[l]-val[i];
if(r!=i) ans++;
cout<<ans<<'\n';
}
}
}
Matryoshka Doll(DP)
Problem
给定个套娃的大小为,一个大小为的套娃能放入大小为套娃中当且仅当。问把这个套娃分成组,每组中小的一定可以放入大的中的方案数。
Solve
由于是单调不减的,所以我们可以预处理出每个数不能和前面的哪些数放在一起,记作,并且不能与在一起的数也不能在同一组里面,因为假设有两个,,并且,因为是单调不减的,所以若和可以在一个组里,那么,矛盾。
定义表示前个数分成组的方案数。转移分两种中情况,类似于斯特林数的递推:
- 第个数自己一个一组,那么
- 第个数放到前面可以与它在一组的组里面,那么
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n,k,r;
cin>>n>>k>>r;
vector<int>ban(n+1),a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++)
if(a[i]<r+a[j]) ban[i]++;
}
vector<vector<ll>>dp(n+1,vector<ll>(k+1));
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i,k);j++){
dp[i][j]=(dp[i-1][j-1]+dp[i-1][j]*(j-ban[i])%mod)%mod;
}
}
cout<<dp[n][k]<<'\n';
}
}
Shortest Path in GCD Graph(数论、莫比乌斯反演)
Problem
有个点,每两个点之间的路径边权是,有次询问,每次给定两个点,问这两个点之间的最短路径是多少,并求出有多少条。
,
Solve
- 若,那么最小路径就是,并且只有直接连接的这一条路径
- 若,那么最小路径就是,我们选一个和都互质的点作为中转点即可。而这个路径条数就是到中即和也和互素的数的个数,所以问题变成了就的答案。
注意到的只有不存在偶数次幂的素因子的时候才不为,所以类似于筛的思想,我们只取有值的地方计算即可,并且不直接计算的素因子,而是把分别求的素因子后去重得到的素因子,然后类似于筛DFS即可。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int N=1e7;
int prime[N+5],st[N+5],cnt,mu[N];
void get_primes(int n){
mu[1]=1;
for(int i=2;i<=n;i++){
if(!st[i]) prime[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt && prime[j]*i<=n ;j++){
st[i*prime[j]]=1;
if(i%prime[j]==0){
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
}
set<int>factor;
vector<int>p;
void divide(int x){
for(int i=2;i*i<=x;i++){
if(x%i==0){
factor.insert(i);
while(x%i==0){
x/=i;
}
}
}
if(x>1) factor.insert(x);
}
ll ans;
int n,q;
void dfs(int i,ll d){
if(d>n){
return;
}
if(i==int(p.size())){
ans+=mu[d]*(n/d);
return;
}
dfs(i+1,d*p[i]);
dfs(i+1,d);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>q;
get_primes(n);
while(q--){
int u,v;
cin>>u>>v;
int k=__gcd(u,v);
if(k==1){
cout<<1<<" "<<1<<'\n';
}
else{
cout<<2<<" ";
factor.clear();
p.clear();
divide(u),divide(v);
ans=0;
for(auto x:factor) p.push_back(x);
dfs(0,1);
cout<<ans+(k==2)<<'\n';
}
}
}
Sum Plus Product(数学推导)
Problem
给定长度为的序列,每次等概率地从序列从选出两个数,然后放回,问最后剩下的一个数期望是多少
Solve
hit1
:
可以发现最后的答案是固定的,即
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
int n;
cin>>n;
ll ans=1;
for(int i=1;i<=n;i++){
int x;
cin>>x;
ans=ans*(x+1)%mod;
}
ans=(ans-1+mod)%mod;
cout<<ans<<'\n';
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)