Codeforces Round 1284 简要题解

This blog provides concise explanations for the problems in Codeforces Round 1284. It covers topics like interval intersection, convex hull, graph theory, and network flows. The author discusses efficient algorithms with time complexities ranging from O(n log n) to O(n^2 log n)." 105208193,9371173,Seaborn教程:直方图与密度图绘制,"['Python', '数据可视化', 'Seaborn']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A. New Year and Naming

B. New Year and Ascent Sequence

C. New Year and Permutation

D. New Year and Conference

容易证明要判定是否存在venue-sensitive的非空子集,只需判定所有大小为 2 2 2的子集。也即判定是否存在区间 x ≠ y x\neq y x=y,使得 [ [ s a x , e a x ] ∩ [ s a y , e a y ] = ∅ ] ≠ [ [ s b x , s b x ] ∩ [ s b y , e b y ] = ∅ ] [[sa_x,ea_x]\cap[sa_y,ea_y]=\empty]\neq [[sb_x,sb_x]\cap[sb_y,eb_y]=\empty] [[sax,eax][say,eay]=]=[[sbx,sbx][sby,eby]=]
比较简单的做法是考虑异或哈希,对每个区间附上一个随机权值,分别求出每个区间在 a a a b b b中与它不相交的区间集合的哈希值,判定是否相等。注意到对于一个区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1],区间 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]与它不相交当且仅当 l 1 > r 2 l_1>r_2 l1>r2 r 1 < l 2 r_1<l_2 r1<l2。那么可以分别对区间按端点排序后做几次two pointer求出哈希值。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

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

using namespace std;

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

mt19937 rnd(time(0));

ull randull() {
  ull s=0;
  for(int i=0;i<4;i++)
    s=(s<<16)|((int)abs((int)rnd())%(1LL<<16));
  return s;
}

ull s1[100005],s2[100005],num[100005];
pr a[100005],b[100005],c[100005],d[100005];

int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++) {
  	int x1,y1,x2,y2;
  	scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
  	a[i]=pr(x1,i);
  	b[i]=pr(y1,i);
  	c[i]=pr(x2,i);
  	d[i]=pr(y2,i);
  	num[i]=randull();
  }
  sort(a+1,a+n+1);
  sort(b+1,b+n+1);
  sort(c+1,c+n+1);
  sort(d+1,d+n+1);
  int r=0;
  ull s=0;
  for(int i=1;i<=n;i++) {
  	while (r<n&&b[r+1].FR<a[i].FR) {
  		r++;
  		s^=num[b[r].SE];
	  }
	s1[a[i].SE]^=s;
  }
  int l=n+1;
  s=0;
  for(int i=n;i>0;i--) {
  	while (l>1&&a[l-1].FR>b[i].FR) {
  		l--;
  		s^=num[a[l].SE];
	  }
	s1[b[i].SE]^=s;
  }
  r=0;
  s=0;
  for(int i=1;i<=n;i++) {
  	while (r<n&&d[r+1].FR<c[i].FR) {
  		r++;
  		s^=num[d[r].SE];
	  }
	s2[c[i].SE]^=s;
  }
  l=n+1;
  s=0;
  for(int i=n;i>0;i--) {
  	while (l>1&&c[l-1].FR>d[i].FR) {
  		l--;
  		s^=num[c[l].SE];
	  }
	s2[d[i].SE]^=s;
  }
  for(int i=1;i<=n;i++)
    if (s1[i]!=s2[i]) {
    	puts("NO");
    	return 0;
	}
  puts("YES");
  return 0;
}

E. New Year and Castle Construction

考虑点 p p p可以在另 4 4 4个点 a , b , c , d a,b,c,d a,b,c,d所构成的某个多边形中的条件,可以发现这当且仅当 p p p a , b , c , d a,b,c,d a,b,c,d的凸包内成立。这里必要性显然,充分性只讨论 a , b , c , d a,b,c,d a,b,c,d的凸包大小为 3 3 3的情况,不妨设 d d d在三角形 a b c abc abc内,由于没有重点和三点共线,对该三角形以 d d d为中心作三角剖分,则 p p p必然落在其中某个三角形内,显然能构造出一个包含它的四边形。
直接对每个点计数它在多少个大小为 4 4 4的点集的凸包内有点困难,考虑计算反面,即它不在多少个大小为 4 4 4的点集的凸包内。这是比较容易的,枚举一个点 p p p,以 p p p为原点,对其他点按极角排序,枚举排序后选的第一个点 q q q,则要求剩余的 3 3 3个点极角序在 q q q后且与 q q q极角差 < π <\pi <π,这可以简单two pointer求解。
时间复杂度为 O ( n 2 log ⁡ n ) \mathcal O(n^2\log n) O(n2logn)

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

using namespace std;

typedef __int128 lll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pr;

struct Point {
  ll x,y;
  int quad; 
  Point() {}
  Point(ll a,ll b):x(a),y(b) {
  	if (x>=0&&y>0) quad=1;
  	else if (x<=0&&y>0) quad=2;
  	else if (x<0&&y<=0) quad=3;
  	else quad=4;
  }
  Point operator - (Point b) {return Point(x-b.x,y-b.y);}
};

inline lll cross(Point x,Point y) {
  return (lll)x.x*y.y-(lll)x.y*y.x;
}

bool cmp(Point x,Point y) {
  if (x.quad!=y.quad) return x.quad<y.quad;
  else return cross(x,y)>0;
}

Point p[3005],q[6005];

int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	p[i]=Point(x,y);
  }
  ll ans=(lll)n*(n-1)*(n-2)*(n-3)/24*(n-4);
  for(int i=1;i<=n;i++) {
  	int sz=0;
  	for(int j=1;j<=n;j++)
  	  if (j!=i) q[++sz]=p[j]-p[i];
  	sort(q+1,q+sz+1,cmp);
  	for(int j=1;j<=sz;j++) q[sz+j]=q[j];
  	int r=1;
  	for(int j=1;j<=sz;j++) {
  		if (r<j) r=j;
  		while (r<2*sz&&cross(q[j],q[r+1])>0) r++;
  		int len=r-j;
  		if (len>=3) ans-=(ll)len*(len-1)*(len-2)/6;
	  }
  }
  printf("%lld\n",ans);
  return 0;
}

F. New Year and Social Network

显然一条 T 1 T1 T1中的边 ( u 1 , v 1 ) (u_1,v_1) (u1,v1)能被一条 T 2 T2 T2中的边 ( u 2 , v 2 ) (u_2,v_2) (u2,v2)替换,当且仅当 T 1 T1 T1 u 2 u_2 u2 v 2 v_2 v2的简单路径包含边 ( u 1 , v 1 ) (u_1,v_1) (u1,v1)
事实上,一定存在一个 T 1 T1 T1中边与 T 2 T2 T2中边的完美匹配。证明可以考虑Hall定理,对于一个 T 1 T1 T1中边大小为 k k k的子集,删去这些边后会将 T 1 T1 T1分成 k + 1 k+1 k+1个连通块,考虑每个连通块的点集 S 1 , S 2 , . . . , S k + 1 S_1,S_2,...,S_{k+1} S1,S2,...,Sk+1,在 T 2 T2 T2上显然必须至少有 k k k条两个端点在不同点集中的边,这些边一定能替换 T 1 T1 T1中选的大小为 k k k的边子集中至少一条边。
考虑如何构造出一组解。我们每次从 T 2 T2 T2找一条边 ( u , v ) (u,v) (u,v),使得 u u u T 2 T2 T2叶子,尝试在 T 1 T1 T1中找一条边 ( u ′ , v ′ ) (u',v') (u,v)与它匹配。事实上,我们只需选择 T 1 T1 T1 u u u v v v的简单路径上第一条边 ( u , w ) (u,w) (u,w)即可。随后我们不再会用到点 u u u了,那么可以在 T 2 T2 T2上删除这个叶子,并且在 T 1 T1 T1上合并点 u u u和点 w w w(这样显然不会影响之后的匹配),归纳构造即可。
事实上显然没有必要每次在 T 1 T1 T1上缩点,只需标记每条边是否已经被用过了,每次在初始的 T 1 T1 T1上找到简单路径上第一条没用过的边即可。这可以简单地用LCT维护,不过我选择了一个比较好写的做法:用并查集维护所有的合并,每次倍增找到应该合并的边。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) O ( n log ⁡ n α ( n ) ) \mathcal O(n\log n\alpha(n)) O(nlognα(n))

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

using namespace std;

typedef __int128 lll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pr;

namespace SETS {

int fa[300005];

void init(int n) {
  for(int i=1;i<=n;i++) fa[i]=i;
}

int find_father(int x) {
  return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}

bool check(int x,int y) {
  x=find_father(x);y=find_father(y);
  return x!=y;
}

void merge(int x,int y) {
  x=find_father(x);y=find_father(y);
  if (x==y) return;
  fa[x]=y;
}

}

vector <int> e1[300005],e2[300005];
int id[300005],fa1[300005],dfs_cnt;

int fa2[300005][20],dep[300005];

void dfs1(int x) {
  id[++dfs_cnt]=x;
  for(int i=0;i<e1[x].size();i++)
    if (e1[x][i]!=fa1[x]) {
    	int u=e1[x][i];
    	fa1[u]=x;
    	dfs1(u);
	}
}

void dfs2(int x) {
  for(int i=0;i<e2[x].size();i++)
    if (e2[x][i]!=fa2[x][0]) {
    	int u=e2[x][i];
    	dep[u]=dep[x]+1;
		fa2[u][0]=x;
    	for(int j=1;j<20;j++) fa2[u][j]=fa2[fa2[u][j-1]][j-1];
    	dfs2(u);
	}
}

int lca(int x,int y) {
  if (dep[x]<dep[y]) swap(x,y);
  int d=dep[x]-dep[y];
  for(int i=0;i<20;i++)
    if ((d>>i)&1) x=fa2[x][i];
  if (x==y) return x;
  for(int i=19;i>=0;i--)
    if (fa2[x][i]!=fa2[y][i]) {
    	x=fa2[x][i];
    	y=fa2[y][i];
	}
  return fa2[x][0];
}

int jump(int x,int v,int d) {
  for(int i=19;i>=0;i--)
    if (fa2[x][i]&&dep[fa2[x][i]]>=d&&SETS::find_father(fa2[x][i])!=v) x=fa2[x][i];
  return x; 
}

int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e2[x].push_back(y);
  	e2[y].push_back(x);
  }
  dfs2(1);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e1[x].push_back(y);
  	e1[y].push_back(x);
  }
  dfs1(1);
  printf("%d\n",n-1);
  SETS::init(n);
  for(int i=n;i>1;i--) {
  	int x=id[i],y=fa1[x];
  	int p=lca(x,y);
  	if (SETS::check(x,p)) {
  		int u=SETS::find_father(x);
  		SETS::merge(u,fa2[u][0]);
  		printf("%d %d %d %d\n",u,fa2[u][0],x,y);
	  }
	else {
		int u=jump(y,SETS::find_father(x),dep[p]);
		SETS::merge(u,fa2[u][0]);
		printf("%d %d %d %d\n",u,fa2[u][0],x,y);
	}
  }
  return 0;
}

G. Seollal

一个挺神仙的题,范围比较有迷惑性。
问题可以描述为给定一个连通网格图,对图黑白染色( ( 1 , 1 ) (1,1) (1,1)为黑点),要求找出一棵生成树使得所有非 ( 1 , 1 ) (1,1) (1,1)的黑点都度数 > 1 >1 >1,或判定无解。事实上,标准算法可以扩展到一般无向图,且给定限制的点形成独立集的情况。
令给定限制的点集为 S S S。可以发现有解当且仅当能选出一个无环的边集,使得 S S S中每个点的度数恰为 2 2 2。这里必要性由于 S S S为独立集,可以在真实的生成树上保留一部分边得到;充分性考虑将选出的边集扩展为一棵生成树即可。
这个充要条件还是不好直接考虑。我们考虑理解为选出一个大小最大的无环边集,使得每条边有一个端点在 S S S中,且 S S S中每个点的度数不超过 2 2 2,问该最大大小是否为 2 ∣ S ∣ 2|S| 2S。限制选出的边集满足 S S S中每个点的度数不超过 2 2 2,这要求了选出的边集集合是一个度数限制拟阵的独立集(事实上无向图中选择一个边集,要求选出集合满足某个点独立集中每个点有度数上限都成立),这里的遗传性和交换性都很显然。那么问题可以理解为只考虑有一个端点在 ∣ S ∣ |S| S中的边集,求图拟阵与度数限制拟阵的交的最大独立集。这是一个不超过 2 n m 2nm 2nm条边的拟阵交问题,直接套用拟阵交算法即可。
单组数据视实现时间复杂度为 O ( n 3 m 3 ) \mathcal O(n^3m^3) O(n3m3) O ( n 3 m 3 α ( n ) ) \mathcal O(n^3m^3\alpha(n)) O(n3m3α(n))

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

using namespace std;

typedef pair<int,int> pr;

namespace SETS {

int fa[405];

void init(int n) {
  for(int i=1;i<=n;i++) fa[i]=i;
}

int find_father(int x) {
  return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}

bool check(int x,int y) {
  x=find_father(x);y=find_father(y);
  return x!=y;
}

void merge(int x,int y) {
  x=find_father(x);y=find_father(y);
  if (x==y) return;
  fa[x]=y;
}

}

char str[25][25];
int id[25][25];
bool kind[405];

int d[1005];
pr ee[1005];
bool in[1005];

vector <int> e[1005];
bool spos[1005],tpos[1005];

void build(int n,int m) {
  memset(d,0,sizeof(d));
  memset(spos,0,sizeof(spos));
  memset(tpos,0,sizeof(tpos));
  for(int i=1;i<=m;i++) vector<int>().swap(e[i]);
  for(int i=1;i<=m;i++)
    if (in[i]) {
    	if (kind[ee[i].FR]) d[ee[i].FR]++;
    	if (kind[ee[i].SE]) d[ee[i].SE]++;
	}
  for(int i=1;i<=m;i++)
    if (in[i]) {
    	SETS::init(n);
    	for(int j=1;j<=m;j++)
    	  if (in[j]&&j!=i) SETS::merge(ee[j].FR,ee[j].SE);
    	if (kind[ee[i].FR]) d[ee[i].FR]--;
    	if (kind[ee[i].SE]) d[ee[i].SE]--;
    	for(int j=1;j<=m;j++)
    	  if (!in[j]) {
    	  	int u=ee[j].FR,v=ee[j].SE;
    	  	if (!kind[u]&&!kind[v]) continue;
    	  	if (SETS::check(u,v)) e[i].push_back(j);
    	  	if ((!kind[u]||d[u]<2)&&(!kind[v]||d[v]<2)) e[j].push_back(i);
		  } 
    	if (kind[ee[i].FR]) d[ee[i].FR]++;
    	if (kind[ee[i].SE]) d[ee[i].SE]++;
	}
  SETS::init(n);
  for(int i=1;i<=m;i++)
    if (in[i]) SETS::merge(ee[i].FR,ee[i].SE);
  for(int i=1;i<=m;i++)
    if (!in[i]) {
    	int u=ee[i].FR,v=ee[i].SE;
    	if (!kind[u]&&!kind[v]) continue;
    	if (SETS::check(u,v)) spos[i]=1;
    	if ((!kind[u]||d[u]<2)&&(!kind[v]||d[v]<2)) tpos[i]=1;
	}
}

bool vis[1005];
int p[1005];
queue <int> q;

bool bfs(int n) {
  while (!q.empty()) q.pop();
  memset(vis,0,sizeof(vis));
  memset(p,0,sizeof(p));
  for(int i=1;i<=n;i++)
    if (spos[i]) {
      vis[i]=1;
	  q.push(i);
    }
  while (!q.empty()) {
  	int x=q.front();q.pop();
  	if (tpos[x]) {
  		for(int i=x;i;i=p[i]) in[i]^=1;
  		return 1;
	  }
	for(int i=0;i<e[x].size();i++)
	  if (!vis[e[x][i]]) {
	  	int u=e[x][i];
	  	vis[u]=1;
	  	p[u]=x;
	  	q.push(u);
	  }
  }
  return 0;
}

char ans[45][45];
pr pos[405];

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	memset(id,0,sizeof(id));
  	memset(kind,0,sizeof(kind));
  	int n,m;
  	scanf("%d%d",&n,&m);
  	for(int i=1;i<=n;i++) scanf("%s",str[i]+1);
  	int cnt=0;
  	for(int i=1;i<=n;i++)
  	  for(int j=1;j<=m;j++)
  	    if (str[i][j]=='O') {
		  id[i][j]=++cnt;
		  kind[cnt]=((i!=1||j!=1)&&(!((i+j)&1)));
		  pos[cnt]=pr(i,j);
	    }
	int sz=0,s=0;
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
        if (str[i][j]=='O') {
        	s+=kind[id[i][j]];
        	if (j<m&&str[i][j+1]=='O') ee[++sz]=pr(id[i][j],id[i][j+1]);
        	if (i<n&&str[i+1][j]=='O') ee[++sz]=pr(id[i][j],id[i+1][j]);
		}
	memset(in,0,sizeof(in));
	bool ok=1;
	for(int i=1;i<=2*s;i++) {
		build(cnt,sz);
		if (!bfs(sz)) {
			ok=0;
			break;
		}
	}
	if (!ok) {
		puts("NO");
		continue;
	}
	puts("YES");
	SETS::init(cnt);
	for(int i=1;i<=sz;i++)
	  if (in[i]) SETS::merge(ee[i].FR,ee[i].SE);
	for(int i=1;i<=sz;i++)
	  if (!in[i]&&SETS::check(ee[i].FR,ee[i].SE)) {
	  	in[i]=1;
	  	SETS::merge(ee[i].FR,ee[i].SE);
	  }
	memset(ans,0,sizeof(ans));
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++) ans[2*i-1][2*j-1]=str[i][j];
	for(int i=1;i<=sz;i++)
	  if (in[i]) {
	  	int u=ee[i].FR,v=ee[i].SE;
	  	if (pos[u].FR==pos[v].FR) 
		  ans[2*pos[u].FR-1][2*pos[u].SE]='O';
		else
		  ans[2*pos[u].FR][2*pos[u].SE-1]='O';
	  }
	for(int i=1;i<=2*n-1;i++) {
	  for(int j=1;j<=2*m-1;j++) putchar((ans[i][j])?ans[i][j]:' ');
	  putchar('\n');
    }
  }
  return 0;
}
Codeforces Round 894 (Div. 3) 是一个Codeforces举办的比赛,是第894轮的Div. 3级别比赛。它包含了一系列题目,其中包括题目E. Kolya and Movie Theatre。 根据题目描述,E. Kolya and Movie Theatre问题要求我们给定两个字符串,通过三种操作来让字符串a等于字符串b。这三种操作分别为:交换a中相同位置的字符、交换a中对称位置的字符、交换b中对称位置的字符。我们需要先进行一次预处理,替换a中的字符,然后进行上述三种操作,最终得到a等于b的结果。我们需要计算预处理操作的次数。 根据引用的讨论,当且仅当b[i]==b[n-i-1]时,如果a[i]!=a[n-i-1],需要进行一次操作;否则不需要操作。所以我们可以遍历字符串b的前半部分,判断对应位置的字符是否与后半部分对称,并统计需要进行操作的次数。 以上就是Codeforces Round 894 (Div. 3)的简要说明和题目E. Kolya and Movie Theatre的要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Codeforces Round #498 (Div. 3) (A+B+C+D+E+F)](https://blog.csdn.net/qq_46030630/article/details/108804114)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Codeforces Round 894 (Div. 3)A~E题解](https://blog.csdn.net/gyeolhada/article/details/132491891)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值