2022杭电多校联赛第九场 题解

比赛传送门
作者: fn


签到题

1010题 Sum Plus Product / 和+积

题目大意
给定 𝑛 𝑛 n 个数,每次随机选两个数 𝑎 , 𝑏 𝑎, 𝑏 a,b 合并成为 𝑎 𝑏 + 𝑎 + 𝑏 𝑎𝑏 + 𝑎 + 𝑏 ab+a+b ,直到剩下一个数为
止。求剩下的数字的期望。答案对998244353取模。

考察内容
签到

分析
最终答案和操作顺序无关。枚举一遍就可以。

#include<bits/stdc++.h>
#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

*/ 

基本题

1008题 Shortest Path in GCD Graph / GCD图中的最短路径

题目大意
给定 𝑛 𝑛 n 个点的完全图,两个点之间距离为它们的最大公因数(gcd), 𝑞 𝑞 q 次询问两个点之间的最短路以及方案数。
( 2 ≤ n ≤ 1 0 7 , 1 ≤ q ≤ 50000 ) (2≤n≤10^7 ,1≤q≤50000) (2n107,1q50000)

考察内容
容斥

分析
因为沿路径 𝑥 → 1 → 𝑦 𝑥 → 1 → 𝑦 x1y 即可得到一条长度为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 时,直接 𝑥 → 𝑦 𝑥 → 𝑦 xy 也是一条长度为2的路径。

#pragma GCC optimize(3)
#include<bits/stdc++.h>
#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

*/ 

进阶题

1007题 Matryoshka Doll / 俄罗斯套娃

题目大意
𝑛 𝑛 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[i1][j1]+f[i1][j]max(0,jg[i])
其中 g [ i ] g[i] g[i] 表示满足 1 ≤ 𝑧 < i 1 ≤ 𝑧 < i 1z<i 𝑎 i − 𝑟 < 𝑎 𝑧 𝑎_i − 𝑟 < 𝑎_𝑧 air<az 𝑧 𝑧 z 的个数。

对转移的解释:
右半部分前一项 f [ i − 1 ] [ j − 1 ] f[i-1][j-1] f[i1][j1] 表示第 i i i 个套娃单独分一组的情况;

右半部分后一项 f [ i − 1 ] [ j ] ∗ m a x ( 0 , j − g [ i ] ) f[i-1][j]*max(0,j-g[i]) f[i1][j]max(0,jg[i]) 表示第 i i i 个套娃套到前面的套娃上面的情况,此时套娃的组数 j j j 不变,所以是从 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j] 转移过来。
j − g [ i ] ≤ 0 j-g[i]\leq0 jg[i]0 的时候,找不到可以套的娃,不用转移。
j − g [ i ] ≥ 0 j-g[i]≥0 jg[i]0 的时候,前面有 j − g [ i ] j-g[i] jg[i] 个可以套的娃,转移方案数。

#include<bits/stdc++.h>
#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
*/ 

1003题 Fast Bubble Sort / 快速冒泡排序

题目大意
对于一个长度为𝑁的数组𝐴,令𝐵(𝐴)表示对𝐴进行冒泡排序中的一轮循环之后得到的数组。
令𝑛𝑢𝑚(𝐴)表示从𝐴到𝐵(𝐴)最少需要移动元素(数组区间循环移位)的次数。

给定一个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<bits/stdc++.h>
#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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值