分类: 动态规划

  • NOIP2017 宝藏

    $$ f\left( vtx,len,set \right) \text{表示当前在点}vtx\text{,已经挖了长度为}len\text{的道路,要去挖通}set\text{这些点的最小代价} \\ \text{转移:} \\ \text{对于每一个}vtx\text{,枚举出边,对于每一个出边}\left( vtx,to \right) \text{,枚举要从这个点去挖的子集}\left( \text{这个子集必须要包含}to \right) \text{,然后统计上挖这条边的代价。} \\ \\ f\left( vtx,len,set \right) =MIN_{S\subset \ set\ and\ vtx\ \notin \ S}MIN_{edge\ \left( vtx,to \right) \ and\ to\in \ S}f\left( to,len+\text{1,}S \right) \ +\ f\left( vtx,len,S^{set} \right) \ +\left( len+1 \right) \times weight\left( vtx,to \right) \ \\ \text{边界条件:} \\ f\left( vtx,len,0 \right) =0 \\ \text{挖空集的代价为}0 \\ $$

     

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstdlib>
    #include <vector>
    #include <cstring>
    #include <string>
    typedef  int int_t;
    
    using std::cin;
    using std::endl;
    using std::cout;
    
    const int_t LARGE = 20;
    const int_t INF = 0x3f3f3f3f;
    
    
    int_t n,m;
    
    int_t graph[LARGE + 1][LARGE + 1];
    int_t memory[LARGE + 1][LARGE + 1][1 << 13];
    bool visited[LARGE + 1][LARGE + 1][1 << 13];
    int_t* subsets[1 << 13];
    struct Edge{
        int_t to;
        int_t weight;
        int_t next;
        Edge(int_t to = 0,int_t weight = 0,int_t next = 0){
            this -> to = to;
            this -> weight = weight;
            this -> next = next;
        }
    } edges[LARGE * LARGE + 1];
    int_t next = 1;
    int_t head[LARGE + 1];
    void pushEdge(int_t from,int_t to,int_t weight){
        int_t id = next++;
        edges[id] = Edge(to,weight,head[from]);
        head[from] = id;
    }
    
    int_t search(int_t vtx,int_t len,int_t set) {
    	//要去挖空集
    	if(set == 0) return memory[vtx][len][set] = 0;
    	//已经挖了长度为n - 1的链
    	if(len == n - 1) {
    		if(set != 0) return memory[vtx][len][set] = INF;
    	}
    	int_t& result = memory[vtx][len][set];
    	if(visited[vtx][len][set]) return result;
    	visited[vtx][len][set] = true;
    	//不要把结果在记忆化返回前赋值 
    	result = INF;
    	for(int_t ptr = head[vtx];ptr != 0; ptr = edges[ptr].next) {
    	    int_t to = edges[ptr].to;
    	    int_t weight = edges[ptr].weight;
    		//枚举出边
    		if(to == vtx  || (set & (1 << to)) == 0) continue;
    		//枚举要挖的子集
    		//不能去挖空集
    		for(int_t k = 1; k <= subsets[set][0]; k ++) {
    			int_t x = subsets[set][k];
    			if(x & (1 << to)) {
    				result = std::min(result,search(to , len + 1, x & (~(1 << to))) + weight * (len + 1) + search(vtx,len,set & (~(x))));
    			}
    		}
    	}
    	return result;
    }
    
    int main() {
    	cin >> n >> m;
    	for(int_t i = 0; i < n; i++) {
    		for(int_t j = 0; j < n; j++) {
    			graph[i][j] = INF;
    		}
    	}
    	for(int_t i = 1; i <= m; i ++) {
    		int_t from,to,weight;
    		cin >> from >> to >> weight;
    		from -= 1;
    		to -= 1;
    		graph[from][to] = std::min(graph[from][to],weight);
    		graph[to][from] = graph[from][to];
    	}
    	for(int_t i = 0;i < n;i++){
    	    for(int_t j = 0;j < n;j++){
    	        if(graph[i][j] < INF && i != j){
    	            pushEdge(i,j,graph[i][j]);
                }
            }
        }
        static int_t subset[1 << 13];
        for(int_t i = 1 ;i < (1 << n);i ++){
            
            for(int_t j = 1;j <= i;j++){
                if((j & i) == j) subset[++subset[0]] = j;
            }
            std::sort(subset + 1,subset + 1 + subset[0]);
            int_t* tail = std::unique(subset + 1,subset + 1 + subset[0]);
            subsets[i] = new int_t[tail - subset];
            memcpy(subsets[i],subset,sizeof(int_t) * (tail - subset));
            subset[0] = 0;
        }
    	int_t result = INF;
    	for(int_t i = 0; i < n; i++) {
    	    int_t temp = search(i,0,((1 << n) - 1) ^ (1 << i));
    		result = std::min(result,temp);
    	}
    	cout << result << endl;
    	return 0;
    }

     

  • $$ \text{题意:给定一个括号序列,求插入若干个括号,将这个括号序列变成} \\ \text{长度为}2\times n\text{的括号序列的方案数} \\ \\ \text{一开始考虑阶段设置为当前要在原串第几个字符后面加字符,然后发现根本没法转移}.. \\ \text{然后考虑把阶段设置为,当前放置了几个字符} \\ \text{设}f\left( n,m,k \right) \text{表示当前已经放置完成了}n\text{个字符,使用掉了原串的前}m\text{个字符} \\ \text{已经放置好了的字符串中,左括号比右括号多}k\text{个} \\ \text{时候的方案数} \\ \text{首先是边界条件}f\left( \text{0,0,}0 \right) =1 \\ \text{因为空串只有一种方案} \\ \text{转移时,首先考虑当前位置是左括号还是右括号。} \\ \text{然后如果这时候可以使用原串中的字符来完成转移,那么要优先使用原串字符} \\ \text{除非原串的字符不能完成这次转移,再去考虑加入新的字符} \\ \text{左括号时:} \\ f\left( n,m,k \right) =f\left( n,m,k \right) +f\left( n-\text{1,}m-\text{1,}k-1 \right) \left( \text{原串第}m\text{位置为左括号} \right) \\ f\left( n,m,k \right) =f\left( n,m,k \right) +f\left( n-\text{1,}m,k-1 \right) \left( \text{原串第}m\text{位置不是左括号} \right) \\ \text{右括号时:} \\ f\left( n,m,k \right) =f\left( n,m,k \right) +f\left( n-\text{1,}m-\text{1,}k+1 \right) \left( \text{原串第}m\text{位置为右括号} \right) \\ f\left( n,m,k \right) =f\left( n,m,k \right) +f\left( n-\text{1,}m,k+1 \right) \left( \text{原串第}m\text{位置不是右括号} \right) \\ \text{最后答案放置了}2\times n\text{个字符,使用了原串的全部字符,左括号比右括号多0个的方案数} $$

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <vector>
    #include <inttypes.h>
    #include <string>
    #include <map>
    #include <cstring>
    #define debug(x) std::cout << #x << " = " << x << std::endl;
    
    typedef long long int int_t;
    
    using std::cin;
    using std::endl;
    using std::cout;
    
    const int_t mod = 1000000007;
    const int_t LARGE = 300;
    char buff[LARGE + 10];
    int_t val[LARGE + 1];
    int_t prefix[LARGE + 1];
    int_t dp[LARGE + 1][LARGE + 1][LARGE + 1];
    int_t n;
    int_t len;
    
    void inc(int_t& x,int_t p) {
    	x = (x % mod + p + 2 * mod) % mod;
    }
    
    int main() {
    	freopen("regular.in","r",stdin);
    	freopen("regular.out","w",stdout);
    
    	cin >> n;
    	scanf("%s",buff + 1);
    	len = strlen(buff + 1);
    	for(int_t i = 1; i <= len; i++) {
    		if(buff[i] == '(') val[i] = 1;
    		else val[i] = -1;
    		prefix[i] = prefix[i - 1] + val[i];
    	}
    	dp[0][0][0] = 1;
    	for(int_t i = 1; i <= 2 * n; i++) {
    		for(int_t j = 0; j <= std::min(i,len); j++) {
    			for(int_t k = 0; k <= i; k++) {
    				//×óÀ¨ºÅ
    				if(val[j]==1&&k-1>=0&&j>0){
    					inc(dp[i][j][k],dp[i-1][j-1][k-1]);
    				} else{
    					if(k-1>=0)inc(dp[i][j][k],dp[i-1][j][k-1]);
    				}
    				//ÓÒÀ¨ºÅ 
    				if(val[j]==-1&&j>0){
    					inc(dp[i][j][k],dp[i-1][j-1][k+1]);
    				} else{
    					inc(dp[i][j][k],dp[i-1][j][k+1]);
    				}
    				continue;
    			}
    		}
    	}
    	cout<<dp[2 * n][len][0]<<endl;
    	return 0;
    }
    
  • Luogu1373 小a和uim之大逃离

    $$ \text{设}f\left( n,m,k,d \right) \text{表示走到了格子}\left( n,m \right) \text{,} \\ \text{小}a\text{采集的魔液与}uim\text{采集的魔夜的差为}k\text{,这一步是小}a\text{还是}uim\text{采集的方案数} \\ \text{初始时,}f\left( n,m,mat_{n,m},1 \right) =\text{1\ }\left( \text{要求可以从任意点出发} \right) \\ \text{转移时} \\ f\left( n+\text{1,}m,k+mat_{n+\text{1,}m},0 \right) +=f\left( n,m,k,1 \right) \\ f\left( n,m+\text{1,}k+mat_{n,m+1},0 \right) +=f\left( n,m,k,1 \right) \\ f\left( n+\text{1,}m,k-mat_{n+\text{1,}m},1 \right) +=f\left( n,m,k,0 \right) \\ f\left( n,m+\text{1,}k-mat_{n,m+1},1 \right) +=f\left( n,m,k,0 \right) \\ \text{最后答案为所有}f\left( n,m,\text{0,}1 \right) \text{的和。} \\ $$

    #include <iostream>
    using int_t = int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t mod = 1000000007;
    
    int_t n, m, k;
    //走到了n行m列,a与uim的差为k,最后一步0:a,1:uim的方案数
    int_t f[802][802][16][2];
    int_t mat[802][802];
    
    inline void inc(int_t &x, int_t b)
    {
        x = ((x + b) % mod + mod) % mod;
    }
    
    int main()
    {
        scanf("%d%d%d", &n, &m, &k);
        k += 1;
        for (int_t i = 1; i <= n; i++)
        {
            for (int_t j = 1; j <= m; j++)
            {
                scanf("%d", &mat[i][j]);
                f[i][j][mat[i][j]][0] = 1;
            }
        }
        int_t result = 0;
        for (int_t i = 1; i <= n; i++)
        {
            for (int_t j = 1; j <= m; j++)
            {
                for (int_t k = 0; k < ::k; k++)
                {
                    //下一步是a
                    inc(f[i + 1][j][(k + mat[i + 1][j]) % ::k][0], (f[i][j][k][1]));
                    inc(f[i][j + 1][(k + mat[i][j + 1]) % ::k][0], (f[i][j][k][1]));
                    //下一步是uim
                    inc(f[i + 1][j][(k - mat[i + 1][j] + ::k) % ::k][1], f[i][j][k][0]);
                    inc(f[i][j + 1][(k - mat[i][j + 1] + ::k) % ::k][1], f[i][j][k][0]);
                }
                inc(result, f[i][j][0][1]);
            }
        }
        printf("%d", result);
        return 0;
    }

     

  • Luogu1220 关路灯

    设$f(l,r,d)$表示区间[l,r]内的所有路灯全关掉之后,人站在位置l还是位置r的最小代价。

    因为所有路灯关掉之后人可以随便走而不消耗任何代价,所以最终答案是f[1][n][0]和f[1][n][1]的最小值。

    转移时,区间[l,r]站在l时的答案的答案可以从区间[l+1,r]转移而来(关完了之后恰好去关掉第l个灯),并不需要从区间[l,r-1]转移,因为关掉[l,r-1]之后又去关r,关掉r又跑回l,这没有意义。

    同理站在r时的答案也可以这样转移。

    #include <iostream>
    #include <algorithm>
    #include <algorithm>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 50;
    const int_t INF = 0x7fffffff;
    int_t pos[LARGE + 1];
    int_t n;
    int_t power[LARGE + 1];
    int_t init;
    int_t dp[LARGE + 1][LARGE + 1][2];
    int_t f(int_t left, int_t right, int_t front, int_t back)
    {
        return (power[n] - power[right - 1] + power[left]) * (pos[back] - pos[front]);
    }
    
    int main()
    {
    
        cin >> n >> init;
        for (int_t i = 1; i <= n; i++)
        {
            cin >> pos[i] >> power[i];
            power[i] += power[i - 1];
        }
        for (int_t i = 1; i <= n; i++)
        {
            for (int_t j = 1; j <= n; j++)
            {
                dp[i][j][0] = dp[i][j][1] = INF;
            }
        }
        dp[init][init][0] = dp[init][init][1] = 0;
        for (int_t j = init; j <= n; j++)
        {
            for (int_t i = j - 1; i >= 1; i--)
            {
                dp[i][j][0] = std::min(
                    dp[i + 1][j][0] + f(i, j + 1, i, i + 1),
                    dp[i + 1][j][1] + f(i, j + 1, i, j));
                dp[i][j][1] = std::min(
                    dp[i][j - 1][0] + f(i - 1, j, i, j),
                    dp[i][j - 1][1] + f(i - 1, j, j - 1, j));
            }
        }
        cout << std::min(dp[1][n][0], dp[1][n][1]);
        return 0;
    }

     

  • NOIP2016 愤怒的小鸟

    $$ \text{设}state\text{是还没死的猪的集合。} \\ \text{转移时,首先考虑一条抛物线只打一头猪的情况,然后转移。} \\ \text{然后再考虑两头猪连成一条抛物线的情况,然后判断一下其他哪些猪也在这条抛物线上} \\ \text{然后转移。} \\ \text{这样做的复杂度是}O\left( T\times n^3\times 2^n \right) \\ \text{在}luogu\text{会}TLE\text{两个点。} \\ \text{考虑优化枚举两头猪连成一条抛物线的过程。} \\ \text{先处理出来一个数组}sets\left[ i \right] \left[ j \right] \text{,表示经过第}i\text{头和第}j\text{头猪的抛物线可以打下来的猪的集合。} \\ \text{然后转移的复杂度可以优化到}O\left( T\times n^2\times 2^n \right) \\ \text{然后}luogu\ AC,LOJ\ AC $$

     

    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <cstdio>
    #include <cmath>
    #include <assert.h>
    using int_t = long long int;
    
    using real_t = double;
    
    using std::cin;
    using std::cout;
    using std::endl;
    const int_t LARGE = (1 << 20) - 1;
    const int_t INF = 0x7fffffff;
    const real_t EPS = 1e-6;
    template <class T>
    T fabs(T x)
    {
        if (x < 0)
            return -x;
        return x;
    }
    template <class A, class B>
    auto fcmp(A a, B b) -> decltype(a - b)
    {
        if (fabs(a - b) < EPS)
            return 0;
        return a - b;
    }
    struct Processor
    {
        struct Pig
        {
            real_t x;
            real_t y;
            int_t bit;
        } pigs[20];
        int_t n, m;
        //经过i j 两个点抛物线所能打下来的猪的集合
        int_t sets[20][20];
        int_t memory[LARGE];
        bool visited[LARGE];
        int_t search(int_t state)
        {
            if (state == 0)
                return 0;
            if (visited[state])
                return memory[state];
            auto &result = memory[state];
            visited[state] = true;
            result = INF;
            std::vector<Pig> alive;
            for (int_t i = 0; i < n; i++)
            {
                if (state & (1 << i))
                {
                    //考虑一条抛物线打一头猪
                    result = std::min(search(state ^ (1 << i)) + 1, result);
                    alive.push_back(pigs[i]);
                    alive.back().bit = i;
                }
            }
            //考虑任何两头猪构成的抛物线
            for (int_t i = 0; i < alive.size(); i++)
            {
                for (int_t j = i + 1; j < alive.size(); j++)
                {
                    if (sets[alive[i].bit][alive[j].bit])
                    {
                        result = std::min(result, search(state & (~sets[alive[i].bit][alive[j].bit])) + 1);
                    }
                }
            }
            return result;
        }
    
        int_t processs()
        {
    
            cin >> n >> m;
            for (int_t i = 0; i < n; i++)
            {
                cin >> pigs[i].x >> pigs[i].y;
            }
            for (int_t i = 0; i < n; i++)
            {
                for (int_t j = i + 1; j < n; j++)
                {
                    auto &curr = sets[i][j];
                    curr = 0;
                    real_t x1 = pigs[i].x, y1 = pigs[i].y;
                    real_t x2 = pigs[j].x, y2 = pigs[j].y;
                    if (x1 == x2)
                        continue;
                    real_t a = ((y1 / x1) - (y2 / x2)) / (x1 - x2);
                    if (a >= 0)
                        continue;
                    real_t b = ((y1 / (x1 * x1)) - (y2 / (x2 * x2))) / ((1 / x1) - (1 / x2));
                    for (int_t k = 0; k < n; k++)
                    {
                        auto &currp = pigs[k];
                        if (fabs(currp.y - (currp.x * currp.x * a + currp.x * b)) <= EPS)
                        {
                            curr ^= (1 << k);
                        }
                    }
                }
            }
            std::fill(visited, visited + LARGE, false);
            return search((1 << n) - 1);
        }
    };
    
    int main()
    {
        int_t T;
        cin >> T;
        for (int_t i = 1; i <= T; i++)
        {
            auto p = new Processor;
            cout << p->processs() << endl;
            delete p;
        }
        return 0;
    }

     

  • luogu1725 琪露诺

    我我我我没救了吧…

    现在普及难度的题还能写1h…

    我..


    单调队列优化DP,或者线段树优化。

    $$f\left( n \right) =A_n+\max _{n-r\le i\le n-l}f\left( i \right) $$

    后面的那个max显然可以用单调队列来维护。

    从前向后枚举状态,每次枚举到一个位置,先把因为枚举到这个位置而能够使用的最新的状态扔到单调队列(即i-left,因为i-left在枚举到状态i之前一定不会用到),然后从队列头删掉不合法的状态。

    #include <iostream>
    #include <algorithm>
    
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 200000;
    
    int_t A[LARGE * 2 + 1];
    int_t f[LARGE * 2 + 1];
    
    int_t queue[LARGE * 3 + 1];
    int_t tail = 0;
    int_t head = 0;
    
    int_t left, right;
    int_t n;
    
    const int_t INF = 0x7fffffff;
    
    int main()
    {
        cin >> n >> left >> right;
        for (int_t i = 0; i <= n; i++)
            cin >> A[i];
        //构造队列,使得队头的元素解最优
        queue[tail++] = 0;
        for (int_t i = 1; i <= n + right; i++)
        {
            //加入新取到的元素
            if (i - left >= 0)
            {
                while (tail > head && f[queue[tail - 1]] < f[i - left])
                {
                    tail--;
                }
                queue[tail++] = i - left;
            }
            while (tail > head && queue[head] < i - right)
            {
                head++;
            }
    
            if (tail > head && queue[head] <= i - left)
            {
                f[i] = A[i] + f[queue[head]];
            }
        }
        int_t result = -INF;
        for (int_t i = n + 1; i <= n + right; i++)
            result = std::max(result, f[i]);
        cout << result << endl;
        return 0;
    }

     

  • APIO2014 序列分割

    很简单的斜率优化题(捂脸)

    $$ \text{状态转移方程} \\ f\left( k,n \right) \text{表示在前}n\text{个数中切了}k\text{刀的最优解} \\ \left( \text{对于}n\le k\text{或者}k=\text{0,结果为}0 \right) \\ \text{设}S_n=\sum_{1\le i\le n}{a_i} \\ f\left( k,n \right) =MAX_{1\le i\le n-1}f\left( k-\text{1,}i \right) +\left( S_n-S_i \right) S_i \\ \text{以}k\text{为阶段进行转移} \\ \text{在从}k-\text{1转移到}k\text{时,维护一个单调队列,从队尾到队首的解越来越差} \\ \text{如何构造这个单调队列?} \\ \text{假设单调队列中的所有的元素已经有序,设其中最后的两个元素的}n\text{的取值为}i,j\ \text{设我们要加入到队列的元素的}n\text{的取值为}k \\ \text{设}g\left( x \right) =f\left( k-\text{1,}x \right) -S_{x}^{2} \\ \text{以为单调队列前面的元素比后面的优,所以}i\text{是比}j\text{优的} \\ \text{假如现在}k\text{还比}j\text{优,那么我们显然可以直接把}j\text{扔了,因为它没用} \\ \text{考虑什么时候}k\text{比}j\text{优} \\ \text{如果}i\text{比}j\left( i<j \right) \text{优,那么一定有} \\ f\left( k-\text{1,}i \right) +S_nS_i-S_{i}^{2}>f\left( k-\text{1,}j \right) +S_nS_j-S_{j}^{2} \\ \text{整理一下} \\ f\left( k-\text{1,}i \right) -S_{i}^{2}-\left( f\left( k-\text{1,}j \right) -S_{j}^{2} \right) >S_n\left( S_j-S_i \right) \\ g\left( i \right) -g\left( j \right) >S_n\left( S_j-S_i \right) \\ i<j,\text{所以}S_i<S_j\ \left( S_n\text{单调不降} \right) \\ \text{所以不等式不要变号} \\ \frac{g\left( i \right) -g\left( j \right)}{S_j-S_i}>S_n \\ \text{两边取负} \\ \frac{g\left( i \right) -g\left( j \right)}{S_i-S_j}<-S_n \\ \text{同理,如果}k\text{优于}j\text{,那么我们可以得到} \\ \frac{g\left( k \right) -g\left( j \right)}{S_k-S_j}>-S_n \\ \text{所以我们可以得到,如果}i\text{优于}j\text{而且}k\text{优于}j\left( \text{这个情况应该扔掉}j \right) \ i,j,k\text{应该满足的条件是} \\ \frac{g\left( k \right) -g\left( j \right)}{S_k-S_j}>\frac{g\left( i \right) -g\left( j \right)}{S_i-S_j} \\ \text{即}k,j\text{所构成的直线的斜率大于}i,j\text{所构成的直线的斜率} \\ \text{所以在压入元素}k\text{时,我们需要一直判断直线}ij\text{和直线}jk\text{的斜率关系,如果满足直线}jk\text{的斜率大于直线}ij\text{的斜率,那么我们就需要扔掉点}j \\ \text{一直重复,直到队列中只剩下一个点或者}jk\text{的斜率小于等于}ij\text{的斜率} \\ \text{同时,每个}n\text{的取值在计算时,我们要做的是从队列头取元素} \\ \text{然而}i<j\text{且}i\text{优于}j\text{的条件是} \\ \frac{g\left( i \right) -g\left( j \right)}{S_i-S_j}<-S_n \\ \text{所以我们需要保证队头的元素满足这个关系} \\ \text{所以我们每次考虑队头的两个元素}i,j\text{,如果不满足这个关系,那么就扔掉}i\text{,因为这个时候}i\text{不可能比}j\text{优} \\ \text{然后转移即可} \\ \text{单调队列中的点构成了一个上凸壳,相邻两点所连成的直线,从左到右满足斜率单调不升} \\ \\ $$

    #include <iostream>
    #include <algorithm>
    #include <deque>
    #include <cstring>
    using int_t = long long int;
    using real_t = long double;
    
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 100000;
    int_t sum[LARGE + 1];
    int n, k;
    const real_t EPS = 1e-12;
    struct Point
    {
        //g(pos)
        real_t value;
        //n的取值
        int_t pos;
        Point(real_t val = 0, int_t pos = 0)
        {
            this->value = val;
            this->pos = pos;
        }
    };
    
    real_t scope(const Point &a, const Point &b)
    {
        if (sum[a.pos] == sum[b.pos])
            return (real_t)(a.value - b.value) / EPS;
        return (real_t)(a.value - b.value) / (real_t)(sum[a.pos] - sum[b.pos]);
    }
    int_t dp[201][LARGE + 1];
    int_t path[201][LARGE + 1];
    Point queue[LARGE * 2 + 1];
    int main()
    {
        scanf("%d%d", &n, &k);
        for (int_t i = 1; i <= n; i++)
        {
            scanf("%lld", &sum[i]);
            sum[i] += sum[i - 1];
        }
    
        //左闭右开区间
        int_t head = 0;
        int_t tail = 0;
        for (int_t k = 1; k <= ::k; k++)
        {
            //每次开始时清空队列
            head = tail = 0;
            //把唯一一个合法的点扔进去
            queue[tail] = Point(dp[k - 1][k] - sum[k] * sum[k], k);
            tail++;
            for (int_t n = k + 1; n <= ::n; n++)
            {
                /*
                g(n)=f(k-1,n)-S(n)^2
                */
                //弹出不合法的元素
                while (tail - head >= 2 && scope(queue[head], queue[head + 1]) >= -sum[n])
                    head++;
                //写结果
                int_t prev = queue[head].pos;
                dp[k][n] = dp[k - 1][prev] + (sum[n] - sum[prev]) * sum[prev];
    #ifdef DEBUG
                cout << "(k=" << k << " n= " << n << ")=" << dp[k][n] << " from (k=" << k - 1 << " n= " << prev << ") = " << dp[k - 1][prev] << endl;
    #endif
                path[k][n] = prev;
                //加入新的元素
                Point curr(dp[k - 1][n] - sum[n] * sum[n], n);
                while (tail - head >= 2 && (sum[curr.pos] == sum[queue[tail - 1].pos] || scope(curr, queue[tail - 1]) >= scope(queue[tail - 1], queue[tail - 2])))
                {
                    tail--;
                }
                queue[tail] = curr;
                tail++;
            }
    #ifdef DEBUG
            cout << endl;
    #endif
        }
    
        printf("%lld\n", dp[k][n]);
        int_t id = path[k][n];
        for (int_t k = ::k; k >= 1; k--)
        {
            printf("%lld ", id);
            id = path[k - 1][id];
        }
        return 0;
    }

     

  • HDU 2089 不要62

    不要62

    Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
    Total Submission(s): 50757    Accepted Submission(s): 19256

    Problem Description
    杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
    杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
    不吉利的数字为所有含有4或62的号码。例如:
    62315 73418 88914
    都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
    你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。

     

    Input
    输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。

     

    Output
    对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。

     

    Sample Input
    1 100 0 0

     

    Sample Output
    80

     

    Author
    qianneng
    地址http://acm.hdu.edu.cn/showproblem.php?pid=2089

    数位DP
    采用记忆化搜索

    $$f(pos,num,less)=0 (num==4时)$$

    $$f(pos,num,less)=0 (less==false且num>arr[pos]时,其中arr是要处理的数的各个数位的数组\ 从高到低排列)$$

    $$f(pos,num,less)=1 (pos==1且num!=4)$$

    $$f(pos,num,less)=\sum _{i=0,num=6与i=2不同时满足}^{9} f(pos-1,i,less||num<arr[pos])$$

    pos:当前要确定的位 从高到低

    num:当前位要搜索的数字

    less:之前的位有没有出现枚举的位比原数中的位小的情况(这样当前位就可以枚举0-9所有的数字 例如枚举上界为15234 那么以15开头只能枚举到15234 而以14开头则能枚举到14999)

    f(pos,num,less) 返回在第pos位上 以num开头 符合条件的数的个数

    最后的结果是

    $$\sum _{i=0}^9 f(pos,i,false)$$

    代码奉上:

     

    // luogu-judger-enable-o2
    
    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    /*
     * File:   main.cpp
     * Author: Ytong
     *
     * Created on 2017年11月10日, 下午2:54
     */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    //#define DEBUG
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    using std::max;
    using std::min;
    using std::sort;
    using std::pair;
    using std::map;
    using std::deque;
    
    typedef long long int int_t;
    int_t memory[20][20][2];
    bool visited[20][20][2];
    //第pos为 以num开头的符合要求的数字个数
    //less:以前的位是否存在枚举的位比约束小的情况
    
    int_t search1(int_t* arr, int_t pos, int_t num, bool less = false, bool first = true) {
        //    cout << "-----";
    //    cout << "searching pos " << pos << " num " << num << " less " << less << endl;
        if (first) {
            memset(visited, 0, sizeof (visited));
        }
        if (num == 4) {
    //        cout << "num=4 return0;" << endl;
            return 0;
        }
        if (less == false && num > arr[pos]) {
    //        cout << "less=false" << " num=" << num << " arr pos=" << arr[pos] << endl;
            return 0;
        }
        if (pos == 1) {
    //        cout << "pos=" << 1 << " return 1 " << endl;
            return 1;
        }
        if (visited[pos][num][less]) {
    //        cout << "memory return " << memory[pos][num][less] << endl;
            return memory[pos][num][less];
        }
        int_t result = 0;
        for (int_t i = 0; i <= 9; i++) {
            if (num == 6 && i == 2) continue;
            result += search1(
                    arr,
                    pos - 1,
                    i,
                    less || num < arr[pos],
                    false
                    );
    
        }
    
        visited[pos][num][less] = true;
        return memory[pos][num][less] = result;
    }
    
    int_t search2(int_t* arr, int_t pos) {
        int_t result = 0;
        for (int_t i = 0; i <= arr[pos]; i++) {
       result += search1(arr, pos, i);
        }
        return result;
    }
    //没有考虑内存泄漏
    int_t* toArray(int_t x) {
        int_t* result = new int_t[10];
        if (x == 0) {
            result[0] = 1;
            result[1] = 0;
            return result;
        }
        result[0] = 0;
    
        while (x) {
            result[++result[0]] = x % 10;
            x /= 10;
        }
        return result;
    }
    
    
    int main() {
        while (true) {
            int_t lower, higher;
            cin >> lower>>higher;
            if (lower == 0 && higher == 0) return 0;
            int_t* h = toArray(higher);
            int_t* l = toArray(lower - 1);
            cout << search2(h, h[0]) - search2(l, l[0]) << endl;
        }
    
        return 0;
    }
    
  • 求最长上升子序列的O(n log n)算法

    上升子序列:从一个序列中按照顺序挑出一些数,使得后一个数大于前一个数

    例如对于序列$$1,5,3,7,7,2$$

    $$1,5,7$$ $$5,7$$ $$1,3$$都是其上升子序列

    但是$$1,5,7,7$$不是上升子序列(不满足后一个数大于前一个数)

    最长上升子序列:一个序列中最长的上升子序列

    对于序列$$1,5,3,7,7,2$$ 其最长上升子序列是$$1,3,7$$或者$$1,5,7$$

     

    求最长上升子序列的算法

    1.朴素的O(n²)的动态规划

    设dp[tail]为序列中以第tail个元素结尾的最长上升子序列的长度

    则有dp[1]=1 (第一个数自成一个最长上升子序列)

    tail>1时$$dp[tail]=max\{0,dp[1],dp[2],dp[3]…dp[i]…dp[tail-1]|满足seq[i]<seq[tail]\}+1$$

    结果为$$max{dp[i]|1<=i<=序列长度}$$

    解释:求dp[tail]时,从序列中tail前的所有数中找到比seq[tail]小的数,在满足这个数比seq[tail]小的情况下使dp[tail]最大

    举例:对于序列

    $$1,5,3,2,5,6$$

    使用seq[1]表示序列中的第一项,seq[2]表示序列中的第二项,以此类推

    初始时dp[1]=1;

    然后开始求dp[2] 发现seq[1]<seq[2] 所以dp[2]=dp[1]+1=2

    开始求dp[3] 发现seq[1]<seq[3] 更新dp[3]=dp[1]+1=2

    开始求dp[4] 发现seq[1]<seq[4] 更新dp[4]=dp[1]+1=2;

    开始求dp[5] 发现seq[1]<seq[5] 更新dp[5]=dp[1]+1=2

    又发现seq[3]<seq[5] 再次更新dp[5]=dp[3]+1=3

    开始求dp[6] 发现seq[1]<seq[6] 更新dp[6]=dp[1]+1=2

    又发现seq[2]<seq[6] 更新dp[6]=dp[2]+1=3

    又发现seq[3]<seq[6] 更新dp[6]=dp[3]+1=3

    以此类推

     

    最终 dp[1]=1 dp[2]=2 dp[3]=2 dp[4]=2 dp[5]=3 dp[6]=3

     

    最终结果为3

     

    程序:

    #include 
    
    using namespace std;
    typedef long long int_t;
    int_t seq[1001];
    int_t dp[1001];
    
    int main() {
        dp[1] = 1;
        int_t n;
        cin>>n;
        for (int_t i = 1; i <= n; i++) cin >> seq[i];
        //一次性计算出LIS
        for (int_t i = 1; i <= n; i++) {
            int_t length = 0;
            for (int_t j = 1; j <= i; j++) { if (seq[i] > seq[j]) {
                    if (dp[j] > length) length = dp[j];
                }
            }
            dp[i] = length + 1;
    
        }
        int_t result = 0;
        for (int_t i = 1; i <= n; i++) {
            result = max(result, dp[i]);
        }
        cout << result;
        return 0;
    }
    

    2.复杂度为O(n log n)的算法

    设dp[i]为以序列中第i个元素结尾的最长上升子序列的长度,那么如果对于序列中任意两个数seq[i]和seq[j] 如果满足dp[i]==dp[j]且seq[i]<seq[j] 那么仅保存i一定不会丢失最优解(因为seq[i]<seq[j],所以可以接到seq[j]后面的数一定可以接到seq[i]后面,然而可以接到seq[i]后面的数却不一定能接到seq[j]后面)

    设g[x]为满足dp[i]==x的最小的seq[i]

    下一句的最长上升子序列长度是指以该数结尾的最长上升子序列的长度

    例:g[3]为满足最长上升子序列长度为3的序列中最小的数

    对于以下序列$$1,5,3,6,7$$

    $$

    g[1]=1

    g[2]=3 (不能是5,因为3比5小)

    g[3]=6

    g[4]=7

    g[5]=\infty (因为不存在长度的5的最长上升子序列)

    $$

    同时一定满足

    $$g[1]<=g[2]<=g[3]<=g[4]……..<=g[n]$$

    所以为了确定序列中的一个数seq[i]的最长上升子序列长度,只需要在g数组中进行二分查找即可。查找后,查找结果即为以seq[i]结尾的最长上升子序列的长度

    但要注意:

    假设在g中二分查找seq[i]所得到的结果是index,那么还需要把g[index]改为seq[i],以此维护g数组的单调性(因为seq[i]一定比g[index]小,如果seq[i]比g[index]大,那么二分查找的结果就不是index了)

    代码:注意,初始时g数组需要全部设置为$$\infty$$

     

     

    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    int_t seq[100000 + 1];
    int_t g[100000 + 1];
    int_t result[100000 + 1];
    
    int main() {
        int_t n;
        cin>>n;
        for (int_t i = 1; i <= n; i++) cin >> seq[i];
        memset(g, 0x7f, sizeof (g));
    
        for (int_t i = 1; i <= n; i++) {
            int_t index = lower_bound(g + 1, g + 1 + n, seq[i]) - g;
            result[i] = index;
            g[index] = seq[i];
        }
        int_t r = 0;
        for (int_t i = 1; i <= n; i++) r = max(r, result[i]);
        cout << r;
        return 0;
    }
    

    因为二分查找的复杂度为$$O(log n)$$,所以该算法的复杂度为$$O(n log n)$$

     

    两种算法的效率对比:

    数据量为10000时:

     
    每组数据第一行为算法2,第二行为算法1

    1:
            0
            0.672
    2:
            0
            0.656
    3:
            0.016
            0.641
    4:
            0.015
            0.656
    5:
            0.016
            0.641
    6:
            0.015
            0.719
    7:
            0
            0.656
    8:
            0.016
            0.609
    9:
            0
            0.687
    10:
            0.016
            0.672
    
    

    数据量为100000时

    1:
            0.094
            65.765
    2:
            0.093
            63.454
    3:
            0.078
            65.172
    4:
            0.093
            65.391
    5:
            0.079
            61.999
    6:
            0.094
            63.703
    7:
            0.094
            65.047
    8:
            0.094
            65.124
    9:
            0.109
            64.954
    10:
            0.062
            63.766
    
    可以看到,n log n的算法明显要比n^2的算法快