题意简化一下就是一个序列,找出两个最大不下降子序列使得他们的长度和最长。
= =作为一个DP渣,状态设计大概也就到了dp[i][j]表示第一个人最后一次取到i,第二个人取到j这个地方了。。怎么在可行复杂度内转移?不会啊望天。。
其实作为图论工作者第一反应是费用流,但是边数太多了没敢搞= =
然而其实费用流也是可以过的。(x, y)表示容量为x费用为y,
建图很简单,建源s和汇e,加一个点t限制从 源最多流量是2,也就是s->t(2, 0)。将各个点拆掉限制只能取一次,x->x'(1, 1),容量1限制只能取一次,费用表示长度+1
t->x(1, 0) 可以从任意点开始。 x'->e(1, 0) 可以从任意一个点结束。
跑最大费用最大流,然后就会T(喂)
记得POJ有一道题卡SPFA,但是改成栈就能过= =
于是你顺手把它改成栈发现居然不T了(从一开始接触ACM,这个问题就一直困扰着我T T怎么才能构造出卡队列不卡栈的数据?T T求指导)
代码1,大概1400ms 左右。
#include<cstdio> #include <cstring> #include <algorithm> #include <queue> #include <stack> using namespace std; const int N = 2010; const int M = 4000005; const int INF = 0x3f3f3f3f; struct point{ int u,v, next, flow, cost; point(){}; point(int x, int y , int z, int f, int c){ u = x, v= y; next = z, flow = f, cost = c; }; }p[M]; int pre[N], head[N], d[N]; bool vis[N]; int s, e, no; struct Info{ int h, d; void scan(){ scanf("%d%d", &h, &d); } bool operator<(const Info &I)const{ if(h == I.h) return d < I.d; return h > I.h; } }info[N]; bool spfa(){ stack<int>q; int x, y, now, i; memset(d, 0xc0, sizeof(d)); memset(vis, 0, sizeof(vis)); memset(pre, -1, sizeof(pre)); q.push(s); d[s] = 0; vis[s] = 1; while(!q.empty()){ x = q.top(); q.pop(); vis[x] = 0; for(i = head[x]; i != -1; i = p[i].next){ y = p[i].v; if(p[i].flow && d[y] < d[x] + p[i].cost){ d[y] = d[x] + p[i].cost; pre[y] = i; if(!vis[y]){ q.push(y); vis[y] = 1; } } } } return (d[e] != d[e+1]); } int mcmf(){ int maxflow = 0, i, minflow, mincost = 0; while(spfa()){ minflow = INF + 1; for(i = pre[e]; i != -1; i = pre[p[i].u]) { if(p[i].flow < minflow) minflow = p[i].flow; } for(i = pre[e]; i != -1; i = pre[p[i].u]){ p[i].flow -= minflow; p[i ^ 1].flow += minflow; } mincost += d[e] * minflow; } return mincost; } void init(){ memset(head, -1, sizeof(head)); no = 0; } void add(int x, int y, int f, int co){ p[no] = point(x, y, head[x], f, co); head[x] = no++; p[no] = point(y, x, head[y], 0, -co); head[y] = no++; } int main(){ int TC, n, i, j; scanf("%d", &TC); while(TC--){ scanf("%d", &n); init();s = 0; e = 2 * n + 2; for(i = 1; i <= n; i++){ info[i].scan(); add(i<<1, i << 1| 1, 1, 1); add(1, i << 1, 1, 0); add(i << 1 | 1, e, 1, 0); } sort(info + 1, info + n + 1); for(i = 1; i <= n; i++){ for(j = i + 1; j <= n; j++){ if(info[i].d <= info[j].d){ add(i<<1|1, j <<1, 1, 0); } } } add(s, 1, 2, 0); printf("%d ", mcmf()); } return 0; }
接下来进入各种尝试优化阶段。
一开始企图改进建边的方式,具体见 http://blog.csdn.net/mxymxy1994mxy/article/details/47818397 这位大神的说法,空间省了很多,然而用普通队列搞还是T(当然也可能是我蠢(然而显然不是可能,事实上是就是我蠢,趴(为什么最近这么想吐槽自己))),单纯形是厉害ORZ,然而改成栈的SPFA,不到500ms就过了,是厉害啊。
代码2,其他都一样,修改了一下建边的方式
init();s = 0; e = 2 * n + 2; for(i = 1; i <= n; i++){ info[i].scan(); add(i<<1, i << 1| 1, 1, -1); add(1, i << 1, 1, 0); add(i << 1 | 1, e, 1, 0); } sort(info + 1, info + n + 1); int maxn; for(i = 1; i <= n; i++){ maxn = INF; for(j = i + 1; j <= n; j++){ if(info[j].d < info[i].d ){ continue; } if(info[j].d <= maxn) { add(i << 1| 1 | 1, j << 1, 1, 0); add(i << 1, j << 1, 1, 0); // flag = false; maxn = info[j].d; } } } add(s, 1, 2, 0);
由于悲剧主要出于SPFA,企图用ZKW改一下,但好像不行。。。
T________T
就这样吧,还是学DP去