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
首先将强连通分量缩点。
将状态设为
,表示从点
出发,初始时间
的最大答案,可以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里面那个)。
但我们要让他们走到终点,假设相遇时的位置是
,第二个人走了
步,那么
,即
,又有
,那么
,因此只要让所有人同时走,第一次汇合的位置一定是终点。
这样的步数不超过
。
#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
因为
,每次插入的只有最左边的人是有用的。
如果在最左边插入一个人,那么原来的人都没有用了。
因此只用维护一段连续的右端插入操作,容易发现只有在下凸壳上的点是有用的,因此可以直接维护凸壳三分。
其实由于每次查询的
也是单调的,也可以用单调栈维护做到线性。
#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
容易发现最后被删除的一定是值最大的节点,那么就可以把无根树转成有根树了。
点
在点
之前被删除,当且仅当它不是
的祖先,并且
的子树最大值不超过
的子树最大值。
发现子树最大值相同的点形成了若干条链,如果没有修改的话可以直接维护。
考虑修改操作,由于每次一定会改成最大值,所以等价于一个换根操作,并且会整体修改到原来的根上一段路径的子树最大值。
这显然可以用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;
}