Good Bye 2022: 2023 is NEAR
Preface
2022的Last Round了,结果CD全卡住,反复横跳后最后才堪堪看出C的问题
D的话其实我刚开始是准备写并查集的,但后来nt了不知道为什么想到边双去了写Tarjan去了
唉只能说是实力不济了,不过索性没怎么掉分(-3pts不算掉)
A. Koxia and Whiteboards
SB题,按题意模拟即可
话说我家网真辣鸡,还不如学校的办电话卡送的宽带好用,开个题目都要1分多钟
#include<cstdio>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
int t,n,m,x; priority_queue < int,vector <int>,greater<int> > hp;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",&x),hp.push(x);
for (i=1;i<=m;++i) scanf("%d",&x),hp.pop(),hp.push(x);
long long ans=0; while (!hp.empty()) ans+=hp.top(),hp.pop();
printf("%lld\n",ans);
}
return 0;
}
B. Koxia and Permutation
瞎JB构造的构造题
不难发现答案的下界是\(n+1\),考虑这样一种构造方案:
每次取出一段长度为\(k\)的段,第一段的两端分别放上\(n,1\),第二段的两端分别放上\(n-1,2\),依此类推
然后剩下的数随便填即可,手玩一下不难发现这样一定是正确的
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,k,a[N]; bool vis[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) a[i]=vis[i]=0;
int tp=n; for (i=1;i+k-1<=n;i+=k) vis[a[i]=tp]=1,vis[a[i+k-1]=n+1-tp]=1,--tp;
for (tp=i=1;i<=n;++i) if (!a[i]) { while (vis[tp]) ++tp; vis[a[i]=tp]=1; }
for (i=1;i<=n;++i) printf("%d%c",a[i]," \n"[i==n]);
}
return 0;
}
C. Koxia and Number Theory
连续naive了好几次,我可以说是纯纯的大彩笔了
首先一个显然的结论,存在两个相同的数时一定时无解的
然后再多加思索,很容易想到当奇数和偶数都有两个及以上时一定不合法,因为此时不管\(x\)取什么值总有两个及以上的变化后的数是偶数
结果挂了之后一时想不出什么毛病,只好先去写D,结果一段时间后D也挂了看不出问题又滚回来写C
结果此时一眼看出来我们不仅要对\(2\)这个数进行验证,考虑对于某个质数\(p\)
我们考虑求出每个数对\(p\)取余的结果的个数,若余数为\(0,1,2,\cdots,p-1\)的个数都大于等于\(2\),则这些数也是无解的
考虑抽屉原理,由于一共只有最多\(100\)个数,因此我们只要考虑小于\(50\)的所有质数即可
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,prm[15]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47};
int t,n,c[50]; long long a[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
bool flag=1; for (i=1;i<=n;++i) for (j=i+1;j<=n;++j)
if (a[i]==a[j]) flag=0; if (!flag) { puts("NO"); continue; }
for (i=0;i<15&&flag;++i)
{
int p=prm[i]; for (j=0;j<p;++j) c[j]=0;
for (j=1;j<=n;++j) ++c[a[j]%p];
bool sign=0; for (j=0;j<p&&!sign;++j) if (c[j]<2) sign=1;
if (!sign) flag=0;
}
puts(flag?"YES":"NO");
}
return 0;
}
D. Koxia and Game
我现在就好奇我当时为什么回去找桥,就是一个判环的并查集讨论问题的说
首先一个显然的结论,对于每一个\(i\),我们必须使得在\(a_i,b_i,c_i\)中存在某个出现了两次及以上的数\(x\),这样才能保证能在这一位上取到\(x\)
然后我们进行一些分析,假设我们已经确定一个位置\(i\)上的数取值为\(x\)了,那么其他所有的含有\(x\)这个数的位置的取值也确定了下来
因此我们可以把每对\(a_i\)和\(b_i\)进行连边,表示当其中一个确定时另一个就都确定了
然后考虑一些情况:
- 若\(a_i=b_i\),显然\(c_i\)可以任意取值
- 若存在一个联通块构成一棵树,则无解(总会存在某对未确定的\(u,v\)在同个位置上)
- 若存在一个联通块,其中包括环(不含自环),则其对答案的贡献为\(\times2\)(很好理解,环上的点有两种选法,其它点会跟着这个环的选法确定)
- 若存在一个联通块,其中包括环,但这个环是自环,则其它部分的选法唯一,答案不变
- 若存在一个联通块中存在两个及以上的环,则无解
因此用并查集维护联通关系的同时在顺带维护下每个联通块的环的类型即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=998244353;
int t,n,a[N],b[N],Fa[N],type[N]; bool sfcir[N];
//type=0 no circle; type=1 normal circle; type=2 self-loop with a link
inline int getfa(CI x)
{
return x!=Fa[x]?Fa[x]=getfa(Fa[x]):x;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) Fa[i]=i,type[i]=sfcir[i]=0;
for (i=1;i<=n;++i) scanf("%d",&a[i]); for (i=1;i<=n;++i) scanf("%d",&b[i]);
int ans=1; for (i=1;i<=n;++i) if (a[i]==b[i])
{
if (sfcir[a[i]]) ans=0; else sfcir[a[i]]=1,ans=1LL*ans*n%mod;
} else
{
int fa=getfa(a[i]),fb=getfa(b[i]);
if (fa!=fb)
{
if (type[fa]&&type[fb]) ans=0; else
{
if (!type[fa]) swap(fa,fb); Fa[fb]=fa;
}
} else
{
if (!type[fa]) type[fa]=1; else ans=0;
}
}
for (i=1;i<=n;++i) if (a[i]==b[i]) type[getfa(a[i])]=2;
for (i=1;i<=n;++i) if (!sfcir[i]&&getfa(i)==i)
{
if (!type[i]) ans=0; else if (type[i]==1) ans=2LL*ans%mod;
}
printf("%d\n",ans);
}
return 0;
}
E. Koxia and Tree
补题
首先考虑如果没有移动操作怎么处理,显然此时我们可以算出所有点对间的距离之和,再除以\(\frac{k(k-1)}{2}\)即可
这是个经典的问题,我们分别考虑每一条边\((u,v)\)(设\(v\)是\(u\)的儿子),设点\(v\)子树内蝴蝶的数目为\(sz_v\),显然这条边的贡献就是\(sz_v\times (k-sz_v)\)
接下来考虑当蝴蝶移动时\(sz\)的变化,但由于移动只会发生一次并且题目中有每条边最多被经过一次的限制
因此我们发现\(sz_i\)的变化量是\(\le 1\)的,这就意味着我们可以直接大力讨论\(sz_u\)的变化情况,找出这种情况的概率即可
我们考虑记\(p_i\)表示某个时刻点\(i\)上有蝴蝶的概率,那么对于边\((u,v)\),则有
- \(sz_v\)加\(1\)的概率为\(p1=p_u\times(1-p_v)\)
- \(sz_v\)减\(1\)的概率为\(p2=p_v\times (1-p_u)\)
- \(sz_v\)不变的概率为\(p3=1-p1-p2\)
因此我们就可以很轻松地维护答案了,同时当我们处理完一条边\((u,v)\)后,显然会有\(p_u=p_v=\frac{p_u+p_v}{2}\)
直接按边的顺序模拟即可,复杂度\(O(n)\)
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005,mod=998244353,inv2=(mod+1)>>1;
int n,k,a[N],x[N],y[N],p[N],sz[N],dep[N],t,ans; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
dep[now]=dep[fa]+1; for (int to:v[now]) if (to!=fa) DFS(to,now),sz[now]+=sz[to];
}
inline int sum(CI x,CI y)
{
return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
return x-y<0?x-y+mod:x-y;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d%d",&n,&k),i=1;i<=k;++i) scanf("%d",&t),sz[t]=p[t]=1;
for (i=1;i<n;++i) scanf("%d%d",&x[i],&y[i]),v[x[i]].push_back(y[i]),v[y[i]].push_back(x[i]);
for (DFS(),i=1;i<n;++i)
{
int u=x[i],v=y[i]; if (dep[u]>dep[v]) swap(u,v);
int p1=1LL*p[u]*sub(1,p[v])%mod*inv2%mod; // prob that sz[v]+=1
int p2=1LL*p[v]*sub(1,p[u])%mod*inv2%mod; // prob that sz[v]-=1
int p3=sub(1,sum(p1,p2)); //prob that sz[v] unchanged
ans=sum(ans,1LL*sum(sz[v],1)*sub(k-sz[v],1)%mod*p1%mod);
ans=sum(ans,1LL*sub(sz[v],1)*sum(k-sz[v],1)%mod*p2%mod);
ans=sum(ans,1LL*sz[v]*(k-sz[v])%mod*p3%mod);
p[u]=p[v]=1LL*sum(p[u],p[v])*inv2%mod;
}
return printf("%d",1LL*ans*quick_pow(1LL*k*(k-1)/2LL%mod)%mod),0;
}
F. Koxia and Sequence
补题,神仙题
首先注意看清题目,要求的是所有元素的异或和的异或和,刚开始看出异或和的和了感觉完全没思路
首先不难发现\(n\)为偶数时答案为\(0\),因为如果我们设\(T(x,y)\)表示\(a_x=y\)时合法的序列个数,则显然有\(T(1,i)=T(2,i)=\cdots=T(n,i)\)
因此此时每个数\(i\)的贡献都是\(0\)(偶数个\(i\)异或在一起),答案显然就是\(0\)
因此考虑\(n\)为奇数的情形,我们不妨枚举\(a_1=t\),求出此时合法的序列个数 ,若为奇数则\(t\)可以造成贡献
再进一步,我们可以直接考虑二进制下每一位,不妨枚举\(a_1\)的第\(i\)位为\(1\)时合法的序列个数,若为奇数则\(2^i\)可以造成贡献
接下来考虑那两个限制,首先考虑或和为\(y\),这个刚好相等时很难计算的,因此不妨考虑容斥
定义\(f_s\)表示所有元素的或和为\(s\)的子集,且所有元素和为\(x\),且\(a_1\)的第\(i\)位为\(1\)的合法序列个数
那么或和刚好为\(y\)的合法序列数量为\(\sum_{s\subseteq y} f_s\cdot(-1)^{|y-s|}\)
考虑到我们只关系奇偶性,因此设\(g_s=f_s\bmod 2\),则我们只要知道\(\bigoplus_{s\subseteq y} g_s\)的值即可
先不考虑\(a_1\)的第\(i\)位为\(1\)的条件,我们知道\(g_s=(\sum_{t_1+t_2+\cdots+t_n=x} \prod_{i=1}^n [t_i\subseteq s])\bmod 2\)
倒着利用卢卡斯定理的经典推论,上面的式子可以写成\(g_s=(\sum_{t_1+t_2+\cdots+t_n=x} \prod_{i=1}^n C_s^{t_i})\bmod 2\)
再观察这个式子的形式,不难发现它满足Vandermonde恒等式,等价于\(C_{ns}^x\bmod 2\)
那么最后再考虑\(a_1\)的第\(i\)位为\(1\)的条件,我们只要看\(C_{ns-2^i}^{x-2^i}\bmod 2\)的值即可
由于要枚举\(i\)和\(s\),总复杂度为\(O(y\log y)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
long long n,x,y,ans;
int main()
{
if (scanf("%lld%lld%lld",&n,&x,&y),n%2==0) return puts("0"),0;
RI i,S; for (i=0;i<20;++i)
{
long long ret=0; for (S=y;S;S=(S-1)&y) if (S&(1LL<<i))
{
long long A=S*n-(1LL<<i),B=x-(1LL<<i);
if (A<0||B<0) continue; if ((A&B)==B) ret^=1LL;
}
if (ret) ans|=(1LL<<i);
}
return printf("%lld",ans),0;
}
Postscript
后面两题太仙了跑路为妙