初涉状态压缩【在更】
一直都只会二进制的状态压缩……
状态压缩?
状态压缩,顾名思义,就是把一个大的状态压成相对来说内存更小的东西(通常来说是一个int数字)。而这样做又有什么好处呢?就是当状态的复制量很大,但修改次数相对较小时占优势。(试想一下如果不用回溯,那么dfs时每一层栈空间都带一个vis数组……)
几种状态压缩
三进制状态压缩
loj#6177. 「美团 CodeM 初赛 Round B」送外卖2
题目描述
一张 n 个点 m 条有向边的图上,有 q 个配送需求,需求的描述形式为 (si,ti,li,ri),即需要从点 s_i 送到 t_i, 在时刻 l_i 之后(包括 l_i)可以在 s_i 领取货物,需要在时刻 r_i 之前(包括 r_i)送达 t_i ,每个任务只需完成一次。
图上的每一条边均有边权,权值代表通过这条边消耗的时间。在时刻 0 有一个工作人员在点 1 上,求他最多能完成多少个配送任务。
在整个过程中,可以认为领货跟交货都是不消耗时间的,时间只花费在路程上。当然在一个点逗留也是允许的。
输入格式
第一行,三个正整数 n,m,q(2≤n≤20,1≤m≤400,1≤q≤10)。
接下来 m 行,每行三个正整数 ui,vi,ci(1≤ui,vi≤n,1≤ci≤20000),表示有一条从 ui 到 vi 耗时为 ci 的有向边。
接下来 q 行,每行四个正整数 si,ti,li,ri(1≤si,ti≤n,1≤li≤ri≤10^6),描述一个配送任务。
输出格式
一个整数,表示最多能完成的任务数量。
题目分析
我们首先可以发现n和q都是很小的,又注意到这题的时空限制很不寻常,于是可以大胆结合题意猜测一些想法。
其实没什么好猜的我直接步入正题讲状态压缩算了就不讲我那zz的dfs了
对于每一个送货任务来说,一共只有三种情况:0.货还没取;1.取货了没送到;2.已经送到了。
于是考虑状压dfs。
状压怎么压呢?形象一些就是一串长为q的三进制数,例如(021)(q=3),代表第一单货未取;第二单货已送到;第三单货正在配送。
于是用一个int类型的state以十进制的方式存放状态。十进制?那么怎么提取各个状态呢?如同二进制状压一样,我们需要预处理三的各次幂。正是因为状态压缩这样的特性,所以它适用于需要频繁复制状态,而提取状态相对容易的情况。
由于我第一次接触这种三进制状压时候,对于一些从未见过的操作颇有感触,那么就在代码里注释吧。
hint:每一次可以接很多单(在路上跑的时候可以一次拿很多外卖)
代码:
1 #include<bits/stdc++.h> 2 const int maxn = 21; //n的最大值(一共多少点) 3 const int max3 = 60003;//送货状态转为十进制数后的最大值,用于最优性剪枝 4 5 struct node 6 { 7 int l,r,s,t; 8 node() {} 9 node(int a, int b, int c, int d):s(a),t(b),l(c),r(d) {} 10 }a[maxn]; //存送外卖每一单的需求 11 int ans,n,m,q; 12 int f[maxn][max3],dis[maxn][maxn],base[maxn]; //base[]是3的各次幂,用于提取状态 13 14 int read() 15 { 16 int num = 0; 17 char ch = getchar(); 18 for (; !isdigit(ch); ch = getchar()); 19 for (; isdigit(ch); ch = getchar()) 20 num = (num<<1)+(num<<3)+ch-48; 21 return num; 22 } 23 void dfs(int now, int state, int times)
//now:现在在now这个点上,注意是送货地图的点
//state:现在所有送货的状态
//times:现在已经花费的时间 24 { 25 for (int i=1; i<=q; i++) //检验在当前这个点,可以取\送哪些货物 26 { 27 if (a[i].s==now && (state/base[i-1]%3==0) && times>=a[i].l) state += base[i-1];
//现在在i单货物起点;i单货物状态为0(没有取货过);现在能够取这个货物;使这个状态+1(在三进制下变为1) 28 if (a[i].t==now && (state/base[i-1]%3==1) && times<=a[i].r) state += base[i-1];
//现在在i单货物终点;i单货物状态为1(已经取货了);现在送这个货物来得及;使这个状态+1(在三进制下变为2) 29 } 30 if (f[now][state] < times) return; //最优性剪枝 31 f[now][state] = times; 32 int cpy = state, cnt = 0; 33 while (cpy) 34 { 35 if (cpy%3==2) cnt++; 36 cpy /= 3; 37 }
//提取当前状态中有多少外卖已经送到了 38 if (ans < cnt) ans = cnt; 39 for (int i=1; i<=q; i++) //接下去遍历每一单货物,考虑dfs的后继 40 { 41 if (state/base[i-1]%3==0){ 42 int tt = std::max(times+dis[now][a[i].s], a[i].l); 43 if (tt <= a[i].r) dfs(a[i].s, state, tt); //如果这一单还没有送,并且现在立即赶过去还来得及,就dfs这一单
//至于为什么这里不更改i单的状态,是因为不排除dfs这一单后能够同时送到其他单的情况。
//所以我们放在每一次dfs开始的时候更新状态 44 }else if (state/base[i-1]%3==1 && times+dis[now][a[i].t] <= a[i].r) 45 dfs(a[i].t, state, times+dis[now][a[i].t]); //如果这一单已经在配送过程中了,那么现在去配送这一单 46 } 47 } 48 int main() 49 { 50 scanf("%d%d%d",&n,&m,&q); 51 memset(dis, 0x3f3f3f3f, sizeof dis); //送货地图上两点之间距离用 52 memset(f, 0x3f3f3f3f, sizeof f); //最优性剪枝用 53 for (int i=1; i<=m; i++) 54 { 55 int x = read(), y = read(), z = read(); 56 if (dis[x][y] > z) dis[x][y] = z; 57 } 58 for (int k=1; k<=n; k++) 59 { 60 for (int i=1; i<=n; i++) 61 for (int j=1; j<=n; j++) 62 if (dis[i][j] > dis[i][k]+dis[k][j]) 63 dis[i][j] = dis[i][k]+dis[k][j]; 64 dis[k][k] = 0; 65 } 66 for (int i=1; i<=q; i++) 67 { 68 int ax = read(), b = read(), c = read(), d = read(); 69 a[i] = node(ax, b, c, d); 70 } 71 base[0] = 1; //预处理三的幂次(3^0,3^1,3^2……) 72 for (int i=1; i<=q; i++) 73 base[i] = base[i-1]*3; 74 dfs(1, 0, 0); 75 printf("%d\n",ans); 76 return 0; 77 }
另,待更,炮兵阵地