Codeforces Round 1284 简要题解

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_1r1<l2。那么可以分别对区间按端点排序后做几次two pointer求出哈希值。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include 
#include 
#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 
#include 
#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 
#include 
#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 
#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,匹配相关,拟阵)