VP记录:Codeforces Round 942 (Div. 2) A~E (猜猜+数学场)

54 篇文章 0 订阅
11 篇文章 1 订阅

传送门:CF

[前题提要]:本人比较喜欢一些数学题.这场的D2以及E都很符合我胃口(虽然都没做出来就是了),特别是E,用到了我之前碰到过的一个K阶前缀和的trick,故写篇题解记录一下


A. Contest Proposal

考虑贪心.遇到一个不满足条件的位置,就加入一个小于等于 b i b_i bi的数.
简单解释上述操作的正确性,设 i i i是从左往右第一位 a i > b i a_i>b_i ai>bi的位置,那么此时我们需要加入一个数字来改变这个状况,我们此时会发现插入一个大于 b i b_i bi的数是没有任何作用的(并不会改变该位置的大小关系).所以我们插入的数一定是小于等于 b i b_i bi的数.当我们插入上述任意一个数字的时候,我们会发现对于 i i i位置后面的数的贡献都是一样的(也就是将 a a a数组中 i i i位置及其后面的数字往后移一位);对于 i i i位置前面的数也没有影响,因为会重新排列,所以只会让原本位置的数更小而不会变.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn],b[maxn];
int main() {
	int T=read();
	while(T--) {
		int n=read();
		priority_queue<int,vector<int>,greater<int> >q;
		for(int i=1;i<=n;i++) {
			a[i]=read();
			q.push(a[i]);
		}
		for(int i=1;i<=n;i++) {
			b[i]=read();
		}
		int ans=0;
		for(int i=1;i<=n;i++) {
			int num=q.top();
			if(num<=b[i]) {
				q.pop();
			}
			else {
				ans++;
			}
		}
		cout<<ans<<endl;
	}	
	return 0;
}

B. Coin Games

猜猜题.感觉很无聊,但是很CF.
遇到这种过题人数极多的签到博弈论.不用细想,必然是奇偶性相关的结论.所以赛时直接猜想 U U U个数为奇数,则 A l i c e    w i n Alice\;win Alicewin反之则是 B o b    w i n Bob\;win Bobwin赛时直接交一发试试正确性即可.99.9999%一发过.

下面简单来证明一下上述做法的正确性:
为了方便起见,我们将 U U U记为 1 1 1, D D D记为 0 0 0.那么对于我们挑选的任意一个翻转区间,有以下几种情况:
01 , 10 , 11 , 010 , 011 , 110 , 111 01,10,11,010,011,110,111 01,10,11,010,011,110,111
对于两个位置的情况(也就是选了一个位置是最左端或者最右端),此时显然会改变1的个数的奇偶性.
对于三个位置的情况,我们分别模拟过去,会惊奇的发现他们都会改变奇偶性.

显然的,我们会发现当没有一个位置是1(为偶数状态)的时候,此时决策者 l o s e lose lose.并且因为奇偶性每次操作都会变化,所以奇数情况下的决策者面对的永远都是奇数,偶数永远都是偶数.所以初始情况下如果是偶数,那么 B o b    w i n Bob\;win Bobwin,反之 A l i c e    w i n Alice\;win Alicewin

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int main() {
	int T=read();
	while(T--) {
		int n=read();string s;cin>>s;s=" "+s;
		int cnt=0;
		for(int i=1;i<=n;i++) {
			if(s[i]=='U') {
				cnt++;
			}
		}
		if(cnt&1) {
			cout<<"YES"<<endl;
		}
		else {
			cout<<"NO"<<endl;
		}
	}
	return 0;
}

C. Permutation Counting

二分答案.
看完题面.不难想到最终的构造方案应该如下这种: x y z x y z . . . x y z x xyzxyz...xyzx xyzxyz...xyzx循环构造.
那么我们肯定会意识到先尽量的补充个数小的,尽量使其能够循环下去.直接考虑似乎有点麻烦,所以我们选择使用二分答案.(二分单调性是显然的,我们需要的分数越高越难满足,反之越容易满足).

因为我们必然选择补充个数小的卡,所以我们可以直接排个序.那么此时我们可以求出需要满足二分出来的答案的循环次数,还有除此之外的余数.这个余数是什么意思呢,举个例子,对于 x y z x y xyzxy xyzxy来说,只有一个循环,但是还有两个单位的余数,所以答案贡献要加上那两个余数.考虑对于 k k k个长度为 n n n的循环贡献是多少,不难发现是 n u m = ( k − 1 ) ∗ n + 1 num=(k-1)*n+1 num=(k1)n+1,所以需要的余数个数也不难计算,即 m i d − ( n ∗ n u m − ( n − 1 ) ) mid-(n*num-(n-1)) mid(nnum(n1)),此时可能为负,但是没关系,负数代表不需要.对于余数来说,我们贪心的去想,显然是尽量用数量多的去当做余数.所以对于那些需要当做余数的卡,我们需要的个数是 n u m + 1 num+1 num+1,其他的需要个数是 n u m num num.并且那些作为余数的卡是排完序后的前缀.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];int n,k;int b[maxn];
int check(int mid) {
	int temp=k;
	for(int i=1;i<=n;i++) {
		b[i]=a[i];
	}
	int num=(mid+n-1)/n;int k=mid-(n*num-(n-1));
	for(int i=1;i<=n;i++) {
		if(i<=k) {
			if(b[i]<=num+1) {
				temp-=num+1-b[i];
			}
		}
		else {
			if(b[i]<=num) {
				temp-=num-b[i];
			}
		}
		if(temp<0) return false;
	}
	return true;
}
signed main() {
	int T=read();
	while(T--) {
		n=read();k=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}
		sort(a+1,a+n+1);
		reverse(a+1,a+n+1);
		int l=1,r=1e18;int ans=-1;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(check(mid)) {
				l=mid+1;ans=mid;
			}
			else {
				r=mid-1;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

D1. Reverse Card (Easy Version)

数学题?打表题.
想一下, ( b ∗ g c d ( a , b ) ) ∣ ( a + b ) (b*gcd(a,b))|(a+b) (bgcd(a,b))(a+b),那么 b ∣ ( a + b ) b|(a+b) b(a+b),因为如果 a + b a+b a+b b b b都不整除,那么就不用谈何再整除 g c d ( a , b ) gcd(a,b) gcd(a,b)了.所以 b ∣ ( a + b ) b|(a+b) b(a+b),所以我们不难发现 b ∣ a b|a ba,所以我们直接枚举 b b b,然后再枚举倍数即可.复杂度是经典的调和级数求和,极限为 n l o g n nlogn nlogn.

当然上述所有的推导我们直接打表也可以观察出来
注意我们不能预处理所有数的倍数,因为 v e c t o r vector vector常数太大,此时会超时,但是我们在线做就并不会.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define int long long
#define maxn 2010000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
signed main() {
//	for(int i=1;i<=100;i++) {
//		for(int j=1;j<=100;j++) {
//			if((i+j)%(j*gcd(i,j))==0) {
//				cout<<i<<" "<<j<<endl;
//			}
//		}
//	}
//	for(int i=1;i<=2e6;i++) {
//		for(int j=i;j<=2e6;j+=i) {
//			factor[j].push_back(i);
//		}
//	}
	int T=read();
	while(T--) {
		int n=read();int m=read();
		ll ans=0;
		for(int i=1;i<=m;i++) {
			for(int j=i;j<=n;j+=i) {
				if((i+j)%(i*i)==0) {
				//这里可以省一个gcd,虽然严格证明下这个gcd可以均摊掉,使得复杂度依旧是nlogn
					ans++;
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

D2. Reverse Card (Hard Version)

哈哈,打表做不了了吧.说的就是我(这种靠直觉+打表过D1的)
考虑怎么做 ( a + b ) ∣ ( b ∗ g c d ( a , b ) ) (a+b)|(b*gcd(a,b)) (a+b)(bgcd(a,b)).此时打表已经没有规律.但是稍微有点经验的人都知道此时我们要枚举 g c d gcd gcd,为了方便起见我们记 g c d ( a , b ) = g gcd(a,b)=g gcd(a,b)=g.所以此时按照套路改写 a , b a,b a,b,记 a = x g , b = y g a=xg,b=yg a=xg,b=yg.然后我们就可以化解原式,使其变为 ( x + y )    ∣    b (x+y)\;|\;b (x+y)b,然后当时我就卡住了.

其实要是能预处理因子的话,我当时想出了一个枚举 b b b,然后枚举因子加一个双指针的一个维护做法.此时的期望复杂度依旧是 n l o g n nlogn nlogn,但是常数巨大,实测跑了4s,过不去本题.(显然的,被出题人故意卡了,如果n为1e6,在cf上该方法不一定跑不过去).因为跑不过去,所以本方法就不在此介绍了.

其实大部分在这里卡住的主要原因是没有发现 g c d ( x , y ) = 1 gcd(x,y)=1 gcd(x,y)=1,这个其实格外重要(毕竟是一个条件,少了一个条件怎么做得出题目呢).我们考虑继续化解原式 ( x + y ) ∣ y g (x+y)|yg (x+y)yg,设 y g = k ( x + y ) yg=k(x+y) yg=k(x+y),因为 g c d ( x , y ) = 1 gcd(x,y)=1 gcd(x,y)=1,所以根据更相损减术,我们可以得到 g c d ( x + y , y ) = 1 gcd(x+y,y)=1 gcd(x+y,y)=1.所以此时我们会发现 x + y x+y x+y只能被 g g g所包括.也就是 ( x + y )    ∣    g (x+y)\;|\;g (x+y)g.所以我们现在考虑枚举 x , y x,y x,y,此时考虑一下 x , y x,y x,y的上限,设 g = t ( x + y ) g=t(x+y) g=t(x+y),所以 a = t ( x + y ) x , b = t ( x + y ) y a=t(x+y)x,b=t(x+y)y a=t(x+y)x,b=t(x+y)y,不难发现 x 2 < n , y 2 < m x^2<n,y^2<m x2<n,y2<m,所以我们直接枚举 x , y x,y x,y的复杂度竟然是 O ( n ) O(n) O(n)的.此时我们就可以来计算对于一个 ( x , y ) (x,y) (x,y),我们有多少个 g g g.累加起来就是最终的贡献.显然有多少个 g g g就有多少个 t t t,所以此时的个数就是 m i n ( n x ( x + y ) , m y ( x + y ) ) min(\frac{n}{x(x+y)},\frac{m}{y(x+y)}) min(x(x+y)n,y(x+y)m).严格来说,上述的 g g g是一个必要条件,但是可以证明,对于任意一个满足上述的g,我们都可以找到一个k,满足 y g = k ( x + y ) yg=k(x+y) yg=k(x+y),证明很简单,此处就略过了

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define int long long
#define maxn 2000001
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int kkk[2000][2000];
signed main() {
	int T=read();
	while(T--) {
		int n=read();int m=read();
		int limit1=__builtin_sqrt(n)+1;
		int limit2=__builtin_sqrt(m)+1;
		ll ans=0;
		for(int i=1;i<=limit1;i++) {
			for(int j=1;j<=limit2;j++) {
				if(gcd(i,j)==1) {
					ans+=min(n/(i+j)/i,m/(i+j)/j);
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

E. Fenwick Tree

打表+瞪眼法?
首先 F e n w i c k    T r e e Fenwick\;Tree FenwickTree是指树状数组,所以肯定跟树状数组的某些性质有关.手模一下.设原序列为

k=0   a1  a2       a3  a4                 a5  a6       a7 
k=1   a1  a1+a2    a3  a1+a2+a3+a4        a5  a5+a6    a7 
k=2   a1  2*a1+a2  a3  3*a1+2*a2+2*a3+a4  a5  2*a5+a6  a7
k=3   a1  3*a1+a2  a3  6*a1+3*a2+3*a3+a4  a5  3*a5+a6  a7
k=4   a1  4*a1+a2  a3  10*a1+4*a2+4*a3+a4 a5  4*a5+a6  a7

其实看到这道k阶的东西,我当时脑子里就出现了这个东西,就是当时我在学多项式的时候碰到的k阶前缀和和k阶差分.为什么我一下子就想到了,因为这个当时我初学的时候感觉十分惊叹,前缀和和差分竟然可以和多项式结合起来,所以在我的印象里特别深刻.我还特意写了一篇博客

然后我瞟了一眼 a 4 a4 a4的变化刚开始是系数 1 , 1 , 1 , 1 1,1,1,1 1,1,1,1,然后是 1 , 2 , 3 1,2,3 1,2,3,然后是 1 , 3 , 6 1,3,6 1,3,6,最后是 1 , 4 , 10 1,4,10 1,4,10.我靠,这不就是k阶前缀和吗.就是初始序列为 1 , 1 , 1 , 1.....1 1,1,1,1.....1 1,1,1,1.....1 k阶前缀和形式.然后仔细观察一下,似乎离当前位置越远的贡献权值越大,并且还有相等的.然后和 l o w b i t lowbit lowbit结合一下,发现距离其实是用lowbit跳跃的次数!!.然后发现 a 4 a4 a4之所以有 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3的贡献,是因为只有它们能通过lowbit跳到a4,同理其他位置也是一样.所以此时我们就知道了每一个数是由那些数组成的.此时我们只要反推一下,将每一个数的贡献减掉即可(因为大的数不会给小的数贡献,所以这样做的正确性是显然的)

但是此时我们还有一个问题,就是我们的 K K K很大(但也没有那么大),所以直接模拟是不行的.但是作为CF的div2E题,又怎么可能用得到 N T T NTT NTT这种科技.然后我们就发现存在这样的一个性质,我们只需要每行前缀和的前 20 20 20个即可.因为lowbit距离在本题范围里跳的次数很小.但是我们可以套用那个k阶前缀和的一个结论,也就是第 k k k行的第 i i i个数字其实就是 C k + i − 1 i C_{k+i-1}^{i} Ck+i1i,这个式子的证明可以使用我那个K阶前缀和里的生成函数法.当然也有更加简单的方法.因为你发现实际上初始序列为 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , . . . . . , 1 1,1,1,1,1,1,1,1,.....,1 1,1,1,1,1,1,1,1,.....,1的k阶前缀和其实就是杨辉三角的一种形式.所以也可以使用杨辉三角来求出这个式子.

至此,本题也就解决了.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const int mod=998244353;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int lowbit(int x) {
	return x&-x;
}
int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
int b[maxn];int sum[30];
int fac[maxn],in_fac[maxn];
void init(int limit=100) {
	fac[0]=1;
	for(int i=1;i<=limit;i++) {
		fac[i]=fac[i-1]*i%mod;
	}
	in_fac[limit]=qpow(fac[limit],mod-2);
	for(int i=limit-1;i>=0;i--) {
		in_fac[i]=in_fac[i+1]*(i+1)%mod;
	}
}
signed main() {		
	init();
	int T=read();
	while(T--) {
		int n=read();int k=read();
		for(int i=1;i<=n;i++) {
			b[i]=read();
		}
		for(int i=1;i<=20;i++) {
			sum[i]=1;
			for(int j=1;j<=i;j++) {
				sum[i]=sum[i]*(k+i-j)%mod;
			}
			sum[i]=sum[i]*in_fac[i]%mod;
		}
		for(int i=1;i<=n;i++) {
			int cnt=1;
			for(int j=i+lowbit(i);j<=n;j+=lowbit(j)) {
				b[j]-=b[i]*sum[cnt];b[j]%=mod;
				cnt++;
			}
			b[i]=(b[i]+mod)%mod;
		}
		for(int i=1;i<=n;i++) {
			cout<<b[i]<<" ";
		}
		cout<<endl;
	}
	return 0;
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值