目录


A. Tit for Tat

​LINK​

记 ∑ i = 1 n − 1 a i = s u m \sum\limits_{i=1}^{n-1}a_i=sum i=1∑n−1ai=sum

如果可以,我们可以让前 n − 1 n-1 n−1个数都减成 0 0 0,然后 a n a_n an一直加到 a n + s u m a_n+sum an+sum

这样字典序最小

但是现在最多操作 k k k次,所以我们最多让前 i − 1 i-1 i−1个数减 m i n ( k , s u m ) min(k,sum) min(k,sum)次

从第一个数开始,能减就减即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int a[maxn];
int t,n,k;
int main()
{
cin >> t;
while( t-- )
{
cin >> n >> k;
int sum = 0;
for(int i=1;i<=n;i++) scanf("%d",&a[i] );
for(int i=1;i<n;i++) sum += a[i];
int now = min( sum,k ),temp = now;
for(int i=1;i<n;i++)
{
if( a[i]<now )
{
now -= a[i];
cout << 0 << " ";
}
else
{
cout << a[i]-now << " ";
now = 0;
}
}
cout << a[n]+temp << endl;
}
}

B. AGAGA XOOORRR(枚举,异或性质)

​LINK​

题意

给出长度 n n n的数组 a a a,问是否有可能把数组分成若干连续段,每一段的异或和相同。


设最后分成了连续的 w w w段,每一段的和是 x x x

①.如果 w w w为偶数,我们知道奇数个 x x x异或起来还是 x x x

那么我们可以把前 w − 1 w-1 w−1段看成一个段,异或还是 x x x

所以我们成功发现如果 w w w为偶数,必定存在 w = 2 w=2 w=2这个解

于是我们枚举端点,验证前一段异或和是否等于后一段

②.如果 w w w为奇数,我们知道奇数个 x x x异或起来还是 x x x

那么我们可以把前 w − 2 w-2 w−2段看成一个段,异或还是 x x x

所以我们成功发现如果 w w w为奇数,必定存在 w = 3 w=3 w=3这个解

那我们枚举 [ 1 , i ] [1,i] [1,i]作为第一段,然后在 [ i + 1 , n ] [i+1,n] [i+1,n]中枚举第二段和第三段的端点

验证这三段的异或和是否相等即可

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 3e5+10;
int a[maxn],pre[maxn];
int t,n,k;
signed main()
{
cin >> t;
while( t-- )
{
cin >> n;
for(int i=1;i<=n;i++) { cin >> a[i]; pre[i] = pre[i-1]^a[i]; }
int sum1 = 0,flag = 0;
for(int i=1;i<n;i++)
{
sum1 ^= a[i];
int sum2 = 0;
for(int j=i+1;j<=n;j++) sum2 ^= a[j];
if( sum1==sum2 ) flag = 1;//w=2

int q = 0,w = 0;
for(int j=i+1;j<n;j++)
{
q ^= a[j];
if( q==sum1 && q==( pre[n]^pre[j]) ) flag = 1;//w=3
}
}
if( flag ) cout << "YES\n";
else cout << "NO\n";
}
}

C. Baby Ehab Partitions Again(01背包+奇偶性质)

​LINK​

先计算 s u m = ∑ i = 1 n a i sum=\sum\limits_{i=1}^na_i sum=i=1∑nai

若 s u m sum sum为奇数,显然不能分成两个和相同的序列,输出零

若 s u m sum sum为偶数

我们先用 01 01 01背包判断一开始是否能划分为两个和相同的序列

如果不能,直接输出零。

如果能,则我们至少需要去掉一个数,而且有结论我们只需要去掉一个数

因为如果一个数组中存在奇数,那么我们去掉这个奇数 s u m sum sum就变成奇数了,那么就是好数组

如果不存在奇数也没关系,我们把数组整体除以二,之前能分成两个和相同的序列现在也同样能,所以和原数组等价

但是经过整体除以二后,会出现一些奇数,我们只需要去掉这些位置上的数就好了

如果仍然没有出现奇数,重复此步骤

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
const int base = 200000;
int t,n,k,sum,a[maxn],f[2000009];
void DP()
{
f[0] = 1;
for(int i=1;i<=n;i++)
for(int j=sum;j>=a[i];j--)
f[j] |= f[j-a[i]];
}
signed main()
{
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) sum += a[i];
if( sum&1 ) cout << 0 << endl;
else
{
DP();
if( !f[sum/2] ) cout << 0 << endl;
else
{
while( 1 )
{
for(int i=1;i<=n;i++)
{
if( a[i]&1 )
{
cout << 1 << endl << i << endl;
return 0;
}
}
for(int i=1;i<=n;i++) a[i]/=2;
}
}
}
}

D. Cut(区间互质+倍增思想)

​LINK​

分在一组的前提是,任意两个数都互质

考虑预处理一个 f [ i ] f[i] f[i]表示 [ i + 1 , f i − 1 ] [i+1,f_i-1] [i+1,fi−1]的所有数和 a i a_i ai互质,且 f i f_i fi最大

这样我们就能得到 s t [ i ] st[i] st[i]表示 [ i , s t i − 1 ] [i,st_i-1] [i,sti−1]能分成一组而且最长

s t [ i ] = min ⁡ j = i f i − 1 { f [ j ] } st[i]=\min\limits_{j=i}^{f_i-1}\{f[j]\} st[i]=j=iminfi−1{f[j]}

这个很好理解,虽然自己最远能到达 f i − 1 f_i-1 fi−1,但是还需要考虑其他数字的限制

但是暴力枚举复杂度爆炸

考虑对 a i a_i ai来说,存在因子 x 1 , x 2 . . . x k x_1,x_2...x_k x1,x2...xk

那么只需要满足组内的数没有这些因子就好了!!!

我们从 n n n往 1 1 1倒序枚举 a i a_i ai,分解质因子,并刷新每种因子出现的最早位置,记 n x t [ x ] nxt[x] nxt[x]表示质因子 x x x出现在索引 i i i后的最早位置

那么 f [ i ] = m i n ( f [ i ] , n x [ x j ] ) f[i]=min(f[i],nx[x_j]) f[i]=min(f[i],nx[xj]),其中 x j x_j xj是 a i a_i ai的一个因子

这样我们可以得到 f f f数组,套一个区间最小值的数据结构可以得到 s t st st数组

但是发现 s t [ i ] = min ⁡ j = i f i − 1 { f [ j ] } st[i]=\min\limits_{j=i}^{f_i-1}\{f[j]\} st[i]=j=iminfi−1{f[j]}

而 f f f数组是单调递增的,所以不用数据结构,只需要让 f i f_i fi去更新 f i − 1 f_{i-1} fi−1即可

然后我们对于询问 [ l , r ] [l,r] [l,r]

每次从 l l l跳到 s t [ l ] st[l] st[l],最后看看超过 r r r时跳了多少次即可。

但是会超时

没关系,倍增一下 s t st st数组即可,也就是预处理 s t [ i ] [ k ] st[i][k] st[i][k]

表示从 i i i跳 2 k 2^k 2k步最远能到达哪里

版本一

比赛时憨憨套了线段树求 f f f数组的区间最小值

#include <bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson ls,l,mid
#define rson rs,mid+1,r
const int maxn = 1e6+10;
int a[maxn],n,q;
vector<int>zhi[maxn];
void make()
{
for(int i=2;i<=100000;i++)
for(int j=i;j<=100000;j+=i )
zhi[j].push_back( i );
}
int nxt[maxn],mi[maxn],su[maxn];
void build(int rt,int l,int r)
{
if( l==r ){ su[rt] = mi[l]; return; }
build( lson ); build( rson );
su[rt] = min( su[ls],su[rs] );
}
int ask(int rt,int l,int r,int L,int R)
{
if( l>R || r<L ) return 1e9;
if( l>=L&&r<=R ) return su[rt];
return min( ask(lson,L,R),ask(rson,L,R) );
}
int st[maxn][22];
void init()
{
for(int i=1;i<=100000;i++) mi[i] = nxt[i] = n+1;
for(int i=n;i>=1;i--)
for( auto v:zhi[a[i]] )
{
mi[i] = min( mi[i],nxt[v] );
nxt[v] = i;
}
build(1,1,n);

memset( st,20,sizeof st );
for(int i=1;i<=n;i++) st[i][0] = ask(1,1,n,i,mi[i]-1 );
for(int i=1;i<=20;i++)
for(int j=1;j+(1<<i)-1<=n;j++)
{
int nx = st[j][i-1];
if( nx>n+1 ) st[j][i] = nx;
else st[j][i] = st[nx][i-1];
}
}
void solve(int l,int r)
{
int ans = 0;
for(int i=20;i>=0;i--)
if( st[l][i]<=r ) l = st[l][i],ans += (1<<i);
cout << ans+1 << endl;
}
signed main()
{
cin >> n >> q;
for(int i=1;i<=n;i++) cin >> a[i];
make(); init();
while( q-- )
{
int l,r; scanf( "%d%d",&l,&r);
solve(l,r);
}
}

最小值扫一遍就好了

版本二

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int a[maxn],n,q;
vector<int>zhi[maxn];
void make()
{
for(int i=2;i<=100000;i++)
for(int j=i;j<=100000;j+=i )
zhi[j].push_back( i );
}
int nxt[maxn],mi[maxn];
int st[maxn][22];
void init()
{
for(int i=1;i<=100000;i++) mi[i] = nxt[i] = n+1;
for(int i=n;i>=1;i--)
{
for( auto v:zhi[a[i]] )
{
mi[i] = min( mi[i],nxt[v] );
nxt[v] = i;
}
mi[i-1] = mi[i];
}
memset( st,20,sizeof st );
for(int i=1;i<=n;i++) st[i][0] = mi[i];
for(int i=1;i<=20;i++)
for(int j=1;j+(1<<i)-1<=n;j++)
{
int nx = st[j][i-1];
if( nx>n+1 ) st[j][i] = nx;
else st[j][i] = st[nx][i-1];
}
}
void solve(int l,int r)
{
int ans = 0;
for(int i=20;i>=0;i--)
if( st[l][i]<=r ) l = st[l][i],ans += (1<<i);
cout << ans+1 << endl;
}
signed main()
{
cin >> n >> q;
for(int i=1;i<=n;i++) cin >> a[i];
make(); init();
while( q-- )
{
int l,r; scanf( "%d%d",&l,&r);
solve(l,r);
}
}