Codeforces Round #545 上红记

cf的账号注册一年多了,但是比赛打得很少,去年暑假才开始正式打比赛。但平时学校的作息时间又跟div1的时间冲突,导致少有机会参加,于是就上不了分。之前曾经想寒假上分,结果只有一场div1,最后也没上多少分。
昨天突然发现下午有一场div1,于是拉着dcx报了名。等到比赛快开始了有点慌,害怕爆炸(有一次div1卡B卡了一个半小时)。
比赛开始,秒了A和B(赛后听说B hack点很多)。然后开了C,我还在读题dcx神仙就秒了,听他讲了好一会才明白,就是缩点后记忆化搜索。码码码,码完发现他炸了几发才PP,有点虚,让他帮我读了几遍代码,感觉tarjan和记忆化搜索都没炸,交上去PP了,感觉稳了(flag)。
这个D是个恶心交互,想了好久不会,结果dcx又秒了。感觉这场全靠他带飞。
看了下榜,好像我们都在前五啊,被吓到了。看下时间还剩1h,开了下E,想了会发现是个维护凸壳的sb题。写了一会PP了,dcx好像头晕烟花,代码混乱,帮他排了一会错也PP了。
这时后还有15min,看了会代码打算弃疗,突然发现TLE再交了一发E,冷静看了眼代码,发现惊天bug——没开long double,赶快改了再交一发,还剩1min,罚时爆炸。
赛后很紧张,等评测等了好久,又听wxhtxdy说这个E用long double可能爆炸,虚到不行。
发现C题fst了(场后看了下,估计是5e6dfs爆栈了),以为E也要fst,然而没有。dcx没有fst,rk6太强了。
于是愉快上红了,dcx由2100+狂飙上红,实在恐怖。
在这里插入图片描述

简要题解

A Skyscrapers

没啥好说的,排序+二分。

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

using namespace std;

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

int a[1005][1005],b[1005][1005];
int num[1005][1005];

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++) scanf("%d",&num[i][j]);
  for(int i=1;i<=n;i++) {
  	for(int j=1;j<=m;j++) a[i][j]=num[i][j];
  	sort(a[i]+1,a[i]+m+1);
  	a[i][0]=unique(a[i]+1,a[i]+m+1)-a[i]-1;
  }
  for(int i=1;i<=m;i++) {
  	for(int j=1;j<=n;j++) b[i][j]=num[j][i];
  	sort(b[i]+1,b[i]+n+1);
  	b[i][0]=unique(b[i]+1,b[i]+n+1)-b[i]-1;
  }
  for(int i=1;i<=n;i++) {
    for(int j=1;j<=m;j++) {
    	int t1=lower_bound(a[i]+1,a[i]+a[i][0]+1,num[i][j])-a[i];
    	int t2=lower_bound(b[j]+1,b[j]+b[j][0]+1,num[i][j])-b[j];
    	printf("%d ",max(t1,t2)+max(a[i][0]-t1+1,b[j][0]-t2+1)-1);
	}
	printf("\n");
  }
  return 0;
}
B Camp Schedule

最优方案显然是先放一个完整的B,然后每次跳next后尝试放剩下的部分。
似乎很多人hash被卡了?

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

using namespace std;

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

char s1[500005],s2[500005];

int next[500005];

void kmp(int n) {
  next[0]=next[1]=0;
  for(int i=1,j=0;i<n;i++) {
  	while (j&&s2[j]!=s2[i]) j=next[j];
  	if (s2[j]==s2[i]) j++;
  	next[i+1]=j;
  }
}

int main() {
  scanf("%s%s",s1,s2);
  int n=strlen(s1),m=strlen(s2);
  kmp(m);
  int a=0,b=0;
  for(int i=0;i<n;i++)
    if (s1[i]=='0') a++; else b++;
  int c=0,d=0;
  for(int i=0;i<m;i++)
    if (s2[i]=='0') c++; else d++;
  if (a<c||b<d) {
  	puts(s1);
  	return 0;
  }
  for(int i=0;i<m;i++) s1[i]=s2[i];
  a-=c;b-=d;
  for(int i=0;i<next[m];i++)
    if (s2[i]=='0') c--; else d--;
  int cur=m;
  while (a>=c&&b>=d) {
  	for(int i=next[m];i<m;i++) s1[cur++]=s2[i];
  	a-=c;b-=d;
  }
  while (a) {
  	s1[cur++]='0';
  	a--;
  }
  while (b) {
  	s1[cur++]='1';
  	b--;
  }
  puts(s1);
  return 0;
}
C Museums Tour

首先将强连通分量缩点。
将状态设为 ( x , y ) (x,y) ,表示从点 x x 出发,初始时间 y ( m o d   d ) \equiv y (mod\ d) 的最大答案,可以bfs出同个强连通分量中的可达状态,容易证明它们的答案是一样的。
于是只用预处理后在拓扑图上dp就好了,场上的记忆化搜索有点挫,被卡了。

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

using namespace std;

typedef pair<int,int> pr;

struct Edge {
  int t,next;
  Edge() {}
  Edge(int a,int b):t(a),next(b) {}
};

Edge e[100005];
int head[100005],d;
char str[100005][51];

int dfn[100005],low[100005],dfs_cnt;
int scc[100005],scc_cnt;
bool in[100005];
int st[100005],top;

vector <int> num[100005];

void tarjan(int x) {
  dfn[x]=low[x]=++dfs_cnt;
  st[++top]=x;in[x]=1;
  for(int i=head[x];i;i=e[i].next) {
  	int u=e[i].t;
  	if (!dfn[u]) {
  		tarjan(u);
  		low[x]=min(low[x],low[u]);
	  }
	else if (in[u]) low[x]=min(low[x],dfn[u]);
  }
  if (low[x]>=dfn[x]) {
  	scc_cnt++;
  	int t;
  	do {
  		t=st[top--];
  		in[t]=0;
  		scc[t]=scc_cnt;
  		num[scc[t]].push_back(t);
	  } while (t!=x);
  }
}

pr q[5000005];
int vis[100005][51],vis_cnt;
int vis2[100005],cnt;

int f[100005][51];

void bfs(int sx,int sy) {
  int l=1,r=0;
  q[++r]=pr(sx,sy);
  vis[sx][sy]=++vis_cnt;
  while (l<=r) {
  	pr t=q[l++];
  	int x=t.FR,y=t.SE;
  	for(int i=head[x];i;i=e[i].next)
  	  if (scc[e[i].t]==scc[x]&&!vis[e[i].t][(y+1)%d]) {
  	  	  vis[e[i].t][(y+1)%d]=vis_cnt;
  	  	  q[++r]=pr(e[i].t,(y+1)%d);
		}
  }
  int s=0;
  cnt++;
  for(int i=1;i<=r;i++)
    if (str[q[i].FR][q[i].SE]=='1'&&vis2[q[i].FR]!=cnt) {
    	vis2[q[i].FR]=cnt;
    	s++;
	}
  for(int i=1;i<=r;i++) f[q[i].FR][q[i].SE]=s;
}

int dfs(int x,int y) {
  if (vis[x][y]) return f[x][y];
  bfs(x,y);
  int ans=0;
  for(int i=0;i<num[scc[x]].size();i++) {
  	int u=num[scc[x]][i];
  	for(int j=0;j<d;j++)
  	  if (vis[u][j]==vis[x][y]) {
  	  	  for(int k=head[u];k;k=e[k].next)
  	  	    if (scc[e[k].t]!=scc[x]) ans=max(ans,dfs(e[k].t,(j+1)%d));
		}
  }
  for(int i=0;i<num[scc[x]].size();i++) {
  	int u=num[scc[x]][i];
  	for(int j=0;j<d;j++)
  	  if (vis[u][j]==vis[x][y]) f[u][j]+=ans;
  }
  return f[x][y];
}

int main() {
  int n,m;
  scanf("%d%d%d",&n,&m,&d);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[i]=Edge(y,head[x]);
  	head[x]=i;
  }
  for(int i=1;i<=n;i++) scanf("%s",str[i]);
  for(int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i);
  int ans=dfs(1,0);
  printf("%d\n",ans);
  return 0;
}
D Cooperative Game

首先让8个人留在原点,另外两个人一个人一次走一步,另一个人两次走一步,这样第一个人一定会追上第二个人(似乎叫floyd判圈算法,就是pollard rho里面那个)。
但我们要让他们走到终点,假设相遇时的位置是 r r ,第二个人走了 k k 步,那么 2 k k ( m o d   c ) 2k\equiv k (mod\ c) ,即 k 0 ( m o d   c ) k \equiv 0 (mod \ c) ,又有 t + r k ( m o d   c ) t+r\equiv k (mod \ c) ,那么 t r ( m o d c ) t\equiv -r (mod c) ,因此只要让所有人同时走,第一次汇合的位置一定是终点。
这样的步数不超过 3 t + 2 c 3t+2c

#include <bits/stdc++.h>

using namespace std;

vector<int> query() {
  fflush(stdout);
  vector <int> ans;
  ans.resize(10);
  int k;
  scanf("%d",&k);
  for(int i=1;i<=k;i++) {
  	char str[10];
  	scanf("%s",str);
  	int len=strlen(str);
  	for(int j=0;j<len;j++) ans[str[j]-'0']=i;
  }
  return ans;
}

int main() {
  vector<int> cur;
  cur.resize(10);
  do {
  	puts("next 0 1");
  	cur=query();
  	puts("next 0");
  	cur=query();
  } while (cur[0]!=cur[1]);
  while (cur[2]!=cur[1]) {
  	puts("next 0 1 2 3 4 5 6 7 8 9");
  	cur=query();
  }
  puts("done");
  return 0;
}
E Train Car Selection

因为 b &gt; 0 , s &gt; 0 b&gt;0,s&gt;0 ,每次插入的只有最左边的人是有用的。
如果在最左边插入一个人,那么原来的人都没有用了。
因此只用维护一段连续的右端插入操作,容易发现只有在下凸壳上的点是有用的,因此可以直接维护凸壳三分。
其实由于每次查询的 s s 也是单调的,也可以用单调栈维护做到线性。

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

using namespace std;

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

struct Point {
  ll x,y;
  Point() {}
  Point(ll a,ll b):x(a),y(b) {}
  Point operator - (Point b) {return Point(x-b.x,y-b.y);}
};

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

inline ll calc(Point x,ll a,ll b) {
  return x.y+a*x.x+b;
}

Point st[300005];
int top;

void insert(Point p) {
  while (top>1&&cross(p-st[top],st[top]-st[top-1])>=0) top--;
  st[++top]=p;
}

pr query(ll a,ll b) {
  int l=1,r=top;
  while (r-l>2) {
  	int m1=l+(r-l)/3,m2=r-(r-l)/3;
  	if (calc(st[m1],a,b)<calc(st[m2],a,b)) r=m2; else l=m1;
  }
  if (l>1) l--;
  ll ans1=inf,ans2=0;
  for(int i=l;i<=r;i++)
    if (calc(st[i],a,b)<ans1) {
    	ans1=calc(st[i],a,b);
    	ans2=st[i].x;
	}
  return pr(ans2,ans1);
} 

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  ll curl=0,curr=n-1;
  st[top=1]=Point(0,0);
  ll a=0,b=0;
  for(int i=1;i<=m;i++) {
  	int kind;
  	scanf("%d",&kind);
  	if (kind==1) {
  		int x;
  		scanf("%d",&x);
  		curl-=x;
  		st[top=1]=Point(curl,0);
  		a=b=0;
	  }
	else if (kind==2) {
		int x;
		scanf("%d",&x);
		insert(Point(curr+1,-a*(curr+1)-b));
		curr+=x;
	}
	else {
		ll x,y;
		scanf("%lld%lld",&y,&x);
		a+=x;b+=y-curl*x;
	}
	pr t=query(a,b);
	printf("%lld %lld\n",t.FR-curl+1,t.SE);
  }
  return 0;
}
F Matches Are Not a Child’s Play

容易发现最后被删除的一定是值最大的节点,那么就可以把无根树转成有根树了。
x x 在点 y y 之前被删除,当且仅当它不是 y y 的祖先,并且 x x 的子树最大值不超过 y y 的子树最大值。
发现子树最大值相同的点形成了若干条链,如果没有修改的话可以直接维护。
考虑修改操作,由于每次一定会改成最大值,所以等价于一个换根操作,并且会整体修改到原来的根上一段路径的子树最大值。
这显然可以用LCT+树状数组维护,具体来说,就是把子树最大值相同的一段链用一棵splay维护,每次access时直接合并,并更新树状数组。

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

using namespace std;

int sumv[400005];

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

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

struct Edge {
  int t,next;
  Edge() {}
  Edge(int a,int b):t(a),next(b) {}
};

Edge e[400005];
int head[200005];

namespace LCT {

int ch[200005][2],fa[200005];
int size[200005],tag[200005];
bool rev[200005];

inline void pushdown(int o) {
  if (rev[o]) {
  	rev[ch[o][0]]^=1;
  	rev[ch[o][1]]^=1;
  	swap(ch[o][0],ch[o][1]);
  	rev[o]=0;
  }
}

inline void pushup(int o) {
  size[o]=size[ch[o][0]]+size[ch[o][1]]+1;
}

inline bool isroot(int o) {
  return ch[fa[o]][0]!=o&&ch[fa[o]][1]!=o;
}

void rotate(int x) {
  int y=fa[x],z=fa[y],d=(ch[y][1]==x);
  if (!isroot(y)) {
  	if (ch[z][0]==y) ch[z][0]=x; else ch[z][1]=x;
  }
  fa[x]=z;fa[y]=x;fa[ch[x][d^1]]=y;
  ch[y][d]=ch[x][d^1];ch[x][d^1]=y;
  pushup(y);
}

int st[200005],top;

void splay(int x) {
  st[++top]=x;
  for(int i=x;!isroot(i);i=fa[i]) st[++top]=fa[i];
  swap(tag[x],tag[st[top]]);
  for(;top;top--) pushdown(st[top]);
  while (!isroot(x)) {
  	int y=fa[x],z=fa[y];
  	if (!isroot(y)) {
  		if ((ch[y][0]==x)^(ch[z][0]==y)) rotate(x); else rotate(y);
	  }
	rotate(x);
  }
  pushup(x);
}

void access(int x) {
  int y=0;
  while (x) {
  	splay(x);
  	tag[ch[x][1]]=tag[x];ch[x][1]=0;
  	pushup(x);
  	add(tag[x],-size[x]);
  	ch[x][1]=y;tag[y]=0;
  	pushup(x);
  	y=x;x=fa[x];
  }
}

void beroot(int x,int y) {
  access(x);
  splay(x);
  tag[x]=y;
  rev[x]^=1;
  pushdown(x);
  add(y,1);
  if (ch[x][1]) {
  	tag[ch[x][1]]=y-1;
  	add(y-1,size[ch[x][1]]);
  	ch[x][1]=0;
  }
}

int query1(int x) {
  splay(x);
  return sum(tag[x])-size[ch[x][0]];
}

int findroot(int x) {
  splay(x);
  while (ch[x][0]) {
  	x=ch[x][0];
  	pushdown(x);
  }
  splay(x);
  return x;
}

int query2(int x,int y) {
  if (findroot(x)!=findroot(y)) {
  	splay(x);
  	splay(y);
  	return (tag[x]<tag[y])?x:y;
  }
  else {
  	splay(x);
  	int s1=size[ch[x][1]];
  	splay(y);
  	int s2=size[ch[y][1]];
  	return (s1<s2)?x:y;
  }
}

void dfs(int x) {
  tag[x]=x;size[x]=1;
  for(int i=head[x];i;i=e[i].next)
    if (e[i].t!=fa[x]) {
    	int u=e[i].t;
    	fa[u]=x;
    	dfs(u);
    	tag[x]=max(tag[x],tag[u]);
	}
  add(tag[x],1);
  for(int i=head[x];i;i=e[i].next)
    if (e[i].t!=fa[x]) {
    	int u=e[i].t;
    	splay(u);
    	if (tag[x]==tag[u]) {
    		ch[x][1]=u;
    		tag[u]=0;
    		pushup(x);
		}
	}
  splay(x);
}

}

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[2*i-1]=Edge(y,head[x]);
  	head[x]=2*i-1;
  	e[2*i]=Edge(x,head[y]);
  	head[y]=2*i;
  }
  LCT::dfs(n);
  int cur=n;
  for(int i=1;i<=m;i++) {
  	char str[10];
  	scanf("%s",str);
  	if (str[0]=='u') {
  		int x;
  		scanf("%d",&x);
  		LCT::beroot(x,++cur);
	  }
	else if (str[0]=='w') {
		int x;
		scanf("%d",&x);
		printf("%d\n",LCT::query1(x));
	}
	else {
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",LCT::query2(x,y));
	}
  }
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38609262/article/details/88364613