NOI2019 简要题解

command_block

2021-06-14 22:13:05

Personal

$$\large\text{Current Score : 300}$$ # D1T1 [回家路线](https://www.luogu.com.cn/problem/P5468) **题意** : 有 $n$ 个站点,编号分别为 $1\sim n$。玩家想要从站点 $1$ 前往站点 $n$。 初始时刻为 $0$ 。有若干趟列车,第 $i$ 班次从 $u_i$ 开往 $v_i$ ,在时刻 $p_i$ 上车,时刻 $q_i$ 下车。 玩家在站点等待会增加烦躁值。具体地,若在某个站点连续等待了 $t$ 秒,则烦躁值增加 $At^2+Bt+C$ ,其中 $A,B,C$ 为给定常数。 若在时刻 $z$ 到达终点,则烦躁值额外增加 $z$。 输出完成任务所积累的最小烦躁值。 $n\leq 10^5,m\leq 2\times 10^5$ ,时限$\texttt{1s}$。 ------------ 签到题。 记 $f_u(t)$ 为在 $t$ 时刻出现在站点 $u$ 所需累积的最小烦躁值。 为了处理 $z$ ,我们将 $B$ 加一,然后坐车会减少 $q-p$ 的烦躁值。 对于某一班列车 $(u,v,p,q)$,有转移 : $$ \begin{aligned} f_v(p+t)&\leftarrow f_u(p)+At^2+Bt+C\\ f_v(t)&\leftarrow A(t-p)^2+B(t-p)+C+f_u(p)\\ &=At^2+(B-2Ap)t+\big(C+f_u(p)-Bp+Ap^2\big) \end{aligned} $$ 于是可以将 $f_u(t)$ 表示成若干个二次函数取 $\max$ 的形式。 按照出发时刻从早到晚考虑列车。 当查询 $f_u(p)$ 时,将早于(含)时刻 $p$ 到达的列车的二次函数取出,求 $\max$。 然后将产生的二次函数贡献至 $f_v$ (暂不生效) 能发现,所有二次函数的二次项系数是一样的,而一次项系数单调减小,于是可以维护($n$ 个)凸壳。 复杂度 $O\big((n+m)\log n\big)$。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #define ll long long #define pb push_back #define MaxN 100500 #define MaxM 1000500 using namespace std; const ll INF=1ll<<60; struct Line{ int k;ll b; ll get(int x){return 1ll*k*x+b;} }; bool chk(Line A,Line B,Line C){ ll x1=A.k-B.k,x2=C.k-B.k, y1=A.b-B.b,y2=C.b-B.b; return x1*y2-y1*x2<=0; } int A,B,C; struct Hull { vector<Line> q; int p=0; void add(Line L){ while(q.size()>=2&&chk(q[q.size()-2],q.back(),L))q.pop_back(); q.pb(L); } ll qry(int t) { if (q.empty())return INF; while(p+1<q.size()&&q[p].get(t)>=q[p+1].get(t))p++; return 1ll*A*t*t+q[p].get(t); } }H[MaxN]; struct Trans{int u,v,p,q;Line t;}l[MaxM]; int n,m,p1[MaxM],p2[MaxM]; bool cmp1(int A,int B){return l[A].p<l[B].p;} bool cmp2(int A,int B){return l[A].q<l[B].q;} int main() { scanf("%d%d%d%d%d",&n,&m,&A,&B,&C); for (int i=1;i<=m;i++){ scanf("%d%d%d%d",&l[i].u,&l[i].v,&l[i].p,&l[i].q); p1[i]=p2[i]=i; }sort(p1+1,p1+m+1,cmp1);sort(p2+1,p2+m+1,cmp2); ll ans=INF; H[1].add((Line){B,C}); int p=1; for (int i=1;i<=m;i++){ int k=p1[i]; for (;l[p2[p]].q<=l[k].p;p++)if (l[p2[p]].t.b!=INF){ int v=l[p2[p]].v,t=l[p2[p]].q; H[v].add(l[p2[p]].t); if (v==n)ans=min(ans,1ll*A*t*t+t+l[p2[p]].t.get(t)-C); } ll buf=H[l[k].u].qry(l[k].p); if (buf==INF){l[k].t.b=INF;continue;} l[k].t=(Line){B-2*A*l[k].q,C+buf-1ll*B*l[k].q+1ll*A*l[k].q*l[k].q}; } for (;p<=m;p++) if (l[p2[p]].v==n&&l[p2[p]].t.b!=INF){ int t=l[p2[p]].q; ans=min(ans,1ll*A*t*t+t+l[p2[p]].t.get(t)-C); } printf("%lld",ans); return 0; } ``` # D1T2 [机器人](https://www.luogu.com.cn/problem/P5469) **题意** : 有从左到右一排柱子,第 $i$ 个柱子的高度为 $h_i$。 实验时,会选择一根柱子 $s$ ,并在上面放置两个机器人。机器人只能在相邻的柱子之间移动。 第一个机器人会一直向左平移,但无法移动到比 $s$ 高的柱子上。 第一个机器人会一直向右平移,但只能移动到比 $s$ 低的柱子上。 现在我们需要设置 $\{h_{1\sim n}\}$ ,满足下列条件 : - $h_i\in[L_i,R_i]$ - 无论测试起点 $s$ 选在哪里,两个机器人走过的柱子数目的差 $\leq 2$ 求方案数,答案对 $10^9+7$ 取模。 $n\leq 300,L,R\leq 10^9$ ------------ 首先观察合法方案的条件。 对于最右侧的最大值,在上面放置机器人可以走遍整个序列,且从其余的位置出发不能跨越这个位置。 考虑区间 $\rm DP$ ,设 $dp[l,r,x]$ 表示区间 $[l,r]$ 内的数最大值恰为 $x$ 的方案数。 枚举最右侧最大值所在位置 $mid$ 以及两侧的最大值,有转移 : $$dp[l,r,x]=\sum\limits_{mid}\sum_{k\leq x}dp[l,mid-1,k]\times \sum_{k<x}dp[mid+1,r,k]$$ 此时 $mid$ 可能的取值只能是中点附近的 $O(1)$ 个。因此,可能的 $[l,r]$ 远远不到 $O(n^2)$ ,当 $n=300$ 时只有 $m=2518$ 个。 记忆化搜索可以通过 $L,R$ 较小的点,获得 $50'$。 注意到 $x$ 可能非常大,不能直接记录在状态中。将值域离散化,划分为若干个区间,每个区间内的 $x$ 的转移都是相同的。 不难发现,$dp[l,r,x]$ 是关于 $x$ 的分段函数,且每一段都是 $O(r-l)$ 次的多项式。(其前缀和自然也是 $O(r-l)$ 次多项式) 于是,对每一段分别进行 DP,只需要转移出前 $O(r-l)$ 个状态,然后就能进行拉插。 拉插的下标连续,故容易线性求出。有 $O(n)$ 个值域区间,每个区间要处理 $m$ 个状态,复杂度为 $O(n^2m)$。 ```cpp ``` # D1T3 [序列](https://www.luogu.com.cn/problem/P5470) - [题解 【P5470 [NOI2019] 序列】](https://www.luogu.com.cn/blog/command-block/solution-p5470) # D2T1 [弹跳](https://www.luogu.com.cn/problem/P5471) **题意** : 给出 $n$ 个二维点,每个点向某个矩形内连边,求单源最短路。卡空间。 $n\leq 7\times 10^4,m\leq 1.5\times 10^5$。 ------------ 套路题。 考虑用数据结构模拟 Dijkstra 的过程。 用堆维护目前最短的转移,每次转移时,找出矩形内尚未到达的点,确定这些点的 $dis$ ,向堆中加入这些点的出边。 这需要一个支持矩阵内找点的数据结构。 可以用线段树套 `std::set` 做到时间 $O(n\log^2n)$ 空间 $O(n\log n)$。也可以用 $\rm KDT$ 做到时间 $O(n\sqrt{n})$ (略微卡常)空间 $O(n)$。 ```cpp #include<algorithm> #include<cstdio> #include<queue> #include<set> #define MaxN 70500 #define pb push_back #define Pr pair<int,int> #define fir first #define sec second #define mp make_pair using namespace std; int read(){ int X=0;char ch=0; while(ch<48||ch>57)ch=getchar(); while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar(); return X; } int w,to;Pr wfs; set<Pr> s[MaxN<<2]; void add(int l=1,int r=w,int u=1) { s[u].insert(wfs); if (l==r)return ; int mid=(l+r)>>1; if (to<=mid)add(l,mid,u<<1); else add(mid+1,r,u<<1|1); } bool vis[MaxN]; int wfl,wfl2,wfr,wfr2,tot,tp[MaxN]; void upd(int l=1,int r=w,int u=1) { if (wfl<=l&&r<=wfr){ set<Pr>::iterator it; while(1){ it=s[u].lower_bound(mp(wfl2,0)); if (it->fir>wfr2)break; tp[++tot]=it->sec; if (vis[tp[tot]])tot--; s[u].erase(it); }return ; }int mid=(l+r)>>1; if (wfl<=mid)upd(l,mid,u<<1); if (mid<wfr)upd(mid+1,r,u<<1|1); } struct Data{ int l1,r1,l2,r2,t; bool operator < (const Data &B) const {return t>B.t;} }; vector<Data> g[MaxN]; int dis[MaxN]; priority_queue<Data> q; void del(int u) { vis[u]=1; for (int i=0;i<g[u].size();i++){ g[u][i].t+=dis[u]; q.push(g[u][i]); } } int n,m,h; int main() { n=read();m=read();w=read();h=read(); for (int i=1;i<=n;i++){ to=read(); wfs=mp(read(),i); add(1,w,1); }for (int i=1;i<=w*4;i++) s[i].insert(mp(n+1,0)); Data sav; for (int i=1,u;i<=m;i++){ u=read();sav.t=read(); sav.l1=read();sav.r1=read(); sav.l2=read();sav.r2=read(); g[u].pb(sav); }del(1); while(!q.empty()){ Data u=q.top();q.pop(); wfl=u.l1;wfr=u.r1; wfl2=u.l2;wfr2=u.r2; tot=0;upd(); for (int i=1;i<=tot;i++){ dis[tp[i]]=u.t; del(tp[i]); } } for (int i=2;i<=n;i++) printf("%d\n",dis[i]); return 0; } ``` # D2T2 [斗主地](https://www.luogu.com.cn/problem/P5472) 一副牌一共有 $n$ 张牌,初始时从上到下依次标号为 $1 - n$。标号为 $i$ 的牌**分数**是 $f(i)$。在 本题,$f(i)$ 有且仅有两种可能:$f(i) = i$ 或 $f(i) = i^2$。 洗牌环节一共分 $m$ 轮,这 $m$ 轮洗牌依次进行。第 $i$ 轮洗牌时: 拿出从最上面往下数的前 $A_i$ 张牌,形成两堆牌(某一堆可能为空) 对两堆牌进行合并。当第一堆牌还剩下 $X$ 张,第二堆牌还剩下 $Y$ 张的时候,以 $\dfrac{X}{X+Y}$ 的概率取第一牌堆底,放入新的第三堆牌的最上面, $\dfrac{Y}{X+Y}$ 的概率取第二牌堆底,放入新的第三堆牌的最上面。 一直取到两堆牌都为空为止。这样我们就完成了一轮洗牌。 询问 $m$ 轮洗牌完成后最终每个位置上的牌的 $Q$ 次询问你在经历了这 $m$ 轮洗牌后,各个位置上的牌的**期望分数**。 答案对 $998244353$ 取模。 $n\leq 10^7,m\leq 5\times 10^5$。 ------------ 命运题。 # D2T3 [I 君的探险](https://www.luogu.com.cn/problem/P5473) **题意** : 本题是交互题。 有一张 $n$ 个点 $m$ 条边的无向图,点编号为 $0\sim n-1$ ,你需要通过交互得知图的具体形态。 点有点权,为 $0$ 或 $1$。初始时均为 $0$。 有下列四种操作 : - ① 查询给定点 $x$ 的点权。 - ② 将 $x$ 以及与 $x$ 相连的点的点权取反。 - ③ 报告找到了一条直接连接 $x,y$ 的无向边。 - ④ 询问与 $x$ 相连的边是否已全部找到。 其中操作 ①②④ 的次数限制分别为 $L_q,L_m,L_c$。 交互库是 inactive 的。 - $n=2\times 10^5,m=3\times 10^5,L_q=L_m=10^7,L_c=2\times m$ - $n=2\times 10^5,m=n-1,L_q=L_m=19\times n,L_c=0$ 对于每个 $u>0$ ,存在 $v<u$ 使得 $u,v$ 直接相连。 ------------