Codeforces Round 1329 简要题解

27 篇文章 1 订阅
23 篇文章 1 订阅

爆炸的一场,掉分掉傻了。

A. Dreamoon Likes Coloring

B. Dreamoon Likes Sequences

C. Drazil Likes Heap

被一个div1C题彻底打爆了。
注意到我们对一个权值当前所在的点调用 f f f后,这个权值就被删去了。但是如果直接删去最大的 2 h − 2 g 2^h-2^g 2h2g个权值,可能导致最后剩下的不是完全二叉树。
换个角度,我们考虑确定保留的权值集合。那么当我们固定了权值集合后,会按某个顺序对所有其他权值当前所在的点调用 f f f。可以发现,操作完后每个权值所在的位置是固定的,与对其他权值的操作顺序无关!
证明是考虑不管我们如何操作,操作完后一定还是一个类似堆的结构,满足每个点的权值小于父亲的权值,且是一个与 1 1 1号点相邻的连通块。并且还可以注意到,每个权值最终所在的位置只可能是它一开始所在的位置到根的路径上。令我们保留的权值集合为 S S S,那么考虑 1 1 1号点,它最终的权值只可能是 S S S中的最大值 m x mx mx。将这个权值删去后,考虑 1 1 1号点的左右子树,它们子树中最终的权值集合与开始时 S S S在子树中的子集必然相同(只是可能删去了 m x mx mx)。这样,对左右子树归纳证明即可。
有了这个引理就比较好做了。有一个自底向上确定的算法,也有一个自顶向下确定的算法。
先考虑自底向上的算法。大致思路是令节点 x x x最终可能的最小值为 p ( x ) p(x) p(x)。对于所有第 g g g层的节点 x x x p ( x ) p(x) p(x)即为 x x x子树中的最小值。假设已经知道了第 k + 1 k+1 k+1层所有节点的 p p p,那么对于第 k k k层的节点 x x x,必有 p ( x ) > max ⁡ ( p ( 2 x ) , p ( 2 x + 1 ) ) p(x)>\max(p(2x),p(2x+1)) p(x)>max(p(2x),p(2x+1)),且 p ( x ) p(x) p(x) x x x子树中出现过,直接贪心取此时的最小可能值即可。这样我们相当于确定了一个保留权值的集合,根据上面的引理,剩余的权值的 f f f调用顺序不会影响最终得到的结果,并且用类似引理的分析方式,容易知道这样得到的确实是一个合法解,而最优性是显然的。
注意到,这样得到的最优解是贪心的,也即 p ( x ) p(x) p(x) x x x在所有可能的合法解中最小的可能权值。
再考虑自顶向下的算法。大致思路是将所有权值从大到小排序,每次考虑当前最大的权值,如果对它当前所在的点调用 f f f后,后面随意操作仍然有可能合法(也即删去的节点不在第 g g g层),那么我们就对它操作,否则不操作。
证明是考虑这样得到的显然会是一个合法解。设按这个算法点 x x x最终的权值是 p ′ ( x ) p'(x) p(x),我们只要证明 p ′ ( x ) = p ( x ) p'(x)=p(x) p(x)=p(x)即可。注意由于 p ( x ) p(x) p(x) x x x在所有可能的合法解中最小的可能权值,于是有 p ′ ( x ) ≥ p ( x ) p'(x)\geq p(x) p(x)p(x)。那么对于 1 1 1号点来说,按这个自顶向下的算法过程,我们会不断调用 f ( 1 ) f(1) f(1)直到不能调用(因为此时最大权值一定在 1 1 1号点),此时 1 1 1号点的权值就是最终的 p ′ ( 1 ) p'(1) p(1)。由引理知道操作顺序是任意的,因此一定存在一个最优解是依次从大往小对所有 > p ( 1 ) >p(1) >p(1)的权值所在的点调用 f f f,因此也有 p ′ ( 1 ) ≤ p ( 1 ) p'(1)\leq p(1) p(1)p(1)。于是确实有 p ′ ( 1 ) = p ( 1 ) p'(1)=p(1) p(1)=p(1)。注意到后面不可能再调用 f ( 1 ) f(1) f(1),于是 1 1 1的左右子树独立了,对左右子树归纳证明即可。
单组数据时间复杂度 O ( h 2 h ) \mathcal O(h2^h) O(h2h)。代码是自顶向下的算法。

#include <bits/stdc++.h>
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;

int num[4000005],id[4000005];
pr a[2000005];

ll ans;
vector <int> vt;

int query(int x) {
  if (!num[x*2]&&!num[x*2+1]) return x;
  if (num[x*2]>num[x*2+1]) return query(x*2);
  else return query(x*2+1);
}

void del(int x) {
  if (!num[x*2]&&!num[x*2+1]) {
  	ans-=num[x];
  	num[x]=0;
  	return;
  }
  if (num[x*2]>num[x*2+1]) {
  	swap(num[x],num[x*2]);
  	swap(id[num[x]],id[num[x*2]]);
  	del(x*2);
  }
  else {
  	swap(num[x],num[x*2+1]);
  	swap(id[num[x]],id[num[x*2+1]]);
  	del(x*2+1);
  }
}

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	int h,g;
  	scanf("%d%d",&h,&g);
  	memset(num,0,sizeof(int)*((1<<(h+1))+5));
  	ans=0;
  	for(int i=1;i<(1<<h);i++) {
	  scanf("%d",&num[i]);
	  a[i]=pr(num[i],i);
	  id[num[i]]=i;
	  ans+=num[i];
    }
    vt.clear();
    sort(a+1,a+(1<<h));
    int r=(1<<h)-1;
    for(int i=1;i<=(1<<h)-(1<<g);i++) {
    	while (query(id[a[r].FR])<(1<<g)) r--;
    	vt.push_back(id[a[r].FR]);
    	del(id[a[r].FR]);
    	r--;
	}
	printf("%lld\n",ans);
	for(int i=0;i<vt.size();i++) printf("%d ",vt[i]);
	printf("\n");
  }
  return 0;
} 

D. Dreamoon Likes Strings

我们让每次操作的区间 [ l , r ] [l,r] [l,r]变为在初始 a a a中左右端点的下标。这样可以发现操作间的区间包含关系形成了一个树形结构。注意到如果某次操作 [ l , r ] [l,r] [l,r] l > 1 l>1 l>1 a l ≠ a l − 1 a_l\neq a_{l-1} al=al1,或 r < ∣ a ∣ r<|a| r<a a r ≠ a r + 1 a_r\neq a_{r+1} ar=ar+1,那么调整一下显然不会变劣。于是一定存在一个最优解使得每次操作的 [ l , r ] [l,r] [l,r]满足 l = 1 l=1 l=1 a l = a l − 1 a_l=a_{l-1} al=al1,且 r = ∣ a ∣ r=|a| r=a a r = a r + 1 a_r=a_{r+1} ar=ar+1
令所有满足 a i = a i + 1 a_i=a_{i+1} ai=ai+1的位置为 0 = p 0 < p 1 < p 2 < . . . < p k < p k + 1 = n 0=p_0<p_1<p_2<...<p_k<p_{k+1}=n 0=p0<p1<p2<...<pk<pk+1=n,定义长为 k k k的字符串 b b b,满足 b i = a p i b_i=a_{p_i} bi=api。注意根据上面的结论,我们第一次操作一定形如 [ p i + 1 , p i + 1 ] [p_i+1,p_{i+1}] [pi+1,pi+1]。那么若 i = 1 i=1 i=1 i = k i=k i=k b i = b i + 1 b_i=b_{i+1} bi=bi+1 a i = a i + 1 a_i=a_{i+1} ai=ai+1的位置只会删去一个 p i + 1 p_{i+1} pi+1,否则会删去 p i p_i pi p i + 1 p_{i+1} pi+1。这样,可以转化为每次我们可以删去 b b b中单一个字符或相邻两个相异的字符,问最少多少次删空(还需要多一次操作删空 a a a)。
这是个经典问题,令 b b b中字符出现次数最大值为 m x mx mx,那么答案即为 max ⁡ ( m x , ⌈ k 2 ⌉ ) \max(mx,\lceil \frac{k}{2} \rceil) max(mx,2k)。具体构造可以考虑当 m x ≤ k 2 mx\leq \frac{k}{2} mx2k时,随便删去两个相邻不同字符,否则只允许删去相邻两个不同字符且要求其中某个字符是该最大值对应的字符,剩下的一个个删。这可以用一个栈实现。
单组数据时间复杂度 O ( ∣ a ∣ ) \mathcal O(|a|) O(a)

#include <bits/stdc++.h>
#define FR first
#define SE second

using namespace std;

typedef pair<int,int> pr;

pr a[200005];
int cnt[26],size[200005];
int st[200005],addv[200005];

char str[200005]; 

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	memset(cnt,0,sizeof(cnt));
  	scanf("%s",str+1);
  	int n=strlen(str+1);
  	int sz=0;
  	for(int i=1;i<n;i++)
	  if (str[i]==str[i+1]) a[++sz]=pr(i,str[i]); 
	for(int i=0;i<=sz;i++) size[i]=addv[i]=0;
	for(int i=1;i<=sz;i++) cnt[a[i].SE-'a']++;
	int maxn=0;
	for(int i=0;i<26;i++) {
	  if (cnt[i]>cnt[maxn]) maxn=i;
	  size[cnt[i]]++;
    }
	int R=sz;
	while (!size[R]) R--;
	if (R>=((sz+1)>>1)) {
		printf("%d\n",R+1);
		int top=0;
		for(int i=1;i<=sz;i++) {
		  addv[i]=addv[i-1];
		  if (top&&(a[st[top]].SE==maxn+'a')!=(a[i].SE==maxn+'a')) {
		  	int u=st[top--];
		  	int l=a[u].FR-addv[u]+1,r=a[i].FR-addv[i];
		  	printf("%d %d\n",l,r);
		  	n-=r-l+1;
		  	addv[i]+=r-l+1;
		  }
		  else st[++top]=i;
	    }
	    for(int i=top;i>0;i--) {
	    	int u=st[i];
	    	int l=a[u].FR-addv[u]+1,r=n;
	    	printf("%d %d\n",l,r);
	    	n-=r-l+1;
		}
	}
	else {
		printf("%d\n",((sz+1)>>1)+1);
		int top=0,nsz=sz;
		bool v=0;
		for(int i=1;i<=sz;i++) {
			addv[i]=addv[i-1];
			if (!v&&top&&a[st[top]].SE!=a[i].SE) {
				int u=st[top--];
				int l=a[u].FR-addv[u]+1,r=a[i].FR-addv[i];
				printf("%d %d\n",l,r);
				n-=r-l+1;
				addv[i]+=r-l+1;
				nsz-=2;
				size[cnt[a[u].SE-'a']]--;
				cnt[a[u].SE-'a']--;
				size[cnt[a[u].SE-'a']]++;
				size[cnt[a[i].SE-'a']]--;
				cnt[a[i].SE-'a']--;
				size[cnt[a[i].SE-'a']]++;
				while (!size[R]) R--;
				if (R>=((nsz+1)>>1)) {
					maxn=0;
					for(int i=0;i<26;i++)
					  if (cnt[i]>cnt[maxn]) maxn=i;
					v=1;
				}
			}
			else if (top&&(a[st[top]].SE==maxn+'a')!=(a[i].SE==maxn+'a')) {
				int u=st[top--];
				int l=a[u].FR-addv[u]+1,r=a[i].FR-addv[i];
				printf("%d %d\n",l,r);
				n-=r-l+1;
				addv[i]+=r-l+1;
			}
			else st[++top]=i;
		}
	    for(int i=top;i>0;i--) {
	    	int u=st[i];
	    	int l=a[u].FR-addv[u]+1,r=n;
	    	printf("%d %d\n",l,r);
	    	n-=r-l+1;
		}
	}
	printf("%d %d\n",1,n);
  }
  return 0;
} 

E. Dreamoon Loves AA

感觉E比C水。。。
注意到若我们要求最终相邻距离落在区间 [ l , r ] [l,r] [l,r]内,那么对于一开始序列中距离为 L L L的相邻的一对 A A A来说,容易证明可能在中间新放的 A A A的数目在 [ ⌈ L r ⌉ − 1 , ⌊ L l ⌋ − 1 ] [\lceil \frac{L}{r} \rceil-1,\lfloor \frac{L}{l}\rfloor-1] [rL1,lL1]之间,注意若 ⌈ L r ⌉ > ⌊ L l ⌋ \lceil \frac{L}{r} \rceil>\lfloor \frac{L}{l}\rfloor rL>lL必然无解。
令一开始相邻两个 A A A的距离为 c i c_i ci 1 ≤ i ≤ m + 1 1\leq i\leq m+1 1im+1)。那么考虑最终合法的充要条件是 m ∈ [ ∑ i = 1 m + 1 ( ⌈ L r ⌉ − 1 ) , ∑ i = 1 m + 1 ( ⌊ L l ⌋ − 1 ) ] m \in [\sum_{i=1}^{m+1}(\lceil \frac{L}{r} \rceil-1),\sum_{i=1}^{m+1}(\lfloor \frac{L}{l}\rfloor-1)] m[i=1m+1(rL1),i=1m+1(lL1)],且 ∀ 1 ≤ i ≤ m + 1 \forall 1\leq i\leq m+1 1im+1 ⌈ L r ⌉ ≤ ⌊ L l ⌋ \lceil \frac{L}{r} \rceil\leq\lfloor \frac{L}{l}\rfloor rLlL
l l l的上界 u l ul ul r r r的下界 d r dr dr显然可以二分出来。那么若 u l ≥ d r ul\geq dr uldr,答案显然是 0 0 0。否则对于某个 1 ≤ i ≤ m + 1 1\leq i\leq m+1 1im+1,有 ⌈ L d r ⌉ − ⌊ L u l ⌋ ≤ 1 \lceil \frac{L}{dr} \rceil - \lfloor \frac{L}{ul}\rfloor\leq 1 drLulL1,对于 ⌈ L d r ⌉ − ⌊ L u l ⌋ = 1 \lceil \frac{L}{dr} \rceil - \lfloor \frac{L}{ul}\rfloor =1 drLulL=1 i i i,相当于给定了一组限制 [ u i , v i ] [u_i,v_i] [ui,vi],即最终的 [ l , r ] [l,r] [l,r]需要满足 l < u i l<u_i l<ui r > v i r>v_i r>vi。将所有的限制排下序,简单贪心即可。
单组数据时间复杂度 O ( m log ⁡ V ) \mathcal O(m\log V) O(mlogV)

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fLL
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pr; 

ll num[500005];

ll check1(int n,ll d) {
  ll s=0;
  for(int i=1;i<=n;i++) s+=num[i]/d-1;
  return s;
}

ll check2(int n,ll d) {
  ll s=0;
  for(int i=1;i<=n;i++) s+=(num[i]+d-1)/d-1;
  return s;
}

pr a[500005];

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	ll m,k;
  	int n;
  	scanf("%lld%d%lld",&m,&n,&k);
  	for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
  	num[++n]=m;
  	ll minn=inf,maxn=0;
  	for(int i=n;i>0;i--) {
	  num[i]-=num[i-1];
	  minn=min(minn,num[i]);
	  maxn=max(maxn,num[i]);
    }
    ll l=1,r=minn;
    while (l<r) {
    	ll mid=((l+r)>>1)+1;
    	if (check1(n,mid)>=k) l=mid; else r=mid-1;
	}
	ll nl=l;
	l=1;r=maxn;
	while (l<r) {
		ll mid=((l+r)>>1);
		if (check2(n,mid)<=k) r=mid; else l=mid+1;
	}
	ll nr=l;
	if (nl>=nr) {
		puts("0");
		continue;
	}
	int sz=0;
    for(int i=1;i<=n;i++)
      if (num[i]/nl<(num[i]+nr-1)/nr) {
      	ll t1=num[i]/nl,t2=(num[i]+nr-1)/nr;
      	a[++sz]=pr((num[i]+t1+1)/(t1+1),(num[i]-1)/(t2-1));
	  }
	a[++sz]=pr(1,nr-1);
	sort(a+1,a+sz+1);
	a[sz+1]=pr(nl+1,0);
	ll ans=inf;maxn=0;
	for(int i=1;i<=sz;i++) {
	  maxn=max(maxn,a[i].SE);
	  if (a[i].FR<a[i+1].FR) ans=min(ans,(maxn+1)-(a[i+1].FR-1));
    }
    printf("%lld\n",ans);
  }
  return 0;
} 
/*
1
10 2 0
2 4
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值