8.11 模拟赛记录
复盘
惯例开局遍历。
T1简单的我有点不敢想象,把一个数移到前面,等同于在前面复制一遍这个数,并忽略掉后面那个。为了方便,翻转这个序列,每次把要移的数移动到最后。移动都结束后,从后往前遍历,每一种数取最靠后出现的输出。时间复杂度O(m+n),完全能过。
考虑到T1如此简单,不如拿来当缓冲,于是先往下看。T2好像是小根堆,但是维护策略想不出来,先放弃。T3看到两个特殊限制,一个并查集可以解决,另一个倍增LCA,但是不太好写。T4第一反应二维树状数组前缀和暴力,但是二维树状数组我不熟悉,也不太好写,于是还是决定回头做T1。这时候是8:30左右。
T1不到20分钟轻松加愉快结束了,然后鉴于时间充裕,打算考虑一下能否写出T3的正解,但是各种各样的思路都试了一遍,并不能突破2000的瓶颈,还是止步于200.就这样一直卡到大约9:40,觉得必须开始写了(T4肯定得留至少一个小时),于是直接奔着60分部分分去了。为了同时判断和做两个子任务,我把并查集和倍增LCA强行写到了一起,毕竟两者本身不冲突。经过一系列缝合和复杂的子任务判定,自己出了个数据调了调,大约就快到11:00了,于是结束此题,直接做T4。
不得不说我对树状数组掌握实属不行,二维树状数组卡了特别长的时间在调试,一直到交卷的那一刻都没能做完。
预估分数:100+0+60+0.
复盘分析
得分:100+0+0+0.
对于T3,我觉得很可能是我子任务的判断有问题,导致两个一起贪又都没得分。而且最窒息的地方在于,T3明明是可以离线先判断子任务后做的,但是我不知道怎么想的愣是写成了在线算法,于是炸了。别的没啥可说的,T1白给,T2就是不会,T4二维树状数组写挂了,也只能说是回去好好复习了。总归今天的节奏一言难尽,说保守吧,T3敢在线做贪两个特殊数据,不保守吧,时间强制控制的非常合理。虽然100分在今天基本上和爆零就没有区别,但总之分数上说得过去,不过T3爆零了就真的难受。
以及T4我赛后去看洛谷上的题解,有人说可以dp,我没看代码就恍然大悟,不到十分钟打了个dp交上去测,有O2的情况下能得高达70分。所以说,思路还是应该活一点,大概是T4树状数组调不出来导致思维也跳不出来了。
题解
T1没啥可讲的。
T2就比较神奇了。
这题想求三种物品最大值和最小,如果暴力就把每个点看成一个空间直角坐标系里的点,看每个点为对角的时候与原点所成长方体是否能覆盖所有的点。要优化的话,肯定要想办法去掉一维,那这里就可以去掉a。去掉a很简单,由于要每种物品最大价值,把a降序排序,如果选择了a[i],a[i]之后所有点都可以扔进a集合,只有前面的需要扔进bc集合。把bc集合里的点抽象成平面直角坐标系里的点,显然我们需要维护一个最外圈的图形把剩下的点全部包括在内。很显然这个最外圈的图案要做到上面的每一个点都做到b比自己大的c比自己小,c比自己大的所有的点b比自己小,反之亦然,说白了就是在c-b图像上呈一个台阶状。
如图所示,淡蓝色的点就是可取的答案。我们只需要随着遍历,每次向图上加点,并维护这个台阶形就可以了。
假设新的点c不大于右边的第一个点,这个点是没用的,因为绝对被右边的覆盖,可以直接扔了;如果不小于左边的第一个点,则可以把左边的点连接的边删了,改为连这个点,把左边的点放在图形内部,直到小于左边的为止;如果正好在左右两点之间,让左右两点连上这个点,把原来连左右两点的线删掉(即扩展了图形)。
为此,我们利用set维护图中的点,以b为第一关键字排序;利用multiset维护淡蓝色点的横纵坐标之和,其第一个值就是本次的答案。更新的过程和上面那一段的做法是一样的。
这个思路很不好想到,实现起来也比较恶心人。核心代码如下:
const int mod = 1e9 + 7;
typedef pair<int,int> pr;
typedef set<pair<int,int> > Setpair;
//中间部分省略
long long solve(){
Setpair St;
multiset<int> Multi;
int i;
long long ret = buc[1].a;//初始化
Setpair::iterator next,last,now;
St.insert(make_pair(mod,0));
St.insert(make_pair(0,mod));//防止越界
Multi.insert(0);
for(i = 1;i <= n;i++){//指的是前i个数存进bc集合
pr P(buc[i].b,buc[i].c);
if(St.count(P)) continue;
St.insert(P);
next = St.find(P);
last = next++,now = last--;//一顿折腾,总之最后是last-now-next
if((*now).second <= (*next).second) St.erase(now);
else{
Multi.insert((*now).first + (*next).second);
Multi.insert((*now).second + (*last).first);
Multi.erase(Multi.find((*last).first + (*next).second));
//先加上,然后判断
while((*now).second >= (*last).second){
--last,--now,--next;//指针整体左移
Multi.erase(Multi.find((*now).second + (*last).first));
Multi.erase(Multi.find((*now).first + (*next).second));
Multi.insert((*last).first + (*next).second);
St.erase(now);//重新取p的下标,减少思考量
now = St.find(P);
last = now,--last;
next = now,++next;
}
}
ret = min(ret,(long long)buc[i + 1].a + *Multi.begin());
}
return ret;
}
T3其实有一种办法可以直接不用并查集,而全部用倍增LCA上树解决。
如果一份文件能从a传到b,有两个条件:b是a的祖先且b和a在传递前是联通的。前者用一个LCA就可以,后者则仿照倍增的思路求一下b到a之间最大的时间,进行一波判断就可以,这个求法就是设time[i][j]表示j向上跳2^i步的路径上最大的时间,维护方式与st表一样,查找的时候枚举i跳就可以了。
完整代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
const int N = 150001;
struct yjx{
int nxt,to,c;
}e[N << 1];
struct fxj{
int deliver,tie;
}p[N << 1];
struct wyx{
int cnt,tim,receiver;
}q[N << 1];
int tot,ecnt = -1,head[N],in[N],time[21][N],fa[21][N],dep[N],dfn[N],siz[N];
int pcnt,qcnt;
void save(int x,int y,int w){
e[++ecnt].nxt = head[x];
e[ecnt].to = y;
e[ecnt].c = w;
head[x] = ecnt;
++in[y];
}
void dfs(int now,int f){
int i,temp;
dfn[now] = ++tot,dep[now] = dep[f] + 1,siz[now] = 1;
fa[0][now] = f;
for(i = 1;(1 << i) <= dep[now];i++){
fa[i][now] = fa[i - 1][fa[i - 1][now]];
time[i][now] = max(time[i - 1][now],time[i - 1][fa[i - 1][now]]);
}
for(i = head[now];~i;i = e[i].nxt){
temp = e[i].to;
if(temp == f) continue;
time[0][temp] = e[i].c;
dfs(temp,now);
siz[now] += siz[temp];
}
return;
}
bool judge(int x,int y){
if(dfn[x] <= dfn[y] && dfn[y] <= dfn[x] + siz[x] - 1) return 1;
return 0;
}
int lca(int x,int y){
int i;
if(dep[x] < dep[y]) swap(x,y);
if(judge(y,x)) return y;
for(i = 18;i >= 0;i--){
if(fa[i][x] && !judge(fa[i][x],y)) x = fa[i][x];
}
return fa[0][x];
}
int Getime(int from,int to){
int i,ret = 0;
for(i = 18;i >= 0;i--){
if(fa[i][from] && dep[fa[i][from]] >= dep[to]){
ret = max(ret,time[i][from]);
from = fa[i][from];
}
}
return ret;
}
signed main(){
//freopen("manage.in","r",stdin);
//freopen("manage.out","w",stdout);
memset(head,-1,sizeof(head));
int n,m,i,x,y,z,end;
scanf("%lld %lld",&n,&m);
for(i = 1;i <= m;i++){
scanf("%lld",&z);
if(z == 1){
scanf("%lld %lld",&x,&y);
save(y,x,i);
}
if(z == 2){
scanf("%lld",&x);
p[++pcnt].deliver = x,p[pcnt].tie = i;
}
if(z == 3){
scanf("%lld %lld",&x,&y);
q[++qcnt].receiver = x,q[qcnt].cnt = y,q[qcnt].tim = p[q[qcnt].cnt].tie;
//这一句有点绕,x要收到y文件的时间就是y被发出的时间
}
}//离线
for(i = 1;i <= n;i++){
if(!in[i]) save(0,i,0);
}
dfs(0,0);
for(i = 1;i <= qcnt;i++){
x = p[q[i].cnt].deliver,y = q[i].receiver;
end = lca(x,y);
if(end != y){//相当于判断y不是x的祖先
printf("NO\n");
continue;
}
//printf("%lld %lld\n",Getime(x,y),q[i].tim);
if(Getime(x,y) >= q[i].tim) printf("NO\n");
else printf("YES\n");
}
return 0;
}
T4首先简单讲一句dp的做法,设dp[i][j]表示(i,j)为右下角最大的正方形边长。如果这格是X,dp[i][j]=0;不然(i,j)的状态只能由(i-1,j-1) (i,j-1) (i-1,j)三个点转移而来,显然应该取三者中最小的一个+1作为答案。
对于正解,随着X变多,答案一定不增。不增不好考虑,于是倒着考虑,每次去掉一个X,答案可能增,这个答案覆盖的正方形一定包括了去掉的X所在的点。所以倒着做这道题,维护每个点向上和向下能延伸的最大距离,每次去掉一个X的时候就把X所在的行更新,然后在X上枚举左端点,用单调队列处理右端点,维护区间最小上下距离,要求左右的距离必须在小于上下距离的时候才能扩展。队列当中存的上下距离是递增的,每次对于新的右端点都更新一下队列,并把这个右端点插入队列当中。如果左端点将要增加,且队头也等于左端点,就把队头后移。这样就维护出了答案。
核心代码:
void update(int x){
int i;
for(i = 1;i <= n;i++){
if(s[i][x] == 'X') up[i][x] = 0;
else up[i][x] = up[i - 1][x] + 1;
}
for(i = n;i >= 1;i--){
if(s[i][x] == 'X') down[i][x] = 0;
else down[i][x] = down[i + 1][x] + 1;
}
}
int solve(int x){
int i,ret = 0,pos = 1,Qu[2002],Qd[2002],frontu = 1,rearu = 0,frontd = 1,reard = 0;
for(i = 1;i <= m;i++){
while(pos == i || pos - i <= up[x][Qu[frontu]] + down[x][Qd[frontd]] - 1){
//printf("%d %d %d\n",pos,i,up[x][frontu] + down[x][frontd] - 1);
ret = max(ret,pos - i);
//此处之所以不是pos-i,可以认为这里是加点-移动序列-更新答案的操作,pos已经+1了,只不过第一次不是这样而已
if(pos > m) break;
while(rearu >= frontu && up[x][Qu[rearu]] >= up[x][pos]) --rearu;
Qu[++rearu] = pos;
while(reard >= frontd && down[x][Qd[reard]] >= down[x][pos]) --reard;
Qd[++reard] = pos;
++pos;
}
if(Qu[frontu] == i) ++frontu;
if(Qd[frontd] == i) ++frontd;
}
return ret;
}
Thank you for reading!