【2018.10.22】图图的游戏 / 图图的设计 / 图图的旅行

题目

我是一个小沙比,爆零本领强~

T1

看起来是一道很捞的、做过无数遍的区间最大值。

直接$O(n^3)$做一做就完了……

具体做法就是预处理每行的前缀和,然后二重循环枚举一个固定的列区间,再用单调队列的思想,从第一行不停向下扩展行区间,如果矩阵内总和$gt k$ 了就从行区间顶部不停删行,删到矩阵内总和$le k$ 为止。每当矩阵总和满足限制时,更新矩阵面积最大值即可。

当然如果你很想大战$T1$的话,可以写个$O(n^2*log(n^2))$的主席树?

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<iostream>
 6 #include<algorithm>
 7 #define N 501
 8 using namespace std;
 9 inline int read(){
10     int x=0; bool f=1; char c=getchar();
11     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
12     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
13     if(f) return x;
14     return 0-x;
15 }
16 int n,m,e[N][N],sum[N][N],x,ans;
17 int main(){
18     n=read()+1,m=read();
19     int i,j,k,go;
20     for(i=1;i^n;++i)
21         for(j=1;j^n;++j)
22             sum[i][j]=sum[i][j-1]+read();
23     for(i=1;i^n;++i){
24         for(j=i;j^n;++j){
25             x=0, go=1;
26             for(k=1;k^n;++k){
27                 x+=sum[k][j]-sum[k][i-1];
28                 while(x>m) x-=sum[go][j]-sum[go][i-1], ++go;
29                 ans=max(ans,(j-i+1)*(k-go+1));
30             }
31         }
32     }
33     printf("%d
",ans);        
34     return 0;
35 }
View Code

T2

这是一道考验水平的$dp$题(机房最高分$70$

0~30pts:

你写的开心就好

40~50pts:

不知道

60~90pts:

由于是一条链,且对于相邻的两个机场之间的点,我们可以用二分等方法快速求出每个点补给来源(就是离它最近的一个机场)的分界处。所以可以考虑链上$dp$。

设$f(i)$表示把最后一个机场设在第 $i$ 个城市时,前 $i$ 个点都得到补给所需的总代价的最小值。

易推$f(i)=min{f(j-1)+sum_{k=j}^{i}min(d_k-d_j,d_i-d_k)}+cost_i space | space (j<i,j≠0)$

其实就是枚举上一个机场的位置,然后计算中间这些点到最近机场的距离代价。

前面说过,相邻两个机场之间的点,一定会分成左右两部分计算,左边部分离左边的机场更近,右边部分离右边的机场更近。只要找到这个分界处,区间距离总和随意预处理就好了(二维前缀和?)。

找到分界处有两种方法,一种是直接二分位置($O(log(n))$),一种是根据分界处的单调变化性(左边的机场 $j$ 向一个方向移动,分界处也只会向这个方向移动)单调修改($O(n)$)。

当然有可能前面没设过机场$(j=0)$,这时转移是$f(i)=cost_i+sum_{k=1}^{i}(d_i-d_k)$。

100pts:

这个dp有点新式……

设$f(i,j)$表示当前最后一个机场设在了节点 $j$ 时(建立顺序任意),以 $i$ 号节点为根的子树中所有点都得到补给所需的总代价的最小值,$g(i)=min{f(i,j)}$。(王爷定义的 $j$ 跟我定义的不太一样,我看了代码之后感觉应该这样定义)

然后转移是这样的

$f(i,j)=sum_{v∈son_i}min(f(v,j)-cost_j,g(v))+dist(i,j)+cost_j$

其中$dist(i,j)$表示$i$与$j$在树上的距离,这个可以$O(n^2)$预处理。

转移就这么些道理:

首先我们要累加当前点得到补给的代价 以及在$j$建机场的代价,也就是 $dist(i,j)+cost_j$。

$f(v,j)-cost_j$ 表示沿用离$v$最近的机场$j$,$-cost_j$是因为建这个机场的代价已经在$f(v,j)$的转移中累加过了(因为离$v$最近的机场也是$j$),为了避免重复花费建机场的代价,我们减去它,之后再重新加上即可。

$g(v)$ 表示在 $v$ 新建一个机场,这样就可以取 $min{f(v,x)} space | space x为任意节点}$,因为跟上一个建的机场没关系了。

但这时候有个问题:新建的机场不一定离点$i$最近啊!为什么可以直接累加点$i$到最新建的机场的距离为点$i$得到补给的代价呢?

这个得有一些$dp$经验才明白,其实是因为按照我们的转移方法,所有建立机场的顺序都被考虑过,而我们取的是总代价的最小值。

再细讲,举个例子:对于机场建设完全相同,但建设顺序不同的情况(假设图中根在上方)

如果我们先在红点建机场,再在绿点设机场,因为绿点是白点儿子,所以对于$f(白点,绿点)$,白点的代价可能被算为到绿点的距离 $2$,但实际上它离红点更近,代价应该是到红点的距离 $1$。

但反过来想,我们既然能够考虑所有情况,那肯定包含红绿两点交换建机场的顺序的情况,比如说,我们就可以把$f(绿点,绿点)$更新给$f(白点,红点)$,这时白点的代价就计算为白点到红点的距离 $1$ 了。

以后再合并答案的时候,由于上述两种顺序的总代价只在计算白点代价时有差异,而我们求的是代价的最小值,于是错误的计算就被更新掉了(因为错误的情况计算白点的代价更大)。

最后的答案即为$g(root)$。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cctype>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define rep(i,s,t) for(int i=s;i<=t;i++)
 7 #define dwn(i,s,t) for(int i=s;i>=t;i--)
 8 using namespace std;
 9 inline int read() { 
10     int x=0,f=1;char c=getchar();
11     for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
12     for(;isdigit(c);c=getchar()) x=x*10+c-'0';
13     return x*f;
14 }
15 typedef long long ll;
16 const int maxn=2610;
17 int n,cost[maxn],first[maxn],nxt[maxn<<1],to[maxn<<1],dis[maxn<<1],e;
18 void AddEdge(int u,int v,int w) {
19     to[++e]=v;dis[e]=w;nxt[e]=first[u];first[u]=e;
20     to[++e]=u;dis[e]=w;nxt[e]=first[v];first[v]=e;
21 }
22 int dist[maxn][maxn];
23 void dfs(int x,int fa,int s) {
24     for(int i=first[x];i;i=nxt[i]) if(to[i]!=fa) dist[s][to[i]]=dist[s][x]+dis[i],dfs(to[i],x,s);
25 }
26 int f[maxn][maxn],g[maxn];
27 void dp(int x,int fa) { 
28     for(int i=first[x];i;i=nxt[i]) if(to[i]!=fa) dp(to[i],x);
29     rep(j,1,n) { 
30         f[x][j]=cost[j]+dist[x][j];
31         for(int i=first[x];i;i=nxt[i]) if(to[i]!=fa)f[x][j]+=min(g[to[i]],f[to[i]][j]-cost[j]);
32     }
33     g[x]=f[x][1];
34     rep(i,2,n) g[x]=min(g[x],f[x][i]);
35 }
36 int main() {
37     //freopen("design.in","r",stdin);
38     //freopen("design.out","w",stdout); 
39     n=read();
40     rep(i,1,n) cost[i]=read();
41     rep(i,1,n-1) {
42         int u=read(),v=read(),w=read();
43         AddEdge(u,v,w);
44     } 
45     rep(i,1,n) dfs(i,0,i);
46     dp(1,0);
47     printf("%d
",g[1]);
48     return 0;
49 }
View Code

T3

这题引出了“线段树优化建图”这个东西?我可能没写过呢。

其实很好理解,就是对于区间向区间连边的题目(而不是点向点连边),可以维护一棵线段树,线段树的每个节点存的是一段点区间,并向它的左右儿子都连一条权值为$0$的边。这样就可以在$log$级别的复杂度内从区间向区间连边。

这题更简单了,只是点向区间连边……又因为每个点只向一个它能到达的区间连一次边,我们甚至不用再建图连边,直接操作一个点能到达的的区间即可。我的$code$就没建图。

当然,我们要预处理出每个点能到达的区间。

然后我们怎么搞区间连边的单源最短路?$spfa$?

$spfa$有一个缺点,就是不能确定每个点的$dis$(到起点的最短路长度)什么时候会成为最小值(当然了,一个点被更新$总点数-1$次后肯定是最小值,但对所有点都这样判的话就被卡成暴力了),也就是$dis$值一定不会再发生变化。而我们只能用线段树快速完成区间更新$dis$值,并不知道哪些点的$dis$值发生了变化,我们只能暴力把这些点放入队列,这样时间复杂度就向暴力退化了。

那我们怎么快速求最短路?只能$dijkstra$咯,要知道这是一个很优秀的算法(带负权边除外)。这题没有负权边,所以可以用它。

于是我们只需要在线段树上维护一下区间$dis$的最小值,每次随便查询一个$dis$值最小的位置,记录它的答案为$dis$值,然后以它为起点更新它能到达的区间的$dis$值即可。用完这个点后,为了避免以后查询$dis$值最小的点再查到它,我们要把这个点从线段树上删除(删除的方法随意,我直接把这个点的$dis$值赋回$inf$ 并打上一个删除标记)。

  1 #include<bits/stdc++.h>
  2 #define ll long long
  3 #define N 152505
  4 const ll inf=1ll<<62;
  5 using namespace std;
  6 inline int read(){
  7     int x=0; bool f=1; char c=getchar();
  8     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
  9     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
 10     if(f) return x;
 11     return 0-x;
 12 }
 13 int n,s,t,x[N],y[N],z[N],l[N],r[N];
 14 int root,L,R,u; ll Dis;
 15 struct SegTree{
 16     struct T{
 17         int son[2];
 18         ll dis,tag; //min,max
 19         bool del; 
 20     }tr[N<<3];
 21     int cnt;
 22     inline void pushup(int o){
 23         tr[o].dis = min(tr[tr[o].son[0]].dis, tr[tr[o].son[1]].dis);
 24         //printf("pushup:%lld %lld
",tr[tr[o].son[0]].dis, tr[tr[o].son[1]].dis);
 25     }
 26     inline void pushdel(int o){
 27         tr[o].del = tr[tr[o].son[0]].del & tr[tr[o].son[1]].del;
 28     }
 29     void build(int &o,int l,int r){
 30         o=++cnt;
 31         tr[o].tag=inf, tr[o].del=0;
 32         if(l==r){tr[o].dis=inf; tr[o].son[0]=tr[o].son[1]=0; return;}
 33         int mid=(l+r)>>1;
 34         build(tr[o].son[0],l,mid), build(tr[o].son[1],mid+1,r);
 35         pushup(o);
 36     }
 37     inline void pushdown(int o){
 38         if(tr[o].tag<inf){
 39             int lc=tr[o].son[0], rc=tr[o].son[1];
 40             if(!tr[lc].del) tr[lc].dis=min(tr[lc].dis,tr[o].tag), tr[lc].tag=min(tr[lc].tag,tr[o].tag);
 41             if(!tr[rc].del) tr[rc].dis=min(tr[rc].dis,tr[o].tag), tr[rc].tag=min(tr[rc].tag,tr[o].tag);
 42             tr[o].tag=inf;
 43         }
 44     }
 45     void update(int o,int l,int r){
 46         if(tr[o].del) return;
 47         if(L<=l && r<=R){
 48             tr[o].dis=min(tr[o].dis,Dis), tr[o].tag=min(tr[o].tag,Dis);
 49             return;
 50         }
 51         pushdown(o);
 52         int mid=(l+r)>>1;
 53         if(L<=mid) update(tr[o].son[0],l,mid);
 54         if(R>mid) update(tr[o].son[1],mid+1,r);
 55         pushup(o);
 56     }
 57     void Delete(int o,int l,int r){
 58         if(l==r){tr[o].dis=inf; tr[o].del=1; return;}
 59         pushdown(o);
 60         int mid=(l+r)>>1;
 61         if(u<=mid) Delete(tr[o].son[0],l,mid);
 62         else Delete(tr[o].son[1],mid+1,r);
 63         //printf("Delete:%d %d %lld
",l,r,tr[o].dis);
 64         pushup(o); //printf("Delete:%d %d %lld
",l,r,tr[o].dis);
 65         pushdel(o);
 66     }
 67     void query(int o,int l,int r){ //查询最小值的最小下标 
 68         //printf("query:%d %d %lld
",l,r,tr[o].dis);
 69         if(l==r){u=l, Dis=tr[o].dis; return;}
 70         pushdown(o);
 71         int mid=(l+r)>>1;
 72         //printf("query:%d %d %lld %lld %lld
",l,r,tr[o].dis,tr[tr[o].son[0]].dis,tr[tr[o].son[1]].dis);
 73         if(tr[o].dis==tr[tr[o].son[0]].dis && !tr[tr[o].son[0]].del) query(tr[o].son[0],l,mid);
 74         else if(tr[o].dis==tr[tr[o].son[1]].dis && !tr[tr[o].son[1]].del) query(tr[o].son[1],mid+1,r);
 75     }
 76 }S;
 77 ll ans[N];
 78 void Dij(){
 79     L=R=s, Dis=0;
 80     S.cnt=0;
 81     S.update(root,1,n);
 82     int i;
 83     for(int i=1;i<=n;++i){
 84         S.query(root,1,n);
 85         ans[u]=Dis;
 86         //printf("%d %lld %d %d
",u,ans[u],l[u],r[u]);
 87         if(u==t) break;
 88         Dis+=z[u];
 89         L=l[u],R=r[u];
 90         S.update(root,1,n);
 91         S.Delete(root,1,n);
 92     }
 93 }
 94 int main(){
 95     //freopen("trip9.in","r",stdin);
 96     //freopen("trip.out","w",stdout);
 97     n=read(),s=read(),t=read();
 98     int i;
 99     for(i=1;i<=n;++i) x[i]=read();
100     for(i=1;i<=n;++i) y[i]=read();
101     for(i=1;i<=n;++i) z[i]=read();
102     for(i=1;i<=n;++i){
103         l[i]=lower_bound(x+1,x+n+1,x[i]-y[i])-x,
104         r[i]=upper_bound(x+1,x+n+1,x[i]+y[i])-x-1;
105         //cout<<i<<' '<<x[i]<<' '<<y[i]<<' '<<l[i]<<' '<<r[i]<<'
';
106     }
107     S.build(root,1,n);
108     Dij();
109     printf("%lld
",ans[t]);
110     return 0;
111 }
112 /*
113 7 3 7
114 -1 0 1 2 3 5 10
115 11 0 1 1 4 10 2
116 3 1 1 1 2 4 5
117 */
View Code
原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/2018_10_22.html