Optimización de cola monotónica DP + puntero doble + codicioso + STL: aplicación integral multiconjunto

Y: "Esta pregunta tiene la dificultad de la competencia nacional del Grupo A de la Copa Blue Bridge o la dificultad de la medalla de plata ACM"

Después de dos tardes de investigación, finalmente descubrí los detalles.

 Descripción del Título:

Dada una secuencia A de longitud N, se requiere dividir la secuencia en varios segmentos y minimizar la suma de "el valor máximo de todos los números en cada segmento" bajo la premisa de que "la suma de todos los números en cada segmento" no excede m. Trate de calcular este mínimo.

La primera línea del formato de entrada contiene dos números enteros N y M. La segunda línea contiene N enteros que representan la secuencia completa A.

El formato de salida genera un número entero que representa el resultado. Si el resultado no existe, genera −1.

Rango de datos 0≤N≤105, 0≤M≤1011, el número en la secuencia A no es negativo y no excede 106

Ejemplo de entrada: 8 17 2 2 2 8 1 8 2 1

Muestra de salida: 12

 El rango de N es 1E5 y la complejidad del tiempo debe controlarse en el nivel O (NlogN)

Nivel de complejidad de tiempo de triple ciclo de enumeración violenta O (N ^ 3

Mucho mayor que 10^8    Considere cómo optimizar

 1, DP 

Definición de estado:        

f(i): Representa el conjunto de todos los esquemas de división legal que terminan en i

Atributo: costo mínimo de partición

transición de estado:

Divida por el número de elementos en la última secuencia dividida, asumiendo que el número de elementos en la última secuencia dividida es k

Entonces f(i) = min(Σ(f(i - k) + amax)) ; Nota: amax representa el número más grande en la última secuencia dividida

2. Doble puntero + cola monotónica

Como se muestra en la figura anterior, se observa que para cualquier esquema de división mayor que f(i) , se puede encontrar un esquema de división correspondiente en f(i), y el costo de división es estrictamente >= f(i), entonces f (i) es una función monótonamente creciente

Debido a que f crece monótonamente, entonces para el valor máximo amax en el último intervalo, cuando el extremo izquierdo j del último intervalo está a la izquierda de amax en este momento, solo necesitamos considerar un punto para la transferencia: el más cercano al a la izquierda.

De esta forma, en el peor de los casos, no es necesario enumerar cada k para el cálculo del estado, sino enumerar el valor máximo de cada intervalo disponible.

Cuando el punto final izquierdo J del intervalo alcanza el amax actual, el significado de f(j) es: el esquema de división que termina en J

En el momento de la transferencia, dado que nuestra ecuación de transferencia es - f(j) + el costo de división amax del último segmento , entonces amax no se actualiza como el costo de división del último segmento. En este momento, necesitamos encontrar otro amax

De acuerdo con esta propiedad, es concebible usar una cola monotónica para mantener una secuencia monotónica de ventana deslizante

Luego, el encabezado de la cola es un amax actualmente disponible . Cuando el extremo izquierdo J excede amax, este elemento aparece y el siguiente elemento sigue siendo el valor máximo del intervalo actualmente disponible.

Nota: Este es el problema de mantener el valor máximo de la ventana deslizante en una cola monotónica. Lo que se mantiene en la cola es (valor máximo, segundo valor más grande, segundo valor más grande ...) No voy a entrar demasiado mucho detalle aquí

El algoritmo de doble puntero puede garantizar que la suma del intervalo no exceda m

Pero en este momento nuestra complejidad de tiempo sigue siendo O(N^2) en el peor de los casos

Debido a que en el peor de los casos, todavía puede haber una secuencia monótona de n elementos, para cada ventana deslizante, es necesario enumerar cada punto en la secuencia monótona para actualizar el valor mínimo y considerar cómo continuar con la optimización.

Ahora para recapitular lo que hicimos:

        Después de introducir la ecuación de transición de estado DP, para cada f(i), debe enumerarse durante la transición: el número de elementos K de  todos los  últimos esquemas de división legal.  La complejidad temporal de este algoritmo es O(N^2 ) arriba

        Dado que f(i) es una función monótonamente creciente , para cada amax disponible, solo se debe considerar el punto más pequeño

  Así que pensé en usar una cola monótona para mantener un valor máximo de una ventana deslizante , y la cabeza de la cola es el valor máximo amax actualmente disponible .

        Pero en el peor de los casos, todavía puede haber n secuencias monótonas, en este caso, es equivalente a ninguna optimización.

    

3.multiconjunto

En este momento, nuestro requisito es mantener el valor mínimo en un intervalo para el cálculo del estado.

Si el valor mínimo en este conjunto se puede mantener dinámicamente , no hay necesidad de enumerar cada amax al transferir.

Para mantener dinámicamente el valor mínimo en un intervalo, se pueden implementar muchas estructuras de datos. Por ejemplo: montón, árbol balanceado

Se puede mantener directamente mediante el conjunto  en STL  (implementado por árbol balanceado) y sincronizado con la ventana deslizante. Como máximo, se puede agregar un elemento y se puede eliminar un elemento a la vez. La complejidad es de nivel O (log) ;

De esta forma, incluso en el peor de los casos, no necesitamos enumerar todos los amax posibles (hasta n),

De esta forma, se optimiza de O(N^2) a O(NlogN) ;

 ¡Si puedes persistir en ver esto, te daré un pulgar hacia arriba!

Código:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL f[N];
int q[N];
LL a[N];
int n;
LL m;
multiset<LL> s;

void remove(LL k)//这样写的目的是维护集合中,防止把多个相同值都删掉
{
    auto x = s.find(k);
    s.erase(x);//用迭代器只删除当前位置

}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        if(a[i] > m) //出现大于m的值,一定不存在合法划分方案
        {
            puts("-1");
            return 0;
        }

    }

    int hh = 0,tt = -1;
    LL sum = 0;
    for(int i = 1, j = 1; i <= n; i ++)//双指针维护区间不超过m的滑动窗口
    {
        sum += a[i];
        while(sum > m)
        {
            sum -= a[j ++ ];//j为左端点
            if(hh <= tt && q[hh] < j)//如果队列中最左边的位置小于滑动窗口左端点 删除掉
            {
                if(hh < tt) remove(f[q[hh]] + a[q[hh + 1]]);
            
                hh ++;
            }
        }

        while(hh <= tt && a[q[tt]] <= a[i])
        /*单调队列 如果队尾元素小于等于当前元素 一定不会作为最大值更新,删除掉*/
        {
            if(hh < tt) remove(f[q[tt - 1]] + a[q[tt]]);
            tt --;
        }

        q[ ++ tt ] = i;//队尾加入当前元素
        if(hh < tt) s.insert(f[q[tt - 1]] + a[q[tt]]);//hh < tt 保证队列中至少两个点

        f[i] = f[j - 1] + a[q[hh]];
/*注意:j是区间左端点,包括在最后一个区间内的,计算的时候需要-1才是上一个区间,否则的话不符合转移方程.这一步是以第一个amax也就是单调队列中的第一个元素来计算f(i),set中维护的是从第二个amax作为最后一段最大值开始的,这步我想了很久才想通,特别注意下*/
        if(s.size()) f[i] = min(f[i],*s.begin());
    }

    printf("%lld", f[n]);


    return 0;
}

consejo :

j es el extremo izquierdo del intervalo mantenido por el puntero doble , por lo que se incluye en el último intervalo,

Luego, cuando se transfiere el estado, se requiere que -1 sea la posición final del intervalo anterior, de lo contrario, no se ajusta a la ecuación de transferencia.

Lo que se mantiene en el conjunto es la situación tras partir del segundo amax como valor máximo del último segmento 

El primer amax se calcula como el mínimo f() correspondiente al segundo amax , porque f es monótono

En otras palabras, el mínimo f() correspondiente a cada posible amax está en  la posición del amax anterior

f (la posición del amax anterior) representa  el conjunto de todos los esquemas de división que terminan en la posición del amax anterior

Entonces este valor no puede ser utilizado como valor máximo en el esquema de división del último párrafo .

Pensé en este paso durante mucho tiempo antes de darme cuenta, ¡presta especial atención!

Supongo que te gusta

Origin blog.csdn.net/m0_66252872/article/details/129526744
Recomendado
Clasificación