MX 暑假集训 7.13
哦哦原来刮台风还能停课的,在宿舍度过了一天。
代码有些是赛时的,写得比较丑。
A. farmpiggie and Subset Sum
题意
一开始你有一个计数器 \(c=0\),对于一个排列 \(p\) 的每一项 \(p_i\),你可以选择 \(c\leftarrow c+i\times p_i\),\(c\leftarrow c-i\times p_i\),或什么都不做。
你需要构造排列 \(p\),使得无论你怎么操作最后得到的 \(c\) 一定不为 \(1\)。
\(2\le n\le 50\),且 \(n\) 为偶数。
solution
\(i\) 为奇数时在 \(p_i\) 上放偶数,否则放奇数,最后 \(c\) 一定为偶数。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
inline void solve() {
int n;read(n);
f(i,1,n) {
if (i&1) printf("%d ",i+1);
else printf("%d ",i-1);
}
putchar(10);
}
int main() {
int T;
read(T);
while (T--) solve();
return 0;
}
B. ezraft and Array
题意
你需要构造一个长度为 \(n\) 且元素互不相同且值域为 \([1,10^{17}]\) 的序列 \(a\),使得 \(\sum\limits_{i=1}^{n} a_i\) 被每个 \(a_i\) 整除。
\(1\le n\le 50\)。
solution
太难了,我真不会做 CF 的 B 题吧。
考虑构造和为 \(2^k\),其中 \(k\) 为某个正整数,但是发现凑不出来。
于是考虑构造和为 \(3\times 2^k\),前四项放 \(1,2,3,6\),后面第 \(i\) 项放 \(3\times 2^{i-3}\) 即可。
\(n=2\) 时无解。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
inline void solve() {
int n;read(n);
if (n==1) return puts("1"),void();
if (n==2) return puts("-1"),void();
if (n==3) return puts("1 2 3"),void();
if (n==4) return puts("1 2 3 6"),void();
ll now=12;
printf("1 2 3 6 ");
f(i,5,n) {
printf("%lld ",now);
now*=2;
}
putchar(10);
}
int main() {
int T;
read(T);
while (T--) solve();
return 0;
}
C. 0mar and Alternating Sums
题意
对于一个长度为 \(k\) 的数组 \(b\),定义其交替和为 \(\sum\limits_{i=1}^{k} {(-1)^{i+1}b_i}\)。
给定一个长度为 \(n\),单调不减的数组 \(a\),每个元素要么为 \(-1\),要么为一个正整数,求有多少个子序列满足其交替和为 \(0\)。
\(1\le n\le 2\times 10^5\)。
solution
赛时一眼秒了,比 B 简单。
对于 \(-1\) 的部分,其交替和一定为 \(0\) 或 \(-1\)。
若交替和为 \(0\),那么由于单调不减的性质,正整数每种值都出现偶数次。
否则,必然会出现一对值 \(x,y\) 满足 \(x+1=y\),\(x,y\) 均选奇数次,非 \(x,y\) 的值必须选偶数次,这样贡献为 \(1\)。
对于一个长度选奇数或偶数个数可以 dp 快速求出。
时间复杂度 \(O(n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline void read(int &x) {
x=0;int f=1;
char ch=getchar();
while (ch<48) {if(ch=='-')f=-1;ch=getchar();}
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}
int n;
int a[N],dp[N][2];
struct Node{int len,val;};
ll ed[N];
inline void solve() {
read(n);
f(i,1,n) read(a[i]);
dp[0][0]=1;
f(i,1,n) {
add(dp[i][0]=dp[i-1][0],dp[i-1][1]);
add(dp[i][1]=dp[i-1][1],dp[i-1][0]);
}
int len=0,num=0;
f(i,1,n) len+=(a[i]==-1);
if (len==n) return printf("%d\n",dp[n][0]),void();
ll ans=0,mul=1;
vector<Node> tmp;
f(i,1,n) {
if (a[i]==-1) continue;
if (a[i]==a[i-1]) {
num++;
continue;
}
if (num) tmp.push_back({num,a[i-1]});
num=1;
}
tmp.push_back({num,a[n]});
int m=tmp.size()-1;
ed[m+1]=1;
F(i,m,0) ed[i]=ed[i+1]*dp[tmp[i].len][0]%mod;
ans+=ed[0]*dp[len][0]%mod;
// printf("ans1=%lld\n",ans);
f(i,1,m) {
if (i>=2) mul=mul*dp[tmp[i-2].len][0]%mod;
if (tmp[i].val==tmp[i-1].val+1)
ans+=ed[i+1]*dp[tmp[i-1].len][1]%mod*dp[tmp[i].len][1]%mod*mul%mod*dp[len][1]%mod;
}
printf("%lld\n",ans%mod);
}
int main() {
int T;
read(T);
while (T--) solve();
return 0;
}
D - diss_quack and Array Game
题意
小 A 和小 B 在进行以下游戏。
初始时,有一个长度为 \(n\) 的数组 \(a\) 和一个计数器 \(c=0\)。
游戏开始时,小 A 可以重复操作任意次,使 \(a_i\leftarrow a_i+1\),同时 \(c\leftarrow c+1\)。
然后小 A 和小 B 轮流进行以下操作,直到数组 \(a\) 为空,小 B 先手:
-
若当前为小 B 操作,则他可以选择一对 \(i,j\),交换 \(a_i\) 和 \(a_j\),也可以什么都不做;
-
若当前为小 A 操作,若 \(a_1\) 为偶数,则找到最大的 \(j\),满足对于所有 \(1\le i\le j\),\(a_i\) 均为偶数,对所有 \(1\le i\le j\),\(a_i\leftarrow \frac{a_i}{2}\),若 \(a_1\) 为奇数,则 \(a_1\leftarrow a_1-1\),若操作后 \(a_1\) 为 \(0\),则移除 \(a_1\),并对剩下的所有数重新编号,无论进行哪种操作,都会使 \(c\leftarrow c+1\)。
小 A 想要最小化 \(c\) 最后的值,而小 B 想要最大化它,若双方都采取最优策略,最后 \(c\) 的值是多少?
\(1\le n\le 10^5,1\le a_i\le 10^5\)。
solution
红温了怎么调不过啊。
不讨论 \(n=1\) 的情况。
游戏开始时,若存在奇数,那么小 B 一定会将它移到数组第二个位置,如果第一个数变成 \(0\) 了,就再拿一个数放到第一个位置,直到数组清空,显然这样是最优的。
当全是偶数时,小 B 什么也做不了,只能等到出现奇数为止。
将一个数 \(x\) 放到第一个位置后,它会经历 $\operatorname{popcount}(x) + \left\lfloor \log_{2}{x} \right\rfloor $ 次变成 \(0\),将这个次数叫做直接代价。
小 A 为了最小化操作次数,他可以进行若干次加 \(1\) 操作一个数的直接代价变小,根据值域,原先的直接代价不会超过 \(60\),所以可以枚举进行几次加 \(1\) 操作能使得操作带来的代价加上变成的数的直接代价比原先的代价要小。
而总代价还与出现奇数的时候有关,所以可以将所有的数变成 \(2^k\) 的倍数,再计算代价,枚举 \(k\) 取最小值即可。
变成 \(2^k\) 的倍数后,可以继续将其加 \(1\) 使得直接代价更小,容易证明得到的数一定也是 \(2^k\) 的倍数。
时间复杂度 \(O(n\log V)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n;
int a[N];
inline ll W(ll x) {
ll ret=0;
while (x) {
if (x&1) ret++;
if (x==1) break;
x/=2;ret++;
}
return ret;
}
int w[N],to[N];
inline void init(int n) {
f(i,1,n) w[i]=W(i);
f(i,1,n) {
int val=w[i];to[i]=i;
f(j,1,80) {
if (w[i+j]+j<val) {
val=w[i+j]+j;
to[i]=i+j;
}
}
}
}
inline void solve() {
read(n);
f(i,1,n) read(a[i]);
ll ans=1e18;
for (ll len=1,kk=0;kk<=18;len<<=1,kk++) {
ll res=0;
f(i,1,n) {
ll now=to[len*(a[i]/len+(a[i]%len!=0))];
res+=w[now]+(now-a[i]);
}
res-=kk*(n-1);
ans=min(ans,res);
}
printf("%lld\n",ans);
}
signed main() {
init(N-1);
int T;read(T);
while (T--) solve();
return 0;
}
E. lce4113 and Security Game
题意
有一个隐藏的布尔变量 \(b\),一个隐藏的整数 \(v\),和一个隐藏的运算类型 \(\operatorname{ty}\)。
其中 \(ty\) 为按位或和按位与其中的一种。
你需要给出一个数 \(x\),交互库会返回 \(v \operatorname{ty} x\),然后你可以给出两个数 \(m_0,m_1\),交互库会返回 \(v \oplus m_b\) 的值,其中 \(\oplus\) 表示按位异或。
你需要在上述操作后,确定 \(b\) 的值,交互库是半自适应的,\(v\) 会根据你给出的 \(x\) 进行构造,但是在你给出 \(x\) 后所有隐藏变量是固定的。
\(1\le T\le 10^5,0\le b\le 1,0\le v< 2^{30}\),你需要保证你给出的所有数都在 \([0,2^{30})\) 范围内。
solution
本场神题。可惜我没来得及细想。
令第一次返回的数为 \(r_1\),第二次返回的数为 \(r_2\)。
发现如果确定了 \(v\) 的某一位,我们可以构造 \(m_0\) 和 \(m_1\) 在这一位上不同,直接确定 \(b\)。
若 \(r_1\ne x\),那么可以直接确定 \(\operatorname{ty}\),并确定 \(v\) 的某一位,从而解决问题。
所以以下讨论均基于 \(r_1=x\)。
我们先考虑返回的状态数和交互库可能的状态数的关系。
打了一个类似于真值表的东西,我们可以发现返回的状态数一定比交互库可能的状态数少 \(2\),所以这题没有确定性做法,只能随机化。
Code
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
int vis[N];
int main() {
int X=15; //00001111
int A=150; //10010110
int B=105; //01101001
f(i,0,N-1) vis[i]=-1;
puts("0->1,Xor=A");
f(i,0,15) {
int val=((i<<4)|X);
printf("val=%d Xor=%d\n",val,val^A);
vis[val^A]=val;
}
puts("1->0,Xor=A");
f(i,0,15) {
int val=(i&X);
printf("val=%d Xor=%d\n",val,val^A);
vis[val^A]=val;
}
puts("0->1,Xor=B");
f(i,0,15) {
int val=((i<<4)|X);
printf("val=%d Xor=%d\n",val,val^B);
if (vis[val^B]!=-1) {
puts("Error");
printf("valA=%d\n",vis[val^B]);
}
}
puts("1->0,Xor=B");
f(i,0,15) {
int val=(i&X);
printf("val=%d Xor=%d\n",val,val^B);
if (vis[val^B]!=-1) {
puts("Error");
printf("valA=%d\n",vis[val^B]);
}
}
return 0;
}
考虑构造 \(x=1\)。
若 \(\operatorname{ty}\) 为与运算,则 \(v\) 的第 \(0\) 位一定为 \(1\)。
若 \(\operatorname{ty}\) 为或运算,则 \(v\) 的第 \(0\) 位为 \(0\) 或 \(1\),且其他位均为 \(0\)。
我们发现,若 \(v\ne 0\),则可以确定 \(v\) 第 \(0\) 位为 \(1\),从而确定 \(b\)。
于是我们考虑构造 \(m_0\) 的第 \(0\) 位是 \(1\),其他位完全随机,\(m_1\) 的第 \(0\) 位是 \(0\),其他位完全随机。
若返回的 \(r_2=m_0\),则判定 \(v=0,b=0\),若返回的 \(r_2=m_1\),则判定 \(v=0,b=1\)。
否则判定 \(v\) 的第 \(0\) 位为 \(1\),并通过第 \(0\) 位确定 \(b\) 的值。
这样判定 \(v=0\) 的情况,出现错误的概率为 \(\frac{1}{2^{29}}\)。
得到正确答案的概率为 \(1-\frac{1}{2^{29}}\),足以通过此题。
Code
#include<cstdio>
#include<algorithm>
#include<random>
#include<time.h>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
const int V=(1<<30)-1,X=1;
mt19937 Ff(time(0));
inline int rnd(int L,int R) {
ll k=Ff();
return L+k%(R-L+1);
}
inline void solve() {
printf("%d\n",X);
fflush(stdout);
int v;read(v);
if (v!=X) {
f(i,0,29) {
int d0=((X>>i)&1),d1=((v>>i)&1);
if (d0==d1) continue;
printf("%d %d\n",0,(1<<i));
fflush(stdout);
int x;read(x);
int d=((x>>i)&1);
if (d0<d1) {
if (!d) puts("1");
else puts("0");
}
else {
if (!d) puts("0");
else puts("1");
}
fflush(stdout);
return;
}
}
int m0=rnd(0,V),m1=rnd(0,V),ret=0,ans=-1;
if (!(m0&1)) m0^=1;
if (m1&1) m1^=1;
printf("%d %d\n",m0,m1);
fflush(stdout);
read(ret);
if (ret==m0) ans=0;
else if (ret==m1) ans=1;
else ans=(ret&1);
printf("%d\n",ans);
fflush(stdout);
}
int main() {
int T;
read(T);
while (T--) solve();
return 0;
}
Vika and Wiki
题意
给定长度为 \(n\) 的数组 \(a\),保证 \(n\) 为 \(2\) 的幂,每次操作会同时将 \(a_i\leftarrow a_i \oplus a_{(i+1)\mod n}\),其中 \(\oplus\) 表示异或操作,下标从 \(0\) 开始,求至少多少次操作后 \(a\) 数组内的元素全部变成 \(0\),若无论多少次操作都无法实现,则输出 \(-1\)。
\(1\le n\le 2^{20}\)。
solution
首先如果所有的数都变成 \(0\),那么继续进行若干次操作仍然为 \(0\)。
对于一个位置 \(i\),构造一个序列,异或上的位置为 \(1\),否则为 \(0\),那么每次操作就相当于将这个序列循环右移一位再与原序列进行异或,例如 \(n=8\) 的情况得到的序列如下。
0 10000000
1 11000000
2 10100000
3 11110000
4 10001000
5 11001100
6 10101010
7 11111111
我们发现 \(n\) 次操作后所有数一定会全变成 \(0\),即不存在无解的情况,还注意到,若操作次数为 \(2^k-1\),那么一个数就会变成它后面 \(2^k\) 个数的异或。
考虑找到最大的 \(k\) 使得进行 \(2^k-1\) 操作后还有数不变成 \(0\),然后加上次数,将 \(a\) 序列进行异或后递归解决即可,利用前缀和可以做到 \(O(n)-O(1)\) 得到一段区间的异或。
时间复杂度 \(O(n\log^2 n)\),可以用二分优化,也可以利用许多性质做到更优的复杂度,不过只观察到这里就可以通过本题了。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=(1<<20)+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,ans;
int a[N],b[N],s[N];
inline int query(int l,int r) {
if (r-l>n) return 0;
if (r>n) return s[n]^(l?s[l-1]:0)^s[r%(n+1)];
return s[r]^(l?s[l-1]:0);
}
inline void solve() {
int flag=1;
f(i,0,n) flag&=(!a[i]);
if (flag) return;
s[0]=a[0];
f(i,1,n) s[i]=(s[i-1]^a[i]);
for (int len=2;;len<<=1) {
int flag=1;
f(i,0,n) if (query(i,i+len-1)) {flag=0;break;};
if (flag) {
if (len==2) return ans++,void();
len>>=1;
ans+=len-1;
f(i,0,n) a[i]=query(i,i+len-1);
return solve();
}
}
}
int main() {
read(n);n--;
f(i,0,n) read(a[i]);
solve();
printf("%d\n",ans);
return 0;
}
Expected Destruction
题意
给定一个包含 \(n\) 个正整数的集合 \(S\),值域在 \([1,m]\) 内。
每一秒都会进行以下操作:在 \(S\) 中随机挑选一个数 \(x\),将 \(x\) 从 \(S\) 移出,若 \(x\ne m\) 且 \(x+1\notin S\) 则将 \(x+1\) 加入 \(S\)。
求期望多少秒后集合为空,答案对 \(10^9+7\) 取模。
\(1\le n\le m\le 500\)。
solution
好难的题,不会数学。
我们可以看作 \(n\) 个物块在数轴上向右移动,若碰上了右边的物块则消失,我们需要求出物块都消失时所有物块的期望移动次数之和。
由期望的线性性可得,所有物块都消失的期望次数和等于每个物块消失期望次数的和,而第 \(i\) 个物块只可能撞上第 \(i+1\) 个物块,或带有第 \(i+1\) 个物块的“灵魂”的物块,我们考虑令 \(dp_{i,j}\) 为一个在位置 \(i\) 的物块撞上位置在 \(j\) 的物块,\(i\) 期望移动次数,若位置在 \(j\) 上的物块撞到了后一个物块,那么相当于在 \(j\) 上的物块将“灵魂”交给了下一个物块,答案没有影响。
若还剩 \(p\) 个物块,则 \(dp_{i,j}=\frac{p-2}{p}dp_{i,j}+\frac{1}{p}(dp_{i+1,j}+1)+\frac{1}{p}dp_{i,j+1}\),移项消去 \(p\) 可得 \(dp_{i,j}=\frac{1}{2}(dp_{i+1,j}+1)+\frac{1}{2}dp_{i,j+1}\)。
答案即为 \(\sum\limits_{i=1}^{n}dp_{a_i,a_{i+1}}\),其中 \(a_{n+1}=m+1\),时间复杂度 \(O(m^2)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=500+10;
constexpr int inf=1e9+10;
constexpr int mod=1e9+7,inv2=(mod+1)>>1;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,m;
int a[N],dp[N][N];
int main() {
read(n);read(m);
f(i,1,n) read(a[i]);
a[n+1]=m+1;
F(i,m,1) F(j,m+1,i+1) {
if (j==m+1) add(dp[i][j]=dp[i+1][j],1);
else dp[i][j]=(1ll*inv2*(dp[i+1][j]+1)+1ll*inv2*dp[i][j+1])%mod;
}
int ans=0;
f(i,1,n) add(ans,dp[a[i]][a[i+1]]);
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号