Codeforces Round 1290 简要题解

A. Mind Control

B. Irreducible Anagrams

C. Prefix Enlightenment

D. Coffee Varieties (hard version)

vp的时候智障的算错了常数,写了一个操作次数 2 n 2 k \frac{2n^2}{k} k2n2WA了,然后不想改了(虽然区别不大)。
考虑分治,定义solve(l,r)返回一个 l ∼ r l\sim r lr间的下标集合,使得 [ l , r ] [l,r] [l,r]中每种不同权值恰好在这个下标集合中对应出现一次。
l e n = r − l + 1 len=r-l+1 len=rl+1,这里有 l e n = 2 c len=2^c len=2c,当 l e n = k len=k len=k的时候我们可以直接 k k k次操作求解(每次加入一个看是否出现过,操作完后要清空)。
对于 l e n > k len>k len>k的,相当于要合并两个下标集合,其实只需要把两个集合并起来,再删去同时出现在两边的权值的其中一次出现即可。
一个暴力的实现是将左右下标集合各自 k 2 \frac{k}{2} 2k个分一块,每次取出一个左边的块插入所有数字,再取出一个右边的块依次插入看有没有出现过即可(操作完后要清空)。这样单层最多要 l e n 2 k \frac{len^2}{k} klen2次操作,易知总操作次数上界为 2 n 2 k \frac{2n^2}{k} k2n2
考虑稍微优化一下,仍然 k 2 \frac{k}{2} 2k个分一块,但是不急着清空。每次取出左边两个块和右边两个块,先加入左边第一个块,然后插入右边第一个块并查看是否出现过,再插入左边第二个块并查看是否出现过,再插入右边第二个块并查看是否出现过,最后再插一次左边第一个块。这样利用了只保留最后 k k k个的特性,不会同时存在自身的两次插入。注意最后剩下的散块要暴力处理。
简单分析可知这样的操作次数上界为 8 n 2 5 + O ( n ) \frac{8n^2}{5}+\mathcal O(n) 58n2+O(n)的,时间复杂度为 O ( n 2 ) \mathcal O(n^2) O(n2)

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f

using namespace std;

typedef long long ll;

int val[1025],q[1000005];
int L=1,R=0;
int vis[1025];

int n,k,sz;

int query(int y) {
  if (!y) {
  	/*L=1;
  	R=0;
  	memset(vis,0,sizeof(vis));
  	return 0;*/
  	puts("R");
  	fflush(stdout);
  	return 0;
  }
  else {
	sz++;
	assert(sz<=(3*n*n)/(2*k));
  	/*bool v=vis[val[y]];
  	vis[val[y]]++;
  	q[++R]=y;
  	if (R-L+1>k) {
  		vis[val[q[L]]]--;
  		L++;
	  }
	return v;*/
  	printf("? %d\n",y);
  	fflush(stdout);
  	char str[5];
  	scanf("%s",str);
  	return (str[0]=='Y');
  }
}

vector<int> solve(int l,int r) {
  if (r-l+1==k) {
  	vector<int> vt;
  	for(int i=l;i<=r;i++)
  	  if (!query(i)) vt.push_back(i);
  	query(0);
  	return vt;
  }
  int m=((l+r)>>1);
  vector<int> a=solve(l,m);
  vector<int> b=solve(m+1,r);
  bool cur[1025];
  memset(cur,0,sizeof(cur));
  for(int i=0;i<a.size();i+=k)
    for(int j=0;j<b.size();j+=k) {
    	if (i+k-1>=a.size()||j+k-1>=b.size()) {
    		for(int l=i;l<i+(k>>1)&&l<a.size();l++) query(a[l]);
    		for(int l=j;l<j+(k>>1)&&l<b.size();l++)
    		  if (query(b[l])) cur[b[l]]=1;
    		query(0);
    		for(int l=i;l<i+(k>>1)&&l<a.size();l++) query(a[l]);
    		for(int l=j+(k>>1);l<j+k&&l<b.size();l++)
    		  if (query(b[l])) cur[b[l]]=1;
    		query(0);
    		for(int l=i+(k>>1);l<i+k&&l<a.size();l++) query(a[l]);
    		for(int l=j;l<j+(k>>1)&&l<b.size();l++)
    		  if (query(b[l])) cur[b[l]]=1;
    		query(0);
    		for(int l=i+(k>>1);l<i+k&&l<a.size();l++) query(a[l]);
    		for(int l=j+(k>>1);l<j+k&&l<b.size();l++)
    		  if (query(b[l])) cur[b[l]]=1;
    		query(0);
		}
		else {
			for(int l=i;l<i+(k>>1);l++) query(a[l]);
			for(int l=j;l<j+(k>>1);l++)
			  if (query(b[l])) cur[b[l]]=1;
			for(int l=i+(k>>1);l<i+k;l++)
			  if (query(a[l])) cur[a[l]]=1;
			for(int l=j+(k>>1);l<j+k;l++)
			  if (query(b[l])) cur[b[l]]=1;
			for(int l=i;l<i+(k>>1);l++)
			  if (query(a[l])) cur[a[l]]=1;
			query(0);
		}
	}
  vector<int> vt;
  for(int i=0;i<a.size();i++)
    if (!cur[a[i]]) vt.push_back(a[i]);
  for(int i=0;i<b.size();i++)
    if (!cur[b[i]]) vt.push_back(b[i]);
  return vt;
}

int main() {
  scanf("%d%d",&n,&k);
  //for(int i=1;i<=n;i++) scanf("%d",&val[i]);
  if (k==1) {
  	int s=0;
  	for(int i=1;i<=n;i++) {
  	  bool ok=1;
  	  for(int j=i+1;j<=n;j++) {
  	  	  query(i);
  	  	  if (query(j)) ok=0;
  	  	  query(0);
  	  	  if (!ok) break;
		}
	  s+=ok;
	}
	printf("! %d\n",s);
	fflush(stdout);
	return 0;
  } 
  vector<int> ans=solve(1,n);
  printf("! %d\n",(int)ans.size());
  fflush(stdout);
  return 0;
} 
/*
4 2
1 4 1 3
*/

E. Cartesian Tree

答案等价于计算点对 ( i , j ) (i,j) (i,j)的数目,使得点 j j j在点 i i i的子树中。根据笛卡尔树的性质, j j j i i i的子树中当且仅当在序列上 i i i j j j所在位置之间最大值为 i i i
考虑分治,对于一个最终序列中的区间 [ l , r ] [l,r] [l,r],求出 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r]之间的贡献。根据上面的讨论,这里显然只需要关注在区间中的元素,且可以发现一个右边的点 a j a_j aj会在左边的点 a i a_i ai的子树中当且仅当 a i = max ⁡ k = i m i d a k a_i=\max_{k=i}^{mid}a_k ai=maxk=imidak a i > max ⁡ k = m i d + 1 j a k a_i>\max_{k=mid+1}^{j}a_k ai>maxk=mid+1jak,反之亦然。那么可以发现一个左边的元素 a i a_i ai会与右边的点有贡献当且仅当它是 [ i , m i d ] [i,mid] [i,mid]中的最大值,且有贡献的 j j j是右边的前缀。
考虑按插入时间从小到大维护对答案的贡献。维护两个从中间向两侧的元素单调栈(即保存左边的后缀最大值和右边的前缀最大值),同时用树状数组维护当前所有元素,支持查询区间元素个数。不放设现在在左边插入了一个最大值 a x a_x ax,那么我们会从左边的单调栈中弹出所有在 x x x左侧的后缀最大值,并减去它们在右边的子树大小和,这可以在右边的单调栈中二分出它们在右边的贡献前缀,并用树状数组查询个数完成。同时也要减去右边的前缀最大值对左边的贡献,这部分可以考虑反过来统计,由于 a i a_i ai在右边的祖先数目只跟 max ⁡ j = i m i d a j \max_{j=i}^{mid}a_j maxj=imidaj有关,那么可以对刚刚弹出的后缀最大值划分出的每一段分别计算,用树状数组算出左边的个数,在右边单调栈二分即可。最后还要记得加上 x x x在右边的子树大小,也即右边当前元素个数。
时间复杂度为 O ( n log ⁡ 2 n ) \mathcal O(n\log^2n) O(nlog2n)

#include <bits/stdc++.h>
#define lowbit(x) (x&-x)

using namespace std;

typedef long long ll;

int sumv[200005];

void add(int x,int n,int num) {
  for(;x<=n;x+=lowbit(x)) sumv[x]+=num;
}

int sum(int x) {
  int s=0;
  for(;x;x-=lowbit(x)) s+=sumv[x];
  return s;
}

int sum(int l,int r) {
  return sum(r)-sum(l-1);
}


int num[200005],pos[200005],n;
ll ans[200005];

int stl1[200005],stl2[200005];
int str1[200005],str2[200005];
int cur[200005];

void solve(int l,int r) {
  if (l==r) return;
  int m=((l+r)>>1);
  solve(l,m);
  solve(m+1,r);
  int top1=0,top2=0,sz=0;
  for(int i=l;i<=r;i++) cur[++sz]=num[i];
  sort(cur+1,cur+sz+1);
  int lsz=0,rsz=0;
  for(int i=1;i<=sz;i++) {
  	int x=cur[i],y=pos[x];
  	if (y<=m) {
  		lsz++;
  		int rx=upper_bound(stl2+1,stl2+top1+1,-y)-stl2-1;
  		if (rsz) {
  			for(int j=rx+1;j<=top1;j++) {
  				int t=upper_bound(str1+1,str1+top2+1,stl1[j])-str1;
  				if (t>top2) ans[x]-=sum(m+1,r);
  				else if (t) ans[x]-=sum(m+1,str2[t]-1); 
  				if (t<=top2) ans[x]-=(ll)sum(((j==top1)?l:-stl2[j+1]+1),-stl2[j])*(top2-t+1);
			  }
			if (rx) {
				int t=upper_bound(str1+1,str1+top2+1,stl1[rx])-str1;
				if (t<=top2) ans[x]-=(ll)sum(((rx==top1)?l:-stl2[rx+1]+1),y)*(top2-t+1);
			}
			ans[x]+=rsz;
		  }
		top1=rx+1;
		stl1[top1]=x;
		stl2[top1]=-y;
	  }
	else {
		rsz++;
		int rx=upper_bound(str2+1,str2+top2+1,y)-str2-1;
		if (lsz) {
			for(int j=rx+1;j<=top2;j++) {
				int t=upper_bound(stl1+1,stl1+top1+1,str1[j])-stl1;
				if (t>top1) ans[x]-=sum(l,m);
				else if (t) ans[x]-=sum(-stl2[t]+1,m);
				if (t<=top1) ans[x]-=(ll)sum(str2[j],((j==top2)?r:str2[j+1]-1))*(top1-t+1);
			}
			if (rx) {
				int t=upper_bound(stl1+1,stl1+top1+1,str1[rx])-stl1;
				if (t<=top1) ans[x]-=(ll)sum(y,((rx==top2)?r:str2[rx+1]-1))*(top1-t+1);
			}
			ans[x]+=lsz;
		}
		top2=rx+1;
		str1[top2]=x;
		str2[top2]=y;
	}
	add(y,n,1);
  }
  for(int i=l;i<=r;i++) add(i,n,-1);
}

int main() {
  scanf("%d",&n);
  for(int i=1;i<=n;i++) {
  	scanf("%d",&num[i]);
  	pos[num[i]]=i;
  }
  solve(1,n);
  for(int i=1;i<=n;i++) ans[i]+=ans[i-1];
  for(int i=1;i<=n;i++) printf("%lld\n",ans[i]+i);
  return 0;
}

F. Making Shapes

首先可以发现答案相当于统计所有可表示出的横纵坐标差 ≤ m \leq m m的凸多边形数目。由于没有平行向量,因此只需要考虑每种向量所用的数目。那么可以发现,这等价于统计这样的集合 ( c 1 , c 2 , ⋯   , c n ) (c_1,c_2,\cdots,c_n) (c1,c2,,cn)的数目( c i ≥ 0 c_i\geq 0 ci0 c i c_i ci不全为 0 0 0),使得:
{ ∑ x i > 0 c i x i = − ∑ x i < 0 c i x i ∑ y i > 0 c i y i = − ∑ y i < 0 c i y i ∑ x i > 0 c i x i ≤ m ∑ y i > 0 c i y i ≤ m \begin{cases} \sum_{x_i>0}c_ix_i &= -\sum_{x_i<0}c_ix_i\\ \sum_{y_i>0}c_iy_i &= -\sum_{y_i<0}c_iy_i\\ \sum_{x_i>0}c_ix_i &\leq m \\ \sum_{y_i>0}c_iy_i &\leq m \\ \end{cases} xi>0cixiyi>0ciyixi>0cixiyi>0ciyi=xi<0cixi=yi<0ciyimm
原因是这样的每个集合显然能唯一对应一个凸多边形(按方位角排序后依次加入),且不同集合对应的两两不同。
那么转化为一个计数问题,考虑按位DP。令 4 4 4个方向的长度和分别为 s u x , s d x , s u y , s d y sux,sdx,suy,sdy sux,sdx,suy,sdy,那么令 F [ i ] [ u x ] [ d x ] [ u y ] [ d y ] [ u ] [ v ] F[i][ux][dx][uy][dy][u][v] F[i][ux][dx][uy][dy][u][v]表示考虑了每种向量数目 c c c的最后 i i i位,且满足 s u x sux sux s d x sdx sdx i i i位相同, s u y suy suy s d y sdy sdy i i i位相同, s u x , s d x , s u y , s d y sux,sdx,suy,sdy sux,sdx,suy,sdy分别向前的进位为 u x , d x , u y , d y ux,dx,uy,dy ux,dx,uy,dy,同时 u u u v v v分别表示了 s u x sux sux s u y suy suy的后 i i i位是否不超过 m m m的后 i i i位。令 max ⁡ ( ∣ x i ∣ , ∣ y i ∣ ) = w \max(|x_i|,|y_i|)=w max(xi,yi)=w,题目保证 w ≤ 4 w\leq 4 w4,那么容易证明任意时刻均有 u x , d x , u y , d y ≤ n w ux,dx,uy,dy\leq nw ux,dx,uy,dynw
转移有两种方式,一种是直接 O ( 2 n ) \mathcal O(2^n) O(2n)枚举所有 c c c的第 i i i位转移,一种是依次DP每种 c c c的第 i i i位。理论复杂度上第二种更优秀,但此时 u x , d x , u y , d y ux,dx,uy,dy ux,dx,uy,dy的上界会翻倍,实际效率可能比第一种慢近百倍。
时间复杂度为 O ( 2 n n 4 w 4 log ⁡ m ) \mathcal O(2^nn^4w^4\log m) O(2nn4w4logm) O ( n 5 w 4 log ⁡ m ) \mathcal O(n^5w^4\log m) O(n5w4logm)。这里的代码是第二种的。

#include <bits/stdc++.h>
#define MOD 998244353

using namespace std;

typedef long long ll;

inline void add(int &x,int y) {
  ((x+=y)>=MOD)?x-=MOD:0;
}

int f[2][41][41][41][41][4]; 
int a[10],b[10];

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
  int cur=0,up=(n<<3);
  f[cur][0][0][0][0][0]=1;
  for(int i=0;i<=30;i++) {
  	for(int j=1;j<=n;j++) {
  		int ux=((a[j]>0)?a[j]:0),dx=((a[j]<0)?-a[j]:0);
  		int uy=((b[j]>0)?b[j]:0),dy=((b[j]<0)?-b[j]:0);
  		cur^=1;
  		memcpy(f[cur],f[cur^1],sizeof(f[cur]));
  		for(int t1=0;t1<=up;t1++)
  		for(int t2=0;t2<=up;t2++)
  		for(int t3=0;t3<=up;t3++)
  		for(int t4=0;t4<=up;t4++)
  		for(int t5=0;t5<4;t5++)
  		  if (f[cur^1][t1][t2][t3][t4][t5]) 
			add(f[cur][t1+ux][t2+dx][t3+uy][t4+dy][t5],f[cur^1][t1][t2][t3][t4][t5]);
	  }
  	bool v=((m>>i)&1);
  	cur^=1;
  	memset(f[cur],0,sizeof(f[cur]));
  	for(int t1=0;t1<=up;t1++)
  	for(int t2=0;t2<=up;t2++)
  	for(int t3=0;t3<=up;t3++)
  	for(int t4=0;t4<=up;t4++) {
  		if (((t1^t2)&1)||((t3^t4)&1)) continue;
  		for(int t5=0;t5<4;t5++)
  		  if (f[cur^1][t1][t2][t3][t4][t5]) {
  		  	  if (!v) 
				  add(f[cur][t1>>1][t2>>1][t3>>1][t4>>1][t5|(t1&1)|((t3&1)<<1)],f[cur^1][t1][t2][t3][t4][t5]);
			  else 
			      add(f[cur][t1>>1][t2>>1][t3>>1][t4>>1][t5&((t1&1)^((t3&1)<<1))],f[cur^1][t1][t2][t3][t4][t5]);
			}
	  }
  }
  printf("%d\n",(f[cur][0][0][0][0][0]-1+MOD)%MOD);
  return 0;
}
/*
5 1000000000
-2 4
2 -3
0 -4
2 4
-1 -3
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值