Codeforces Round #814 (Div. 2+Div. 1)
Preface
关于军训……它死了
第一次感觉到疫情的好处,就是不知道训了一半给不给学分,要不要补训
一直打隔膜颓废也不是办法,因此来写写题(才不是因为路由器没到舍不得用流量更新永劫无间呢)
A. Chip Game
看到这种可以用SG函数的题目先暴力艹上一发,发现当且仅当\(n,m\)奇偶性相同时为Tonya
,反之
后来一想证明也很eazy,因为总的移动格子数是\(n+m-2\),每次的变化值都是奇数,因此等价于看\(n+m\)的奇偶性
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
//const int N=105;
//int SG[N][N]; bool vis[N];
int t,n,m;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
/*RI i,j,k; for (i=1;i<=20;++i) for (j=1;j<=20;++j)
{
memset(vis,0,sizeof(vis));
for (k=1;k<i;k+=2) vis[SG[i-k][j]]=1;
for (k=1;k<j;k+=2) vis[SG[i][j-k]]=1;
for (k=0;vis[k];++k); SG[i][j]=k;
}
for (i=1;i<=20;++i) for (j=1;j<=20;++j)
printf("%d %d: %d\n",i,j,SG[i][j]);*/
for (scanf("%d",&t);t;--t) scanf("%d%d",&n,&m),puts((n&1)==(m&1)?"Tonya":"Burenka");
return 0;
}
B. Mathematical Circus
首先把\(k\)对\(4\)取模,不难证明当\(k=0\)时是无解的
接下来考虑\(k=1/3\)的情况,容易发现只要分成一个奇数+一个偶数的情况即可
对于\(k=2\)的情况,我们每次考虑相邻的\(4\)个数\(\{4n+1,4n+2,4n+3,4n+4\}\),显然可以划分成\((4n+1,4n+4),(4n+2,4n+3)\)的形式,对于可能剩下的两个数\(\{4n+1,4n+2\}\)也分成\((4n+2,4n+1)\)即可
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int t,n,k;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d",&t);t;--t)
{
scanf("%d%d",&n,&k); k%=4; if (!k) { puts("NO"); continue; }
if (puts("YES"),k==2)
{
for (i=1;i+3<=n;i+=4) printf("%d %d\n%d %d\n",i,i+3,i+1,i+2);
if (n%4==2) printf("%d %d\n",n,n-1);
} else
{
for (i=1;i<=n;i+=2) printf("%d %d\n",i,i+1);
}
}
return 0;
}
C. Fighting Tournament
不难发现一个关键性质,当最大的那个人来到最前面后其他人的胜利次数就不会发生变化了,这个人会一直赢下去
同时我们发现我们可以离线询问,这样就可以直接模拟比赛过程了,可以用deque
,非常方便
#include<cstdio>
#include<algorithm>
#include<deque>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct ques
{
int id,p,t;
friend inline bool operator < (const ques& A,const ques& B)
{
return A.t<B.t;
}
}q[N]; int t,n,m,a[N],c[N],pos,num[N],ans[N]; deque <int> dq;
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",&a[i]),c[i]=0,dq.push_back(a[i]),num[a[i]]=i;
for (i=1;i<=m;++i) scanf("%d%d",&q[i].p,&q[i].t),q[i].id=i;
RI now=1; for (sort(q+1,q+m+1),i=1;;++i)
{
int x=dq.front(); dq.pop_front(); int y=dq.front(); dq.pop_front();
if (x<y) swap(x,y); ++c[num[x]]; dq.push_front(x); dq.push_back(y);
while (now<=m&&q[now].t==i) ans[q[now].id]=c[q[now].p],++now;
if (x==n) { pos=i; break; }
}
while (now<=m) ans[q[now].id]=c[q[now].p]+(q[now].p==num[n]?q[now].t-pos:0),++now;
for (i=1;i<=m;++i) printf("%d\n",ans[i]); dq.clear();
}
return 0;
}
D. Burenka and Traditions
这里不单独讲easy version了,直接讲正解
首先不难发现我们只需要考虑长度为\(1/2\)的操作即可,因为所有更大的区间操作都可以分解,并且这样更灵活
设\(pre_i=\bigoplus_\limits{1\le j\le i} a_j\),我们考虑以下一种操作方式:对于所有的\([i,i+1](1\le i<n)\)进行操作,每次异或上\(pre_i\)
不难发现经过\(i\)次操作后,\(a_j=0,j\in [1,i]\and a_{i+1}=pre_{i+1}\),最后我们对剩余的\(a_n\)单独处理即可
然后我们发现如果操作的过程中出现了某个\(pre_i=0\),那么到这个位置时会出现无需操作而\(a_{i+1}\)已经等于\(0\)的情形,相当于减少了一次操作
显然要想让答案更小,就要使得这种情况次数更多
普遍地,若\(pre_j=pre_i(j<i)\),那么\((j,i]\)必然可以节省一次操作,只需要把原来的正序操作在这个区间内逆序即可(建议手玩模拟一下)
一个naive的想法是记录\(f_i\)表示前\(i\)个数的最小操作次数,可以从所有\(pre_j=pre_i\)的位置转移过来
但是我们很容易发现最优的转移位置一定是最近的那个,因此直接用set
维护所有的\(pre_j\),如果找到相同的就清空即可
#include<cstdio>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
int t,n,x,pre,ans; set <int> s;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n); ans=pre=0; s.clear(); s.insert(0); for (RI i=1;i<=n;++i)
{
scanf("%d",&x); pre^=x; if (s.count(pre)) s.clear(); else ++ans; s.insert(pre);
}
printf("%d\n",ans);
}
return 0;
}
E. Fibonacci Strings
饭前随手一写就过了2333
首先不难发现\(\sum_{i=1}^k c_i\)必须是斐波那契数列的前缀和,这样我们就能确定需要分成多少项了
一眼从大到小考虑,每次找出剩下的\(c_i\)中非上次操作且最大的数,然后将其减去当前的斐波那契数
考虑证明这个做法的正确性,因为若当前的这个数不拿走\(fib_k\),那么它接下来最多拿走\(fib_{k-1}+fib_{k-3}+fib_{k-5}+\cdots\)
利用归纳法不难证明\(fib_k\ge fib_{k-1}+fib_{k-3}+fib_{k-5}+\cdots\),因此这样一定是最优的
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,k,c[N],cnt,fib[10000],lst; long long pfx[10000],sum;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (fib[0]=fib[1]=1,i=2;;++i)
if ((fib[i]=fib[i-1]+fib[i-2])>1e9) { cnt=i-1; break; }
for (pfx[0]=i=1;i<=cnt;++i) pfx[i]=pfx[i-1]+fib[i];
for (scanf("%d",&t);t;--t)
{
for (sum=0,scanf("%d",&k),i=1;i<=k;++i) scanf("%d",&c[i]),sum+=c[i];
int pos=-1; for (i=0;i<=cnt&&!~pos;++i) if (sum==pfx[i]) pos=i;
if (!~pos) { puts("NO"); continue; } for (lst=0;~pos;--pos)
{
int mx=-1,num; for (i=1;i<=k;++i) if (i!=lst&&c[i]>mx) mx=c[i],num=i;
if (mx<fib[pos]) break; c[num]-=fib[pos]; lst=num;
}
puts(~pos?"NO":"YES");
}
return 0;
}
F. Tonya and Burenka-179
一个重要性质没想到,直接G了
首先我们发现若\(k|n\),那么就是一些循环,否则就是所有数的和
刚开始以为要处理所有的\(n\)的约数,但这样复杂度是寄的
考虑若\(k_1|n,k_2|n,k_1|k_2\),则我们只需要考虑\(k_2\)而无需考虑\(k_1\),因为\(k_1\)相当于把\(k_2\)的集合拆分成了\(\frac{k_2}{k_1}\)份,此时必然存在某一份的平均值大于原来的平均值
因此我们只需要考虑\(n\)的所有极大约数即可,具体地,设\(n=p_1^{a_1}p_2^{a_2}p_3^{a_3}\cdots p_k^{a_k}\),我们只需要考虑\(\frac{n}{p_1},\frac{n}{p_2},\frac{n}{p_3},\cdots,\frac{n}{p_k}\)即可
由于\(n\)的质因数是\(\log\)级别的,因此极大约数也是\(\log\)级别的,初始时先暴力一遍求出答案,然后我们发现问题就是单点修改,问整体最大值
直接用两个堆来实现可删除堆即可,单组数据复杂度\(O(q\log^2 n)\)
#include<cstdio>
#include<vector>
#include<queue>
#define RI register int
#define CI const int&
#define LL long long
using namespace std;
const int N=200005;
int t,n,q,a[N],x,y,cnt,fac[N]; vector <LL> sum[N]; priority_queue <LL> add,del;
inline LL get(void)
{
while (!add.empty()&&!del.empty()&&add.top()==del.top()) add.pop(),del.pop(); return add.top();
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d%d",&n,&q),i=0;i<n;++i) scanf("%d",&a[i]);
for (cnt=0,x=n,i=2;i<=x;++i) if (x%i==0)
{
fac[++cnt]=n/i; while (x%i==0) x/=i;
}
while (!add.empty()) add.pop(); while (!del.empty()) del.pop();
for (i=1;i<=cnt;++i)
{
int k=fac[i]; sum[i].resize(k); for (j=0;j<k;++j) sum[i][j]=0;
for (j=0;j<n;++j) sum[i][j%k]+=a[j];
for (j=0;j<k;++j) add.push(sum[i][j]*k);
}
printf("%lld\n",get()); while (q--)
{
scanf("%d%d",&x,&y); --x; for (i=1;i<=cnt;++i)
{
int k=fac[i]; del.push(sum[i][x%k]*k);
sum[i][x%k]+=y-a[x]; add.push(sum[i][x%k]*k);
}
a[x]=y; printf("%lld\n",get());
}
}
return 0;
}
(Div. 1)D. Permutation for Burenka
woc尼玛的这场的Div1难度恐怖,后三题都是3300+的题,刚开始傻傻地刚这道题直接做了一个下午(只有英文题解看的脑壳疼)
首先从区间最大值我们很容易想到笛卡尔树,我们先对\(p\)建出笛卡尔树,然后考虑若\(a\)的每个元素都确定了,那么只需要验证对于树上的每一对\(u,v\),满足\(v\)在\(u\)的子树内,是否均有\(a_u>a_v\),若均符合我们称其为相似树
那么有空位怎么办呢,我们可以放宽上面的条件,若\(a_u,a_v\)均不为\(0\),则需要\(a_u>a_v\),但若\(a_u,a_v\)均为\(0\)亦满足条件,我们把符合这个条件的树称为类相似树
接下来有一个结论,若\(a\)是\(p\)的类相似树,则必然存在某种构造方案,使得\(a\)是\(p\)的相似树
考虑证明,首先我们不妨找出任意一种构造方案,对于每个节点\(u\),设其所有子树中的最大值为\(v\),若\(a_u>a_v\)则合法无事发生
否则若\(a_u<a_v\),由类相似树的定义可知\(u,v\)的值一定都是填入的(即\(a_u,a_v\)初始时均为\(0\)),那我们直接交换\(a_u,a_v\)即可
因此现在问题转化为如何判断\(a\)是否是\(p\)的类相似树,考虑利用笛卡尔树的性质
设\(l_u=\max_\limits{v\in \operatorname{subtree}(u)} a_v,r_u=\min_\limits{u\in \operatorname{subtree}(v)} a_v\),不难发现此时需要满足所有的\(l_i\le a_i\le r_i\)均成立
于是问题转化为,有\(k-1\)个已经确定的数和\(k\)段区间,每次给出剩下的一个数,问是否存在一种完美的数和区间的匹配方案
这里有一个很神的结论,最终合法的数是一个区间,具体证明我策了半天不太懂,这里直接贴一下某大佬的证法(是用霍尔定理证的)
然后问题就转化为一个经典的贪心问题了,我们把所有区间排序后双指针加入每个点,贪心地匹配即可,不过这里注意要维护最后的合法区间需要用到线段树
单组数据复杂度\(O(n\log n)\)
#include<cstdio>
#include<utility>
#include<algorithm>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=300005,INF=1e9;
int t,n,q,m,x,a[N],b[N],c[N],rst[N<<1],tot,l[N],r[N]; pi itv[N];
class Cartesian_tree
{
private:
int ch[N][2],stk[N],top,rt;
#define lc(x) ch[x][0]
#define rc(x) ch[x][1]
inline void DFS(CI now,CI fa)
{
r[now]=r[fa]; if (b[now]) r[now]=min(r[now],b[now]); l[now]=b[now];
for (RI i=0;i<=1;++i) if (ch[now][i]) DFS(ch[now][i],now),l[now]=max(l[now],l[ch[now][i]]);
}
public:
inline void build(void)
{
RI i; for (top=stk[1]=0,i=1;i<=n;++i)
{
lc(i)=rc(i)=0; while (top&&a[i]>a[stk[top]]) --top;
if (top) lc(i)=rc(stk[top]),rc(stk[top])=i; else lc(i)=stk[1]; stk[++top]=i;
}
rt=stk[1]; r[0]=INF; DFS(rt,0);
}
#undef lc
#undef rc
}CT;
class Segment_Tree
{
private:
struct segment
{
int tag; pi mi;
}node[N<<3];
#define T(x) node[x].tag
#define M(x) node[x].mi
#define TN CI now=1,CI l=1,CI r=tot
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void pushup(CI now)
{
M(now)=min(M(now<<1),M(now<<1|1));
}
inline void apply(CI now,CI v)
{
T(now)+=v; M(now).fi+=v;
}
inline void pushdown(CI now)
{
if (T(now)) apply(now<<1,T(now)),apply(now<<1|1,T(now)),T(now)=0;
}
public:
inline void build(TN)
{
T(now)=0; if (l==r) return (void)(M(now)=mp(0,l));
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
inline void modify(CI beg,CI end,CI v,TN)
{
if (beg<=l&&r<=end) return apply(now,v); int mid=l+r>>1; pushdown(now);
if (beg<=mid) modify(beg,end,v,LS); if (end>mid) modify(beg,end,v,RS); pushup(now);
}
inline pi query(void)
{
return M(1);
}
#undef T
#undef M
#undef TN
#undef LS
#undef RS
}ST;
inline int get(CI x)
{
return lower_bound(rst+1,rst+tot+1,x)-rst;
}
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,&q),i=1;i<=n;++i) scanf("%d",&a[i]);
for (m=0,i=1;i<=n;++i) scanf("%d",&b[i]),m+=!b[i];
for (i=1;i<m;++i) scanf("%d",&c[i]); sort(c+1,c+m);
for (tot=0,i=1;i<=n;++i) rst[++tot]=a[i],rst[++tot]=b[i]; rst[++tot]=INF;
sort(rst+1,rst+tot+1); tot=unique(rst+1,rst+tot+1)-rst-1;
bool flag=1; for (CT.build(),i=1;i<=n&&flag;++i) if (l[i]>r[i]) flag=0;
for (m=0,i=1;i<=n;++i) if (!b[i]) itv[++m]=mp(l[i],r[i]);
sort(itv+1,itv+m+1); ST.build(); int L=0,R=INF,now=m-1;
for (i=m;i&&flag;--i)
{
ST.modify(get(itv[i].se),tot,-1);
while (now&&c[now]>=itv[i].fi) ST.modify(get(c[now--]),tot,1);
pi rt=ST.query(); if (rt.fi<-1) flag=0; else
if (rt.fi==-1) L=max(L,itv[i].fi),R=min(R,rst[rt.se]);
}
while (q--) scanf("%d",&x),puts(flag&&x>=L&&x<=R?"YES":"NO");
}
return 0;
}
Postscript
明天就要开始上网课了,不知道还有没有时间打代码了……