SNOI2019 通信

【题解】[SNOI2019]通信

评价:一眼秒了,两个板子合在一起就成难题了是嘛 /怄火

题目描述:

nn 个排成一列的哨站要进行通信。第 ii 个哨站的频段为 aia_i

每个哨站 ii 需要选择以下二者之一:

  1. 直接连接到控制中心,代价为 WW
  2. 连接到前面的某个哨站 jj (j<ij<i),代价为 aiaj|a_i-a_j|
    每个哨站只能被后面的至多一个哨站连接。

请你求出最小可能的代价和。

对于所有数据,1n10001 \leq n \leq 10000W,ai1090 \leq W,a_i \leq 10^9

题目分析:

一个想法就是 dpdp,但是这样的话我们就要记录每个点选/不选,这种东西就很不能做。
注意到一个哨站最多连接到前面的一个和后面的一个哨站。
这个东西就有一种匹配的感觉,而这个东西显然不是二分图,所以只有网络流这一种思路了。
所以就有一个显然的建图:
设源点为 ss,汇点为 tt,控制中心为 pp,将哨站 ii 拆成一个入点 iniin_i 和一个出点 outiout_i
对于每一个哨站建边:(s,ini,1,0)(s,in_i,1,0)(outi,t,1,0)(out_i,t,1,0)(ini,p,1,W)(in_i,p,1,W)(ini,outj,1,aiaj)(in_i,out_j,1,\left| a_i-a_j \right|)
主要是最后这一种建边数量 O(n2)O(n^2),所以直接费用流的复杂度是接受不了的。
可以发现最后一种建边就相当于一个二维偏序,因为我们可以将绝对值拆开,即:

  • j<ij < iaj<aia_j < a_i,建边 (ini,outj,1,aiaj)(in_i,out_j,1,a_i-a_j)
  • j<ij < iaj>aia_j > a_i,建边 (ini,outj,1,ajai)(in_i,out_j,1,a_j-a_i)

可以使用分治去掉第一维,这样第二维就可以使用前缀和优化建图解决了。

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3005;
const int INF = 1e18+5;
struct edge{
int nxt,to,cost,val;
edge(){}
edge(int _nxt,int _to,int _val,int _cost){
nxt = _nxt,to = _to,cost = _cost,val = _val;
}
}e[5000 * N];
int cnt=1,tot,s,t,pre[N],suf[N],b[N],tmp[N],in[5 * N],out[5 * N],a[5 * N],dis[5 * N],cur[5 * N],head[5 * N];
bool in_que[5 * N],vis[5 * N];
void add_edge(int from,int to,int val,int cost){
e[++cnt] = edge(head[from],to,val,cost);
head[from] = cnt;
e[++cnt] = edge(head[to],from,0,-cost);
head[to] = cnt;
}
bool spfa(){
for(int i=1; i<=tot; i++) dis[i] = INF,in_que[i] = false,cur[i] = head[i];
dis[s] = 0;
queue<int> q;q.push(s);in_que[s] = true;
while(!q.empty()){
int now = q.front();q.pop();
in_que[now] = false;
for(int i=head[now]; i; i=e[i].nxt){
int to = e[i].to;
if(dis[to] > dis[now] + e[i].cost && e[i].val){
dis[to] = dis[now] + e[i].cost;
if(!in_que[to]) q.push(to),in_que[to] = true;
}
}
}
return dis[t] != INF;
}
int dfs(int now,int limit){
if(now == t) return limit;
int flow = 0;vis[now] = true;
for(int i=cur[now]; i && flow < limit; i=e[i].nxt){
int to = e[i].to;
cur[now] = i;
if(e[i].val && dis[to] == dis[now] + e[i].cost && !vis[to]){
int h = dfs(to,min(e[i].val,limit-flow));
e[i].val -= h,e[i^1].val += h;
flow += h;
if(!h) dis[to] = INF;
}
}
return flow;
}
int dinic(){
int ans = 0;
while(spfa()){
memset(vis,false,sizeof(vis));
int flow = dfs(s,INF);
ans += flow * dis[t];
}
return ans;
}
int find(int l,int r,int val){
int ans = r + 1;
while(l <= r){
int mid = (l + r) >> 1;
if(a[b[mid]] > val){
ans = mid,r = mid - 1;
}
else l = mid + 1;
}
return ans;
}
void solve(int l,int r){
if(l == r) return;
int mid = (l + r) >> 1;
solve(l,mid);solve(mid+1,r);
for(int i=l; i<=mid; i++){
pre[i] = ++tot;
add_edge(pre[i],out[b[i]],1,-a[b[i]]);
if(i != l) add_edge(pre[i],pre[i-1],INF,0);
}
for(int i=mid; i>=l; i--){
suf[i] = ++tot;
add_edge(suf[i],out[b[i]],1,a[b[i]]);
if(i != mid) add_edge(suf[i],suf[i+1],INF,0);
}

for(int i=mid+1; i<=r; i++){
int pos = find(l,mid,a[b[i]]); //第一个大于 a[b[i]] 的数
if(pos-1 >= l) add_edge(in[b[i]],pre[pos-1],1,a[b[i]]);
if(pos <= mid) add_edge(in[b[i]],suf[pos],1,-a[b[i]]);
}
int sz = l-1;
int posl = l,posr = mid+1;
while(posl <= mid && posr <= r){
if(a[b[posl]] < a[b[posr]]) tmp[++sz] = b[posl++];
else tmp[++sz] = b[posr++];
}
while(posl <= mid) tmp[++sz] = b[posl++];
while(posr <= r) tmp[++sz] = b[posr++];
for(int i=l; i<=r; i++) b[i] = tmp[i];
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n,W;scanf("%lld%lld",&n,&W);
s = ++tot;
t = ++tot;
int p = ++tot;add_edge(p,t,INF,0);
for(int i=1; i<=n; i++) in[i] = ++tot,out[i] = ++tot;
for(int i=1; i<=n; i++){
scanf("%lld",&a[i]);
add_edge(s,in[i],1,0);add_edge(out[i],t,1,0);
add_edge(in[i],p,1,W);
}

for(int i=1; i<=n; i++) b[i] = i;
solve(1,n);

printf("%lld\n",dinic());
return 0;
}
作者

linyihdfj

发布于

2023-09-21

更新于

2023-09-21

许可协议