传送门: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=(k−1)∗n+1,所以需要的余数个数也不难计算,即 m i d − ( n ∗ n u m − ( n − 1 ) ) mid-(n*num-(n-1)) mid−(n∗num−(n−1)),此时可能为负,但是没关系,负数代表不需要.对于余数来说,我们贪心的去想,显然是尽量用数量多的去当做余数.所以对于那些需要当做余数的卡,我们需要的个数是 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)
(b∗gcd(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
b∣a,所以我们直接枚举
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)∣(b∗gcd(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+i−1i,这个式子的证明可以使用我那个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;
}