Preface
这场貌似很简单啊,每道题过的人都好多……
A. Mark the Photographer
sb题,升序排序后把\([1,n]\)个人排前面,然后把\([n+1,2n]\)顺序排在后面验证即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,x,a[N<<1];
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,&x),i=1;i<=(n<<1);++i) scanf("%d",&a[i]);
bool flag=1; for (sort(a+1,a+(n<<1)+1),i=1;i<=n&&flag;++i)
if (a[i+n]-a[i]<x) flag=0; puts(flag?"YES":"NO");
}
return 0;
}
B. Mark the Dust Sweeper
首先不难发现,我们从后往前考虑,记录下所有\(0\)的位置
然后对于每一个非\(0\)的数,我们可以花费若干次数来把它后面最近的\(0\)都变成\(1\),或者直到这个数为\(1\)
这样维护一遍之后我们发现此时要么一路上都没有\(0\)了,直接算答案即可
否则就是一段连续的\(0/1\)间隔的串(类似于\(111010010\)这样),直接从左往右模拟一遍即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],st,stk[N]; long long ans;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,top=0; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (st=n,i=1;i<n;++i) if (a[i]) { st=i; break; }
for (ans=0,i=n-1;i>=st;--i) if (!a[i]) stk[++top]=i; else
while (a[i]>1&&top) --a[i],a[stk[top--]]=1,++ans;
if (!top)
{
for (i=1;i<n;++i) ans+=a[i]; printf("%lld\n",ans); continue;
}
for (i=st;i<n;++i) if (a[i])
{
if (top) a[i]=0,a[stk[top--]]=1,++ans; else ans+=a[i];
}
printf("%lld\n",ans);
}
return 0;
}
C. Mark and His Unfinished Essay
不难发现数据范围很小,因此考虑直接暴力
对于每次询问的\(k\),我们设一个数组\(len_i\)表示做完\(i\)个操作后串的长度
考虑分解问题,我们可以先找出询问\(k\)所在的操作\(j\),满足\(len_{j-1}<k\le len_j\)
容易发现此时我们要做的就是找出在操作\(j-1\)中的第\(k-len_{j-1}-1+l_j\)个字符
直接模拟这个过程即可,复杂度\(O(n+cq)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=200005;
int t,n,c,q,len[N],l[N],r[N],x; char s[N];
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld%lld%lld%s",&n,&c,&q,s+1),len[0]=n,i=1;i<=c;++i)
scanf("%lld%lld",&l[i],&r[i]),len[i]=len[i-1]+r[i]-l[i]+1;
while (q--)
{
for (scanf("%lld",&x),i=c;i;--i)
if (x>len[i-1]&&x<=len[i]) x=x-len[i-1]+l[i]-1;
putchar(s[x]); putchar('\n');
}
}
return 0;
}
D. Mark and Lightbulbs
首先因为连续的\(0/1\)串中间是不能进行操作的,因此我们考虑将原串的连续\(0/1\)串分开,考虑只有它们的边界上可以操作
因此无论怎么操作,连续的\(1\)的段数是不变的,因此可以作为判断是否有解的依据
那么考虑如何计算答案,因为每一段的个数也不一样,看似不好计算
实际上我们手玩一下就发现,我们可以统计出每一段的左右端点,只需要统计两个端点的移动距离之和即可
注意开long long
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int T,n,a[N],b[N],ta,tb; char s[N],t[N]; long long ans;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&T);T;--T)
{
RI i; for (scanf("%d%s%s",&n,s+1,t+1),ta=tb=ans=0,i=1;i<n;++i)
{
if (s[i]!=s[i+1]) a[++ta]=i;
if (t[i]!=t[i+1]) b[++tb]=i;
}
if (s[1]!=t[1]||s[n]!=t[n]||ta!=tb) { puts("-1"); continue; }
for (i=1;i<=ta;++i) ans+=abs(a[i]-b[i]); printf("%lld\n",ans);
}
return 0;
}
E. Mark and Professor Koro
怎么感觉这题很早之前就做过的说
首先容易发现可以把题目转化为模拟二进制,先把修改拆成删除和加入一个数
然后加入\(x\)相当于在第\(x\)位上加\(1\),每一位逢二进一,删除同理
不难发现对于加法操作,我们要找的就是某一位后面最近的\(0\),而减法就是找最近的\(1\)
同时还有处理进位/借位带来的区间修改,直接上线段树即可
注意那个找某一位后面的\(0/1\)可以直接在线段树上二分,复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=200050;
int n,q,a[N],x,y;
class Segment_Tree
{
private:
struct segment
{
int sum,tag;
}node[N<<2];
#define S(x) node[x].sum
#define T(x) node[x].tag
#define TN CI now=1,CI l=1,CI r=200030
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void pushup(CI now)
{
S(now)=S(now<<1)+S(now<<1|1);
}
inline void updata(CI now,CI l,CI r,CI x)
{
S(now)+=x*(r-l+1); T(now)+=x;
}
inline void pushdown(CI now,CI l,CI r)
{
int mid=l+r>>1; if (T(now)) updata(LS,T(now)),updata(RS,T(now)),T(now)=0;
}
public:
inline void modify(CI beg,CI end,CI x,TN)
{
if (beg<=l&&r<=end) return updata(now,l,r,x); int mid=l+r>>1; pushdown(now,l,r);
if (beg<=mid) modify(beg,end,x,LS); if (end>mid) modify(beg,end,x,RS); pushup(now);
}
inline int get(CI pos,TN)
{
if (l==r) return S(now); int mid=l+r>>1; pushdown(now,l,r);
return pos<=mid?get(pos,LS):get(pos,RS);
}
inline int querymax(TN)
{
if (l==r) return l; int mid=l+r>>1; pushdown(now,l,r);
return S(now<<1|1)?querymax(RS):querymax(LS);
}
inline int query0(CI pos,TN)
{
if (r<pos||S(now)==r-l+1) return -1; if (l==r) return l; int mid=l+r>>1,ret; pushdown(now,l,r);
ret=query0(pos,LS); if (!~ret) ret=query0(pos,RS); return ret;
}
inline int query1(CI pos,TN)
{
if (r<pos||!S(now)) return -1; if (l==r) return l; int mid=l+r>>1,ret; pushdown(now,l,r);
ret=query1(pos,LS); if (!~ret) ret=query1(pos,RS); return ret;
}
#undef S
#undef T
#undef TN
#undef LS
#undef RS
}T;
inline void add(CI x)
{
int pos; if (!T.get(x)) T.modify(x,x,1); else
pos=T.query0(x+1),T.modify(x,pos-1,-1),T.modify(pos,pos,1);
}
inline void del(CI x)
{
int pos; if (T.get(x)) T.modify(x,x,-1); else
pos=T.query1(x+1),T.modify(x,pos-1,1),T.modify(pos,pos,-1);
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d%d",&n,&q),i=1;i<=n;++i)
scanf("%d",&a[i]),add(a[i]); while (q--)
{
scanf("%d%d",&x,&y); del(a[x]); add(a[x]=y);
printf("%d\n",T.querymax());
}
return 0;
}
F. Mark and the Online Exam
今天在路上站岗,没事干就一直在yy这道题,结果只搞了个半成品,回来看了sol大呼巧妙
首先根据操作上限,猜测出大概是\(\frac{2}{3}n\)的做法
刚开始很naive的想到找出一些二元组一起做(二元组随机分配),每个二元组有\(\frac{1}{2}\)的概率被一次操作看出(当修改后答案变化为\(2\)),而其它的\(\frac{1}{2}\)可以被两次操作看出(修改后答案不变)
因此现在期望操作次数是\(\frac{3}{4}n\),然后就开始想三个一起做,四个一起做,结果推了下怎么样都是\(\frac{3}{4}n\)
后来发现还需要一个小技巧,具体地,我们设\(S_1=TTT\cdots TTT,S_2=TFTF\cdots TFT\),且以下除法默认下取整
考虑首先询问\(S_1\)翻转\(2i-1,2i\ (1\le i\le \frac{n}{3})\),与\(S_1\)所得结果比较:
- 正确数\(+2\),说明答案是\(TT\),同时可再利用一次操作问出\(\frac{2}{3}n+i\)的值
- 正确数\(+2\),说明答案是\(FF\),同时可再利用一次操作问出\(\frac{2}{3} n+i\)的值
- 正确数不变,说明答案是\(TF/FT\),接下来在\(S_2\)中翻转\(2i-1,2i,\frac{2}{3}n+i\ (1\le i\le \frac{n}{3})\):
- 结果\(+3\),全部换后正确
- 结果\(+1\),\(2i-1,2i\)换后正确,\(\frac{2}{3}n+i\)换前正确
- 结果\(-1\),\(2i-1,2i\)换钱正确,\(\frac{2}{3}n+i\)换后正确
- 结果\(-3\),全部换前正确
若\(n\)不是\(3\)的倍数,最后注意下把后面的剩下的部分处理以下,最终最高次数大约在\(670\)左右
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int n,ct1,ct2; char s[N],t[N],ans[N];
inline void flip(char& ch)
{
if (ch=='T') ch='F'; else ch='T';
}
inline int ask(char *s)
{
for (RI i=1;i<=n;++i) putchar(s[i]); putchar('\n');
fflush(stdout); int x; scanf("%d",&x);
if (x==n) exit(0); return x;
}
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) s[i]=t[i]='T';
ct1=ask(s); for (i=2;i<=n;i+=2) t[i]='F';
ct2=ask(t); for (i=1;i<=n;++i) s[i]='T';
int m=n/3; for (i=1;i<=2*m;i+=2)
{
flip(s[i]); flip(s[i+1]); int ct=ask(s),id=2*m+(i+1)/2;
if (ct==ct1+2)
{
ans[i]=s[i]; ans[i+1]=s[i+1]; flip(s[i]); flip(s[i+1]);
flip(s[id]); ct=ask(s); if (ct==ct1+1)
ans[id]=s[id],flip(s[id]); else flip(s[id]),ans[id]=s[id];
} else
if (ct==ct1-2)
{
flip(s[i]); flip(s[i+1]); ans[i]=s[i]; ans[i+1]=s[i+1];
flip(s[id]); ct=ask(s); if (ct==ct1+1)
ans[id]=s[id],flip(s[id]); else flip(s[id]),ans[id]=s[id];
} else
{
flip(t[i]); flip(t[i+1]); flip(t[id]); ct=ask(t);
if (ct==ct2+3)
{
ans[i]=t[i]; ans[i+1]=t[i+1]; ans[id]=t[id];
flip(t[i]); flip(t[i+1]); flip(t[id]);
} else
if (ct==ct2-3)
{
flip(t[i]); flip(t[i+1]); flip(t[id]);
ans[i]=t[i]; ans[i+1]=t[i+1]; ans[id]=t[id];
} else
if (ct==ct2+1)
{
ans[i]=t[i]; ans[i+1]=t[i+1]; flip(t[i]); flip(t[i+1]);
flip(t[id]); ans[id]=t[id];
} else
{
flip(t[i]); flip(t[i+1]); ans[i]=t[i]; ans[i+1]=t[i+1];
ans[id]=t[id]; flip(t[id]);
}
}
}
for (i=3*m+1;i<=n;++i)
{
flip(s[i]); int ct=ask(s); if (ct==ct1+1)
ans[i]=s[i],flip(s[i]); else flip(s[i]),ans[i]=s[i];
}
return ask(ans),0;
}
PS:这题的加强版好像是Luogu P7848 「JZOI-2」填问卷,据说有\(O(\frac{n}{\log n})\)的神仙做法,但找不到题解的说
Postscript
明天就要去二中摸鱼啦,虽然陈指导要准备NOI已经人在苏州了,但是还是有点期待的说
希望还有人认识我的说(据说迷途知返已经成为二中的机房文 化了?),反向名垂青史233