比赛传送门
作者: fn
题目大意
给定
𝑛
𝑛
n 个数,每次随机选两个数
𝑎
,
𝑏
𝑎, 𝑏
a,b 合并成为
𝑎
𝑏
+
𝑎
+
𝑏
𝑎𝑏 + 𝑎 + 𝑏
ab+a+b ,直到剩下一个数为
止。求剩下的数字的期望。答案对998244353取模。
考察内容
签到
分析
最终答案和操作顺序无关。枚举一遍就可以。
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=505;
const ll mod=998244353;
ll n,m,a[N];
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t; cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
ll ans=0;
for(int i=1;i<=n-1;i++){
ll cnt=0;
cnt+=(a[i]+a[i+1])%mod;
cnt%=mod;
cnt+=a[i]*a[i+1]%mod;
cnt%=mod;
a[i+1]=cnt;
}
cout<<a[n]<<endl;
}
return 0;
}
/*
1
10
1 2 4 8 16 32 64 128 256 512
*/
题目大意
给定
𝑛
𝑛
n 个点的完全图,两个点之间距离为它们的最大公因数(gcd),
𝑞
𝑞
q 次询问两个点之间的最短路以及方案数。
(
2
≤
n
≤
1
0
7
,
1
≤
q
≤
50000
)
(2≤n≤10^7 ,1≤q≤50000)
(2≤n≤107,1≤q≤50000)
考察内容
容斥
分析
因为沿路径
𝑥
→
1
→
𝑦
𝑥 → 1 → 𝑦
x→1→y 即可得到一条长度为2的路径,所以最短路长度不超过2。
最短路长度为1当且仅当
𝑔
𝑐
𝑑
(
𝑥
,
𝑦
)
=
1
𝑔𝑐𝑑 (𝑥, 𝑦) = 1
gcd(x,y)=1 ,否则等价于询问
[
1
,
𝑛
]
[1, 𝑛]
[1,n] 中满足
𝑔
𝑐
𝑑
(
𝑥
,
𝑧
)
=
1
𝑔𝑐𝑑 (𝑥, 𝑧) = 1
gcd(x,z)=1 且
𝑔
𝑐
𝑑
(
𝑦
,
𝑧
)
=
1
𝑔𝑐𝑑 (𝑦, 𝑧) = 1
gcd(y,z)=1 的
𝑧
𝑧
z 的数量,
先跑一遍欧拉筛,然后对
𝑥
,
𝑦
𝑥, 𝑦
x,y 分解质因数,用这些质因数把
[
1
,
n
]
[1,n]
[1,n] 中选不了的数字筛掉。如果直接在
[
1
,
n
]
[1,n]
[1,n] 筛,不容易判断是向下取整还是向上取整,所以考虑容斥,“把多筛的补回来,把多补的筛回来”。
注意特判: 𝑔 𝑐 𝑑 ( 𝑥 , 𝑦 ) = 2 𝑔𝑐𝑑 (𝑥, 𝑦) = 2 gcd(x,y)=2 时,直接 𝑥 → 𝑦 𝑥 → 𝑦 x→y 也是一条长度为2的路径。
#pragma GCC optimize(3)
#include
#define ll long long
#define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e7+5;
const ll mod=998244353;
ll read(ll &n){
char ch=' '; ll q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
n=q*w; return n;
}
ll n,m,q,a[N];
bool isPrime[10000005];
// isPrime[i] == 1表示:i是素数
int Prime[6000010], cnt = 0;
// Prime存质数
void findPrime(ll n) {
memset(isPrime, 1, sizeof(isPrime));
isPrime[1] = 0;// 1不是素数
for (int i = 2; i <= n; i++){
if (isPrime[i])// 没筛掉
Prime[++cnt] = i; // i成为下一个素数
for (int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++){
// 从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
// 当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
isPrime[i*Prime[j]] = 0;
if (i % Prime[j] == 0)break;
}
}
}
signed main(){ //
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
read(n); read(q);
findPrime(n);
ll x,y;
for(int i=1;i<=q;i++){ // q<=50000,O(qlonn)左右可以通过
read(x); read(y);
if(__gcd(x,y)==1){ // 互质
cout<<"1 1"<<endl;
continue;
}
// x,y不互质时
unordered_map<int,bool> mp;
vector<int> v;
ll cnt3=0;
if(isPrime[x]){
if(mp[x]==0){
mp[x]=1;
cnt3++;
v.push_back(x);
}
}
else{
ll x2=x;
ll p=2;
while(x2>=2){
while(x2%p==0){
if(mp[p]==0){
mp[p]=1;
cnt3++;
v.push_back(p);
}
x2/=p;
}
p++;
if(isPrime[x2]){ // x2是质数
if(mp[x2]==0){
mp[x2]=1;
cnt3++;
v.push_back(x2);
}
break; // 提前退出
}
}
}
if(isPrime[y]){
if(mp[y]==0){
mp[y]=1;
cnt3++;
v.push_back(y);
}
}
else{
ll x2=y;
ll p=2;
while(x2>=2){
while(x2%p==0){
if(mp[p]==0){
mp[p]=1;
cnt3++;
v.push_back(p);
}
x2/=p;
}
p++;
if(isPrime[x2]){ // x2是质数
if(mp[x2]==0){
mp[x2]=1;
cnt3++;
v.push_back(x2);
}
break; // 提前退出
}
}
}
sort(v.begin(),v.end());
int len=v.size(); // len<=8
// len>=9时的n>=223092870,所以len<=8
// 容斥
ll ans=n;
for(int i=0;i<=len-1;i++){
ans-=n/v[i]; // 向下取整
for(int j=0;j<=i-1;j++){
ll sum=v[i]*v[j];
if(sum>n)break;
ans+=n/sum;
for(int k=0;k<=j-1;k++){
sum*=v[k];
if(sum>n){
sum/=v[k];
break;
}
ans-=n/sum;
for(int k2=0;k2<=k-1;k2++){
sum*=v[k2];
if(sum>n){
sum/=v[k2];
break;
}
ans+=n/sum;
for(int k3=0;k3<=k2-1;k3++){
sum*=v[k3];
if(sum>n){
sum/=v[k3];
break;
}
ans-=n/sum;
for(int k4=0;k4<=k3-1;k4++){
sum*=v[k4];
if(sum>n){
sum/=v[k4];
break;
}
ans+=n/sum;
for(int k5=0;k5<=k4-1;k5++){
sum*=v[k5];
if(sum>n){
sum/=v[k5];
break;
}
ans-=n/sum;
for(int k6=0;k6<=k5-1;k6++){
sum*=v[k6];
if(sum>n){
sum/=v[k6];
break;
}
ans+=n/sum;
sum/=v[k6];
}
sum/=v[k5];
}
sum/=v[k4];
}
sum/=v[k3];
}
sum/=v[k2];
}
sum/=v[k];
}
}
}
// 特判2
if(__gcd(x,y)==2){
ans++;
}
cout<<"2 ";
cout<<ans%mod<<endl;
}
return 0;
}
/*
100000 1
4 6
10000000 1
2 9699690
正确输出:
2 1710234
99999 1
2 10
正确输出:
40000
*/
题目大意
有
𝑛
𝑛
n 个套娃,现在要将这些套娃分成
𝑘
𝑘
k 组,每组套娃按照大小排序后相邻两个套娃之间的大小差距要求
≥
𝑟
≥ 𝑟
≥r ,求方案数。
考察内容
动态规划
分析
这道题主要是转移比较难想。。。
状态:
f
[
i
]
[
j
]
:
f[i][j]:
f[i][j]: 前
i
i
i 个格子,分成
j
j
j 组的方案数。
边界:
f
[
0
]
[
0
]
=
1
f[0][0]=1
f[0][0]=1
可以用来转移,使
f
[
1
]
[
1
]
=
1
f[1][1]=1
f[1][1]=1
转移:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
+
f
[
i
−
1
]
[
j
]
∗
m
a
x
(
0
,
j
−
g
[
i
]
)
f[i][j]=f[i-1][j-1]+f[i-1][j]*max(0,j-g[i])
f[i][j]=f[i−1][j−1]+f[i−1][j]∗max(0,j−g[i])
其中
g
[
i
]
g[i]
g[i] 表示满足
1
≤
𝑧
<
i
1 ≤ 𝑧 < i
1≤z<i 且
𝑎
i
−
𝑟
<
𝑎
𝑧
𝑎_i − 𝑟 < 𝑎_𝑧
ai−r<az 的
𝑧
𝑧
z 的个数。
对转移的解释:
右半部分前一项
f
[
i
−
1
]
[
j
−
1
]
f[i-1][j-1]
f[i−1][j−1] 表示第
i
i
i 个套娃单独分一组的情况;
右半部分后一项
f
[
i
−
1
]
[
j
]
∗
m
a
x
(
0
,
j
−
g
[
i
]
)
f[i-1][j]*max(0,j-g[i])
f[i−1][j]∗max(0,j−g[i]) 表示第
i
i
i 个套娃套到前面的套娃上面的情况,此时套娃的组数
j
j
j 不变,所以是从
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j] 转移过来。
j
−
g
[
i
]
≤
0
j-g[i]\leq0
j−g[i]≤0 的时候,找不到可以套的娃,不用转移。
j
−
g
[
i
]
≥
0
j-g[i]≥0
j−g[i]≥0 的时候,前面有
j
−
g
[
i
]
j-g[i]
j−g[i] 个可以套的娃,转移方案数。
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=5005;
const ll mod=998244353;
ll n,m,k,r,a[N];
ll f[N][N]; // f[i][j]:前i个格子,分成j组的方案数
ll g[N];
int main(){ // AC
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t; cin>>t;
while(t--){
cin>>n>>k>>r;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=0;i<=n;i++){ // 初始化
for(int j=0;j<=k;j++){
f[i][j]=0;
}
}
f[0][0]=1; // 边界
// 转移
for(int i=1;i<=n;i++){ // 枚举到第i个格子
g[i]=0;
for(int j=1;j<=i-1;j++){
if(a[i]-r<a[j]){ // 统计塞不下的个数
g[i]++;
}
}
for(int j=1;j<=k;j++){ // 分成j组
f[i][j]=f[i-1][j-1]; // 第i个单独分一组
f[i][j]+=f[i-1][j]*max(0ll,j-g[i])%mod;
f[i][j]%=mod;
}
}
cout<<f[n][k]%mod<<endl;
}
return 0;
}
/*
1
4 3 2
1 2 3 4
正确输出:
3
1
4 4 100
1 2 3 4
正确输出:
1
*/
题目大意
对于一个长度为𝑁的数组𝐴,令𝐵(𝐴)表示对𝐴进行冒泡排序中的一轮循环之后得到的数组。
令𝑛𝑢𝑚(𝐴)表示从𝐴到𝐵(𝐴)最少需要移动元素(数组区间循环移位)的次数。
给定一个1 − 𝑛的排列𝑃。𝑞次询问,每次询问𝑛𝑢𝑚(𝑃 [𝑙, 𝑟])
考察内容
单调栈,树状数组,线段树
分析
假设
𝑃
=
𝑛
1
λ
1
𝑛
2
λ
2
⋅
⋅
⋅
𝑛
𝑘
λ
𝑘
𝑃=𝑛_1λ_1𝑛_2λ_2 · · · 𝑛_𝑘λ_𝑘
P=n1λ1n2λ2⋅⋅⋅nkλk ,则
𝐵
(
𝑃
)
=
λ
1
𝑛
1
λ
2
𝑛
2
⋅
⋅
⋅
λ
𝑘
𝑛
𝑘
𝐵(𝑃) = λ_1𝑛_1λ_2𝑛_2 · · · λ_𝑘𝑛_𝑘
B(P)=λ1n1λ2n2⋅⋅⋅λknk ,其中
𝑛
1
,
⋅
⋅
⋅
,
𝑛
𝑘
𝑛_1, · · · , 𝑛_𝑘
n1,⋅⋅⋅,nk 为从左到右的局部最大值且有
𝑛
1
<
𝑛
2
<
⋅
⋅
⋅
<
𝑛
𝑘
𝑛_1 < 𝑛_2 < · · · < 𝑛_𝑘
n1<n2<⋅⋅⋅<nk ,则不难证明答案为
[
l
,
r
]
[l,r]
[l,r] 区间里非空
λ
𝑖
λ_𝑖
λi 的个数。
将询问离线,每次从
𝑛
𝑛
n 到
1
1
1 倒序扫描左端点𝑙并回答所有左端点为
𝑙
𝑙
l 的询问。
对于每个固定的左端点
𝑙
𝑙
l ,
[
𝑙
,
𝑛
]
[𝑙, 𝑛]
[l,n] 中从左到右的局部最大值可以通过单调栈维护,局部最大值插入/删除对于答案的影响可以用树状数组/线段树快速更新/求解。
单组数据时间复杂度为 𝑂 ( ( 𝑛 + 𝑞 ) l o g 𝑛 ) 𝑂( (𝑛 + 𝑞) log 𝑛) O((n+q)logn) 。
#pragma GCC optimize("O3")
#pragma GCC optimize("O2")
#include
#define int long long
#define double long double
using namespace std;
#define rep(i,n) for (int i=0;i<(int)(n);++i)
#define rep1(i,n) for (int i=1;i<=(int)(n);++i)
#define range(x) begin(x), end(x)
#define sz(x) (int)(x).size()
#define pb push_back
#define F first
#define S second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
namespace internal {
int ceil_pow2(int n) {
int x = 0;
while ((1U << x) < (unsigned int)(n)) x++;
return x;
}
int bsf(unsigned int n) {
#ifdef _MSC_VER
unsigned long index;
_BitScanForward(&index, n);
return index;
#else
return __builtin_ctz(n);
#endif
}
} // namespace internal
template <class S, S (*op)(S, S), S (*e)()> struct segtree { // 线段树
public:
segtree() : segtree(0) {}
segtree(int n) : segtree(std::vector<S>(n, e())) {}
segtree(const std::vector<S>& v) : _n((int)(v.size())) {
log = internal::ceil_pow2(_n);
size = 1 << log;
d = std::vector<S>(2 * size, e());
for (int i = 0; i < _n; i++) d[size + i] = v[i];
for (int i = size - 1; i >= 1; i--) {
update(i);
}
}
void set(int p, S x) {
assert(0 <= p && p < _n);
p += size;
d[p] = x;
for (int i = 1; i <= log; i++) update(p >> i);
}
S get(int p) {
assert(0 <= p && p < _n);
return d[p + size];
}
S prod(int l, int r) {
assert(0 <= l && l <= r && r <= _n);
S sml = e(), smr = e();
l += size;
r += size;
while (l < r) {
if (l & 1) sml = op(sml, d[l++]);
if (r & 1) smr = op(d[--r], smr);
l >>= 1;
r >>= 1;
}
return op(sml, smr);
}
S all_prod() { return d[1]; }
template <bool (*f)(S)> int max_right(int l) {
return max_right(l, [](S x) { return f(x); });
}
template <class F> int max_right(int l, F f) {
assert(0 <= l && l <= _n);
assert(f(e()));
if (l == _n) return _n;
l += size;
S sm = e();
do {
while (l % 2 == 0) l >>= 1;
if (!f(op(sm, d[l]))) {
while (l < size) {
l = (2 * l);
if (f(op(sm, d[l]))) {
sm = op(sm, d[l]);
l++;
}
}
return l - size;
}
sm = op(sm, d[l]);
l++;
} while ((l & -l) != l);
return _n;
}
template <bool (*f)(S)> int min_left(int r) {
return min_left(r, [](S x) { return f(x); });
}
template <class F> int min_left(int r, F f) {
assert(0 <= r && r <= _n);
assert(f(e()));
if (r == 0) return 0;
r += size;
S sm = e();
do {
r--;
while (r > 1 && (r % 2)) r >>= 1;
if (!f(op(d[r], sm))) {
while (r < size) {
r = (2 * r + 1);
if (f(op(d[r], sm))) {
sm = op(d[r], sm);
r--;
}
}
return r + 1 - size;
}
sm = op(d[r], sm);
} while ((r & -r) != r);
return 0;
}
private:
int _n, size, log;
std::vector<S> d;
void update(int k) { d[k] = op(d[2 * k], d[2 * k + 1]); }
};
using S=int;
S op(S l,S r){return l+r;}
S e(){return 0;}
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
const int mod=998244353;
const int base[]={12321,32123};
const double EPS=1e-9;
const double pi=acos(-1);
const int INF=1e18;
const int N=100017;
mt19937 rng(1235);
int n,q;
int p[N];
int ans[N],C[N],now[N];
vector<pii> info[N];
int st[N];
int lowbit(int u){return u&(-u);}
void update(int u,int w){for (int i=u;i<=n+7;i+=lowbit(i)) C[i]+=w;}
int query(int u){int ans=0; for (int i=u;i;i-=lowbit(i)) ans+=C[i]; return ans;}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int _;
cin>>_;
while (_--){
cin>>n>>q;
rep(i,n) cin>>p[i];
for (int i=1;i<=n+5;++i) C[i]=0,now[i]=0;
rep(i,N) info[i].clear();
p[n]=n+1;
rep(i,q){
int u,v;
cin>>u>>v;
u--, v--;
info[u].pb({i,v});
}
int cnt=0;
st[cnt++]=n;
auto upd=[&](int u){
update(u+1,(now[u+1]?-1:1)), now[u+1]^=1;
update(u+2,(now[u+2]?-1:1)), now[u+2]^=1;
};
for (int i=n-1;i>-1;--i){
while (cnt&&p[i]>p[st[cnt-1]]) {
cnt--;
upd(st[cnt]);
}
st[cnt++]=i, upd(i);
for (auto c:info[i]){
int id=c.F, r=c.S;
if (i==r) ans[id]=0;
else ans[id]=(query(r+1)-query(i))/2;
}
}
rep(i,q) cout<<ans[i]<<"\n";
}
return 0;
}