Codeforces Round 893 (Div. 2) 解题报告

Codeforces Round 893 (Div. 2)

Contest link

要开始复健解题报告了。

啊啊啊啊啊网站炸了比赛推了,又变回正常时间了

获得惩罚:睡觉时间 \(-1\operatorname{hour}\)

首次 Div. 2 打进前 100 名(可能也是最后一次),上了 master。


A. Buttons

显然两个人都会优先按都能按的。

那么如果 \(c\) 是奇数,A 就可以比 B 多按一次。

最后只要 A 按的比 B 多就是 first

时间复杂度 \(\Theta(1)\)

代码实现

int a,b,c;
void Solve()
{
	cin>>a>>b>>c;
	if(a>b||(a==b&&(c&1)))puts("First");
	else puts("Second");
}

B. The Walkway

吐槽题面丑。

定义 \(f(x,y)\) 表示从位置 \(x\) 开始走并在此时吃一块饼干,走到位置 \(\bm{y-1}\) 一共吃的饼干数。特别的,定义 \(f(x,x)=0\)。这个东西是好计算的。

那么,如果不移除任何小卖部,则总饼干数为

\[S=f(1,a_1)+\sum_{i=1}^{m-1}f(a_i,a_{i+1})+f(a_m,n+1). \]

考虑移除第 \(x\) 个小卖部,则饼干数为

\[S-f(a_{x-1},a_x)-f(a_x,a_{x+1})+f(a_{x-1},a_{x+1}). \]

注意,此时 \(x=1\)\(x=m\) 需要特判。代码中用了一点 trick 简化代码。

然后随便统计一下即可。时间复杂度 \(\Theta(m)\)

代码实现

int n,m,k,a[100005];
int f(int x,int y)
{
	return (a[y]-a[x]-1<0?0:1+(a[y]-a[x]-1)/k);
}
void Solve()
{
	cin>>n>>m>>k;
	a[0]=1,a[m+1]=n+1;
	int sum=0;
	for(int i=1;i<=m;i++)cin>>a[i];
	int cnt=0,ans;
	for(int i=0;i<=m;i++)sum+=f(i,i+1);
	ans=sum;
	for(int i=1;i<=m;i++)
	{
		int a2=sum-f(i,i+1)-f(i-1,i)+f(i-1,i+1);
		if(a2<ans)ans=a2,cnt=1;
		else if(a2==ans)cnt++;
	}
	cout<<ans<<" "<<cnt<<endl;
}

C. Yet Another Permutation Problem

吐槽撞题。U R wrong, that's Y.

显然任意两个数的 \(\gcd\) 不能超过 \(\left\lfloor\dfrac{n}{2}\right\rfloor\)

下面给出一个达到理论上界的构造:

从大到小考虑每个 \(x\),如果 \(x\) 还未在排列中,则将 \(x,\dfrac{x}{2},\dfrac{x}{4},\dfrac{x}{8},\dots\) 依次加到排列中,直到变成了一个奇数为止。证明见原题。

时间复杂度 \(\Theta(n)\)

代码实现

int n;
set<int>s;
void Solve()
{
	cin>>n;
	if(n&1)cout<<(n--)<<" ";
	s.clear();
	for(int i=n;i;i--)
	{
		if(s.count(i))continue;
		int t=i;
		while(!(t&1))cout<<t<<" ",s.insert(t),t>>=1;
		cout<<t<<" ";
		s.insert(t);
	}
	cout<<endl;
}

D. Trees and Segments

DP 好题。赛时没做出来。

\(pre_{i,j}\) 为前 \(i\) 个字符,最多修改 \(j\) 次,所能得到的最长连续的 \(0\) 的数量。同理定义 \(suf_{i,j}\),表示从 \(i\) 到末尾,其余同 \(pre\)

然后这两个东西是好转移的。

考虑令最长连续 \(1\) 的区间为 \([l,r]\),我们可以求出需要修改的位置数量(假设为 \(t\))。那么此时,最长连续的 \(0\) 的数量就是 \(\max\{pre_{l-1,k-t},suf_{r+1,k-t}\}\),然后再去更新答案(当然如果 \(t>k\) 也就不用更新)。

直接这样做是 \(\Theta(n^3)\) 的(但是我代码假成 \(\Theta(n^3)\) 居然 \(\text{2800+ ms}\) 跑过了)。

注意到对于固定的长度,我们只需求出连续 \(0\) 的最大长度就可以了。然后对于相邻长度相等的两个区间,要修改的个数也只需要 \(\Theta(1)\) 转移。

于是我们枚举长度 \(len\),再扫一遍长度为 \(len\) 的所有区间(这只需要 \(\Theta(n)\)),可以求出此时连续 \(0\) 的最大长度。然后我们用其来更新答案即可。

总复杂度 \(\Theta(n^2)\)。听说还能优化到 \(\Theta(nk)\)

多测不清空,WA 两行泪。

代码实现

const int N=3005;
int n,k,pre[N][N],suf[N][N],ans[N];
string st;
void Solve()
{
	cin>>n>>k>>st;st="$"+st;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=k;j++)
			if(st[i]=='1')
			{
				if(j)pre[i][j]=pre[i-1][j-1]+1;
				else pre[i][j]=0;
			}
			else pre[i][j]=pre[i-1][j]+1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=k;j++)
			pre[i][j]=max(pre[i][j],max(pre[i-1][j],j?pre[i][j-1]:0));
	for(int i=n;i;i--)
		for(int j=0;j<=k;j++)
			if(st[i]=='1')
			{
				if(j)suf[i][j]=suf[i+1][j-1]+1;
				else suf[i][j]=0;
			}
			else suf[i][j]=suf[i+1][j]+1;
	for(int i=n;i;i--)
		for(int j=0;j<=k;j++)
			suf[i][j]=max(suf[i][j],max(suf[i+1][j],j?suf[i][j-1]:0));
	for(int i=0;i<=n;i++)
	{
		int d=-1,c=k;
		for(int j=1;j<i;j++)if(st[j]=='0')c--;
		for(int j=1;j<=n-i+1;j++)
		{
			if(st[j-1]=='0')c++;
			if(st[j+i-1]=='0')c--;
			if(c<0)continue;
			d=max(d,max(pre[j-1][c],suf[j+i][c]));
		}
		if(~d)for(int j=1;j<=n;j++)ans[j]=max(ans[j],d*j+i);
	}
	for(int i=1;i<=n;i++)cout<<ans[i]<<sp_el(i,n);
}

E. Rollback

吐槽:好像 E1 和 E2 确实没啥关系。

离线做法(E1)

先给一个赛时的 E1 离线做法,不感兴趣也可以直接去看在线做法。

考虑建一棵类似 trie 的东西,加数的时候直接跳到相应儿子,删的时候直接跳 \(k\) 级祖先,同时用 vector 记录之前走过的节点用来回溯即可。

最后用 set 统计一下每个结点的答案,统一输出答案即可。时间复杂度 \(\Theta(n\log n)\)\(\log\) 来自倍增跳 \(k\) 级祖先。)

但是 trie 没法直接存儿子,用 map 也会 MLE。这里给一个时间换空间的做法:

注意到只有加数的时候会建至多一个新节点,加数时我们可以直接建一个新点(无论父亲是否已经有这个儿子)。这样做,节点数仍然为 \(\Theta(n)\),而我们省去了 map,于是更容易通过。

代码实现

(这是被 skipped 掉的一发,赛后再交同样 accepted,但是更好看。)

const int N=1000005;
struct node
{
	vector<PII>sons;
	int val;
	int fa[20];
}tr[N];int cur;
int n,q;
char op;int x;
int now,p[N];
VI ops;
int new_calc(int x,int Fa)
{
	++cur;
	tr[Fa].sons.PB(x,cur);
	tr[cur].fa[0]=Fa;
	for(int i=1;;i++)
		if(!(tr[cur].fa[i]=tr[tr[cur].fa[i-1]].fa[i-1]))return cur;
}
int grand(int x,int k)
{
	for(int i=19;~i;i--)
		if(k&(1<<i))x=tr[x].fa[i];
	return x;
}
int cnt[N],siz;
void dfs(int x)
{
	tr[x].val=siz;
	for(PII son:tr[x].sons)
	{
		int v=son.first;
		if(!cnt[v]++)siz++;
		dfs(son.second);
		if(!--cnt[v])siz--;
	}
}
void Solve()
{
	cin>>n;
	now=cur=1;
	ops.PB(1);
	for(int i=1;i<=n;i++)
	{
		cin>>op;
		if(op=='!')
		{
			ops.PPB();
			now=ops.back();
			continue;
		}
		if(op=='+'||op=='-')
		{
			cin>>x;
			if(op=='+')now=new_calc(x,now);
			if(op=='-')now=grand(now,x);
			ops.PB(now);
		}
		else p[++q]=now;
	}
	dfs(1);
	for(int i=1;i<=q;i++)cout<<tr[p[i]].val<<endl;
}

在线做法(E1+E2)

如果不需要回溯的话,暴力就可以均摊 \(\Theta(1)\)回溯:万恶之源

那现在怎样卡暴力?加一堆数,全部删除,回溯,再全部删除,再回溯……

那现在怎样防止这件事发生?想想 dijkstra 咋从堆里删元素的?是的,懒惰删除

我们记当前序列长度为 \(len\)。当删除时,我们只将 \(len\gets len-k\),而不需要实际上地删除。

那查询咋办?我们可以记录每个元素最早出现的位置,当查询的时候求出最早出现位置 \(\le len\) 的数的个数,用树状数组维护即可。至于最早出现位置,用 set 就可以维护。

至于回溯,记录上一次修改过的量,滚回去即可。

注意到对于加入、删除操作,要修改的元素(包括 \(len\)\(a_i\))只有 \(\Theta(1)\) 个,完美。

总时间复杂度 \(\Theta(n\log n)\)\(\log\) 来自树状数组和 set。)

const int N=1000005;
int n,len,a[N];
vector<int>clen;
vector<PII>ca;
int c[N],b[N];
set<int>p[N];
void change(int pos,int val)
{
	int d=val-b[pos];b[pos]=val;
	while(pos<N)c[pos]+=d,pos+=pos&(-pos);
}
int sum(int pos)
{
	int res=0;
	while(pos)res+=c[pos],pos-=pos&(-pos);
	return res;
}
void cg(int k,int x)
{
	int y=a[k];a[k]=x;
	if(y)
	{
		change(*p[y].begin(),0);
		p[y].erase(k);
		if(nonEmp(p[y]))change(*p[y].begin(),1);
	}
	if(x)
	{
		if(nonEmp(p[x]))change(*p[x].begin(),0);
		p[x].insert(k);
		change(*p[x].begin(),1);
	}
}
void Solve()
{
	cin>>n;
	while(n--)
	{
		char op=' ';
		while(op!='+'&&op!='-'&&op!='!'&&op!='?')op=getchar();
		if(op=='?')printf("%d\n",sum(len)),fflush(stdout);
		else if(op=='!')
		{
			len=clen.back();clen.PPB();
			if(ca.back().first)
				cg(ca.back().first,ca.back().second);
			ca.PPB();
		}
		else
		{
			int x;scanf("%d",&x);
			if(op=='+')
			{
				clen.PB(len++);
				ca.PB(len,a[len]);
				cg(len,x);
			}
			else clen.PB(len),len-=x,ca.PB(0,0);
		}
	}
}
posted @ 2023-08-15 15:25  No_Play_Yes_Splay  阅读(62)  评论(0编辑  收藏  举报