@loj - [email protected]「SDOI2014」Lis

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

给定序列 A,序列中的每一项 Ai 有删除代价 Bi 和附加属性 Ci

请删除若干项,使得 A 的最长上升子序列长度减少至少 1,且付出的代价之和最小,并输出方案。

如果有多种方案,请输出将删去项的附加属性排序之后,字典序最小的一种。

输入格式
输入包含多组数据。
输入的第一行包含整数 T,表示数据组数。
接下来 4T 行描述每组数据:
每组数据的第一行包含一个整数 N,表示 A 的项数,接下来三行,每行 N 个整数 A1, ..., AN; B1, ..., BN; C1, ..., CN。

输出格式
对每组数据,输出两行。
第一行包含两个整数 S,M,依次表示删去项的代价和与数量;
接下来一行 M 个整数,表示删去项在 A 中的位置,按升序输出。

样例
样例输入
1
6
3 4 4 2 2 3
2 1 1 1 1 2
6 5 4 3 2 1
样例输出
4 3
2 3 6

数据范围与提示
对于所有的数据, 1 <= N <= 700, T <= 5, 1 <= Ai, Bi, Ci <= 10^9 且保证 Ci 两两不同。

@[email protected]

假如不考虑方案,我们可以先跑最长上升子序列的 dp,记录每个点为结尾的最长上升子序列 f[x],并记录全局最优解 mx。
然后考虑建一个分层图,其中 x 向 y 连边当且仅当 dp 时 x 能够作为 y 的最优决策点(即 x < y 且 A[x] < A[y] 且 f[x] + 1 = f[y])。
假如我选择一些点删掉,使得整张图没有点可以从 f[x] = 1 跑到 f[x] = mx,则原序列的最长上升子序列肯定长度减小。

于是我们考虑先将建出来的分层图拆点,然后建源点 s 连 f[x] = 1 的点,将 f[x] = mx 的点连向汇点 t,跑最小割即可得到答案。
现在来考虑怎么求一个字典序最小的最小割方案。不难想到应该使用贪心的方法,按 Ci 从小到大依次取,并尝试将当前这个 i 加入最小割。

怎么判断一条边 i 是否可以在最小割内?最暴力的方法无疑是将这条边删掉,看最大流(即最小割)是否改变。
等效替代一下,就是看这条边 (u, v) 是否能从 u 出发通过残余网络到达 v。

怎么消掉一条边的影响?最暴力的方法一样是将这条边删掉,然后重跑最大流。
稍微快一些的方法是:跑 (t, v) 的最大流,跑 (u, s) 的最大流,并把 (u, v) 这条边的容量与流量都设置为 0。
这个操作称之为退流操作。

然而退流操作要常数小,有时候单路增广甚至要快一些(因为增广路本身不会太长),而多路增广需要重新给 n 个点求距离标号所以会大量访问到无用点。
比较折中方法是在 dinic 求距离标号时一旦遇到终点就立刻返回进行增广。

@accepted [email protected]

#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int MAXN = 700;
const int MAXV = 2*MAXN;
const int MAXE = 3*MAXV*MAXV;
const int INF = (1<<30);
struct FlowGraph{
    struct edge{
        int to, cap, flow;
        edge *nxt, *rev;
    }edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
    int s, t, n;
    void clear(int _n) {
        n = _n; ecnt = &edges[0];
        for(int i=0;i<=n;i++)
            adj[i] = NULL;
    }
    void addedge(int u, int v, int c) {
        edge *p = (++ecnt), *q = (++ecnt);
        p->to = v, p->cap = c, p->flow = 0;
        p->nxt = adj[u], adj[u] = p;
        q->to = u, q->cap = 0, q->flow = 0;
        q->nxt = adj[v], adj[v] = q;
        p->rev = q, q->rev = p;
//      printf("%d %d %lld\n", u, v, c);
    }
    queue<int>que; int d[MAXV + 5];
    bool relabel() {
        for(int i=0;i<=n;i++)
            cur[i] = adj[i], d[i] = n + 5;
        while( !que.empty() ) que.pop();
        d[t] = 0, que.push(t);
        while( !que.empty() ) {
            int f = que.front(); que.pop();
            for(edge *p=adj[f];p;p=p->nxt)
                if( d[f] + 1 < d[p->to] && p->rev->cap > p->rev->flow ) {
                    d[p->to] = d[f] + 1;
                    que.push(p->to);
                    if( p->to == s ) return true;
                }
        }
        return !(d[s] == n + 5);
    }
    int aug(int x, int tot) {
        if( x == t ) return tot;
        int sum = 0;
        for(edge *&p=cur[x];p;p=p->nxt) {
            if( p->cap > p->flow && d[p->to] + 1 == d[x] ) {
                int del = aug(p->to, min(p->cap - p->flow, tot - sum));
                p->flow += del, p->rev->flow -= del, sum += del;
                if( sum == tot ) break;
            }
        }
        return sum;
    }
    int max_flow(int _s, int _t) {
        s = _s, t = _t; int flow = 0;
        while( relabel() )
            flow += aug(s, INF);
        return flow;
    }
}G;
int A[MAXN + 5], f[MAXN + 5];
FlowGraph::edge *e[MAXN + 5];
pair<int, int>C[MAXN + 5];
int ans[MAXN + 5], cnt;
void solve() {
    int N, s, t; scanf("%d", &N);
    s = 0, t = 2*N + 1, G.clear(t);
    for(int i=1;i<=N;i++)
        scanf("%d", &A[i]);
    int mx = 0;
    for(int i=1;i<=N;i++) {
        f[i] = 1;
        for(int j=1;j<i;j++)
            if( A[j] < A[i] )
                f[i] = max(f[i], f[j] + 1);
        mx = max(mx, f[i]);
    }
    for(int i=1;i<=N;i++) {
        int x; scanf("%d", &x);
        G.addedge(i, i + N, x);
        e[i] = G.adj[i];
    }
    for(int i=1;i<=N;i++) {
        if( f[i] == 1 ) G.addedge(s, i, INF);
        if( f[i] == mx ) G.addedge(i + N, t, INF);
        for(int j=1;j<i;j++)
            if( f[j] + 1 == f[i] )
                G.addedge(j + N, i, INF);
    }
    for(int i=1;i<=N;i++)
        scanf("%d", &C[i].first), C[i].second = i;
    sort(C + 1, C + N + 1);
    printf("%d", G.max_flow(s, t));
    cnt = 0;
    for(int i=1;i<=N;i++) {
        int x = C[i].second;
        G.s = e[x]->rev->to, G.t = e[x]->to;
        if( !G.relabel() ) {
            e[x]->cap = e[x]->flow = e[x]->rev->cap = e[x]->rev->flow = 0;
            G.max_flow(t, e[x]->to), G.max_flow(e[x]->rev->to, s);
            ans[++cnt] = x;
        }
    }
    sort(ans + 1, ans + cnt + 1);
    printf(" %d\n", cnt);
    for(int i=1;i<=cnt;i++)
        printf("%d%c", ans[i], i == cnt ? '\n' : ' ');
}
int main() {
    int T; scanf("%d", &T);
    while( T-- ) solve();
}

@[email protected]

woc 为什么我用 long long 就会多 TLE 一个点啊。。。

难道这个数据范围(指 Bi <= 10^9)不应该开 long long 吗。。。

为什么 int 能过啊。。。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11396826.html

时间: 2024-08-29 16:02:47

@loj - [email protected]「SDOI2014」Lis的相关文章

@loj - [email&#160;protected]「CEOI2017」Building Bridges

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 根柱子依次排列,第 i 根柱子的高度为 hi .现可以花费 (hi - hj)^2 的代价建桥架在第 i 根柱子和第 j 根柱子之间. 所有用不到的柱子都会被拆除,第 i 根柱子被拆除的代价为 wi . 求用桥把第 1 根柱子和第 n 根柱子连接的最小代价.注意桥梁不能在端点以

@loj - [email&#160;protected] 「CQOI2017」老 C 的方块

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 老 C 是个程序员. 作为一个懒惰的程序员,老 C 经常在电脑上玩方块游戏消磨时间.游戏被限定在一个由小方格排成的 R 行 C 列网格上,如果两个小方格有公共的边,就称它们是相邻的,而且有些相邻的小方格之间的公共边比较特殊. 特殊的公共边排列得有很强的规律.首先规定,第 1 行的前两个

@loj - [email&#160;protected] 「SDOI2017」硬币游戏

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利. 大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了. 同学们觉得要加强趣味性,所以要找一个同学扔很多很多次硬币,其他同学记录下正反面情况. 用 H 表示正面朝上, 用 T 表示反面朝上,扔很多

@loj - [email&#160;protected] 「CTSC2016」时空旅行

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 2045 年,人类的技术突飞猛进,已经找到了进行时空旅行的方法.小 R 得到了一台时空旅行仪,他想用它调查不同时空中人类的发展状况. 根据平行时空理论,宇宙中存在着很多独立的时空,每个时空在下一个时间点还会分化出若干个不同的时空.宇宙是一个三维空间,人类使用空间直角坐标系来描述空间中的

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

loj#2552. 「CTSC2018」假面

题目链接 loj#2552. 「CTSC2018」假面 题解 本题严谨的证明了我菜的本质 对于砍人的操作好做找龙哥就好了,blood很少,每次暴力维护一下 对于操作1 设\(a_i\)为第i个人存活的概率,\(d_i\)为死掉的概率,\(g_{i,j}\)是除i以外活了j个人的概率 那个选中i人的答案就是 \[a_i\times\sum_{j = 0} ^{k - 1}\frac{g_{i,j}}{j + 1}\] 对于\(g_{i,j}\) ,设\(f_{i,j}\)表示前\(i\)个人有\(

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

目录 题目链接 题解 代码 题目链接 loj#2076. 「JSOI2016」炸弹攻击 题解 模拟退火 退火时,由于答案比较小,但是温度比较高 所以在算exp时最好把相差的点数乘以一个常数让选取更差的的概率降低 代码 #include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define gc getchar() #define

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后:

Loj #2542. 「PKUWC2018」随机游走

Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次询问给定一个集合 \(S\),求如果从 \(x\) 出发一直随机游走,直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步. 特别地,点 \(x\)(即起点)视为一开始就被经过了一次. 答案对 $998244353 $ 取模. 输入格式 第一行三个正整数 \(n,Q,x\). 接下来 \(