给定一个长度为\(n\)的序列,问是否可以对它重新排序使得重排后的序列中不存在等差子序列
0 1
开始,左边是整体乘\(2\),右边是整体乘\(2\)加\(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
,所以我们可以把每个数字二进制序列翻转后排序即可构造一个合法的序列#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'; } } }
给定长度为\(n\)的排列\(\{p_i\}\),\(q\)次询问,每次给出一个区间\([l,r]\),问要对该区间进行多少次子区间的循环平移(\(a_i a_{i+1}\cdots a_j\rightarrow a_{i+1}\cdots a_{j} a_{i}\)或者\(a_ia_{i+1}\cdots a_j \rightarrow a_{j}a_{i}\cdots a_{j-1}\)),变成\(B(p[l,r])\),其中\(B(p[l,r])\)的操作是
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; }
\(1\le n,q\le 10^5\)
首先判断\(B\)的作用是什么:假设当前位置是\(i\),假设右边第一个比\(a_i\)大的数的位置是\(j\),那么把\(a_i\)移动到\(j-1\)和\(j\)之间的位置,然后从\(j\)开始继续这样的交换操作。
找右边第一个比\(a_i\)大的数,很容易想到使用单调栈来操作,记作\(r_i\),如果\(r_i=i+1\),那么就不需要操作。问题变成了现在询问给定一个区间\([l,r]\),\([l,r_l-1],[r_l,r_{r_l}-1]\cdots [r_j+1,r]\)有多少段,并且两端断点之间的差值大于\(2\)。考虑\(f_{i,j}\)表示在位置\(i\)经过\(2^j\)次跳跃可以到达的位置,并且我们倒序转移,维护一个\(val\)表示从\(i\)到\(n\)有多少个可以跳跃的\(r_j\),然后对于给定的询问\([l,r]\),倍增找出最后一个小于\(r\)的\(r_j\)的位置\(j\),然后利用一次后缀和作差\(val[l]-val[j]\)即可求出需要跳跃的次数。
#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'; } } }
给定\(n\)个套娃的大小为\(\{a_i\}\),一个大小为\(x\)的套娃能放入大小为\(y\)套娃中当且仅当\(x+r\le y\)。问把这\(n\)个套娃分成\(k\)组,每组中小的一定可以放入大的中的方案数。\(1\le n,k\le 5000\)
由于\(\{a\}\)是单调不减的,所以我们可以\(O(n^2)\)预处理出每个数不能和前面的哪些数放在一起,记作\(ban_i\),并且不能与\(i\)在一起的数也不能在同一组里面,因为假设有两个\(a_{j_1}+r \gt a_i\),\(a_{j_2}+r\gt a_i\),并且\(j_1\lt j_2\),因为\(\{a\}\)是单调不减的,所以若\(a_{j_1}\)和\(a_{j_2}\)可以在一个组里,那么\(a_{j_1}+r\le a_{j_2}\le a_i\),矛盾。
定义\(dp_{i,j}\)表示前\(i\)个数分成\(j\)组的方案数。转移分两种中情况,类似于斯特林数的递推:
#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'; } }
有\(n\)个点,每两个点\((i,j)\)之间的路径边权是\(\gcd(i,j)\),有\(q\)次询问,每次给定两个点,问这两个点之间的最短路径是多少,并求出有多少条。
\(1\le n\le 10^7\),\(1\le q\le 5\times 10^4\)
注意到\(\mu(d)\)的只有不存在偶数次幂的素因子的时候才不为\(0\),所以类似于\(PN\)筛的思想,我们只取有值的地方计算即可,并且不直接计算\(lcm(u,v)\)的素因子,而是把分别求\(u,v\)的素因子后去重得到\(lcm(u,v)\)的素因子,然后类似于\(PN\)筛DFS即可。
#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'; } } }
给定长度为\(n\)的序列\(\{a\}\),每次等概率地从序列从选出两个数\(a,b\),然后放回\(ab+a+b\),问最后剩下的一个数期望是多少
hit1
:\(ab+a+b=(a+1)(b+1)-1\)#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'; } }