동적 계획 구간 DP 입문 (기초 초과)

구간 DP:
(1) 합병: 곧 두 개 또는 여러 부분 을 통합 시 킬 것 이 고 당연히 반대로 할 수 있다.(2) 특징: 문 제 를 두 가지 합병 할 수 있 는 형식 으로 나 눌 수 있다.(3) 구 해: 전체 문 제 를 가장 좋 은 값 으로 버 리 고 합병 점 을 매 거 하 며 문 제 를 좌우 두 부분 으로 분해 하고 마지막 으로 합 친 두 부분의 가장 좋 은 값 은 원래 문제 의 가장 좋 은 값 으로 할 수 있다.
* 예제:
NC 13230 (답장 하위 문자열 통합)
제목 설명: 두 문자열 A 와 B 를 입력 하고 하나의 문자열 C 로 합 쳐 A 와 B 에 속 하 는 문 자 는 C 에서 순서 가 변 하지 않 습 니 다.예 를 들 어 'abc' 와 'xyz' 는 'axbycz' 나 'abxcyz' 등 으로 조합 할 수 있다.우 리 는 문자열 의 가 치 를 가장 긴 답장 문자열 의 길이 로 정의 합 니 다. (답장 문자열 은 정반 양쪽 에서 완전히 일치 하 는 문자열 을 표시 합 니 다. 예 를 들 어 "aba" 와 "xyyx")가능 한 모든 C 에서 가장 큰 값 을 가 진 문자열 을 구 해 야 합 니 다. 이 최대 값 을 출력 하면 됩 니 다.
입력 설명: 첫 줄 의 정수 T (T ≤ 50).다음 2T 줄 은 두 줄 마다 A, B (| A |, | B | ≤ 50), A, B 의 문자 집합 은 전체 소문 자 를 나타 낸다.
출력 설명: 각 그룹의 데이터 출력 에 대해 한 줄 의 정수 가 가장 큰 C 의 가 치 를 표시 합 니 다.
샘플:
2
aa
bb
a
aaaabcaa
4
5

++++++++++
DP 상태 분석: 저자: 왕 칭 링크 - >: 사고방식 상세 분석
Dp [i] [j] 까지 두 가지 상황 이 있 습 니 다.
  • s [i] = s [j]: 이 럴 때 s [i] 와 s [j] 를 각각 0 의 답문 서브 시퀀스 의 양 끝, 즉 i + 1 에서 j - 1 구간 의 문자 로 구 성 된 가장 긴 답문 서브 시퀀스 의 양 끝 에 추가 할 수 있 습 니 다. 이때 dp [i] [j] = dp [i + 1] [j - 1] + 2;
  • s[i] != s [j]: 그럼 {i} i 에서 {j} j 구간 의 가장 긴 답장 서브 시퀀스 에서 {i} i 문자 와 {j} j 문 자 는 동시에 존재 할 수 없 기 때문에 둘 중 하 나 를 버 릴 수 있 습 니 다 (임의의 것 이 아 닙 니 다). 이때 dp [i] [j] = max (dp [i + 1] [j], dp [i] [j - 1]).

  • 그리고 두 꼬치 의 합병 은 바로 이 두 가지 상태 전이 방정식 의 서브 상태 로 코드 를 구체 적 으로 본다.
    ++++++++++
    AC 코드 (times = 973 ms / 2000 ms):
    //  DP
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #define LL long long
    #define mem(x,y) memset(x,y,sizeof(x))
    const int maxn = 52;
    const int mod = 1e9 + 7;
    using namespace std;
    namespace needs {
    	template<typename T> inline void read(T& ans)
    	{
    		T x = 0, f = 1; char c = getchar();
    		while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
    		while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + c - '0', c = getchar();
    		ans = f * x;
    	}
    	inline void read(string& st1)
    	{
    		char ch = getchar(); st1 = "";
    		while (!((ch >= 'a') && (ch <= 'z'))) ch = getchar();
    		while ((ch >= 'a') && (ch <= 'z')) st1 += ch, ch = getchar();
    	}
    }using namespace needs;
    int dp[maxn][maxn][maxn][maxn];
    //i,j,k,l;    (a )   i       j        (b )   k       l             
    int main()
    {
    	int t;read(t);
    	while (t--)
    	{
    		mem(dp, 0);
    		string  a, b; read(a), read(b);
    		int alen = a.size() , blen = b.size() , ans = 0;
    		a = ' ' + a; b = ' ' + b;//     ,    -1   
    		for (int lena = 0; lena <= alen; ++lena)//  a    
    			for (int lenb = 0; lenb <= blen; ++lenb)//  b    
    				for (int i = 1; i <= alen - lena + 1; ++i)//  a       
    					for (int k = 1; k <= blen - lenb + 1; ++k)//  b       
    					{
    						int j = i + lena - 1, l = k + lenb - 1;//                   
    						if (lena + lenb <= 1) dp[i][j][k][l] = 1;//    ,           
    						else
    						{
    						    //    
    							if (a[i] == a[j]) dp[i][j][k][l] |= dp[i + 1][j - 1][k][l];
    							//  a[i+1]   a[j−1]   b[k]   b[l]           a[i]   a[j]     
    							if (a[i] == b[l]) dp[i][j][k][l] |= dp[i + 1][j][k][l - 1];
    							//  a[i+1]   a[j]   b[k]   b[l−1]           a[i]   b[l]     
    							if (a[j] == b[k]) dp[i][j][k][l] |= dp[i][j - 1][k + 1][l];
    							//  a[i]   a[j−1]   b[k+1]   b[l]           b[k]   a[j]     
    							if (b[k] == b[l]) dp[i][j][k][l] |= dp[i][j][k + 1][l - 1];
    							//  a[i]   a[j]   b[k+1]   b[l−1]           b[k]   b[l]     
    						}
    						if(dp[i][j][k][l]) ans = max(ans, lena + lenb);//    
    					}
    		printf("%d
    "
    , ans);// } return 0; }

    NC 16129 (작은 도장공)
    제목 설명: "lalala, 나 는 즐 거 운 도장공 입 니 다.""사장 님 은 목이 쉬 도록 소 리 를 질 렀 습 니 다. 고민 하 는 작은 이름 은 잘 리 고 싶 지 않 기 때문에 가능 한 한 빨리 벽 을 닦 고 싶 습 니 다. 본인 의 수학 기초 가 매우 좋 지 않 기 때문에 그 는 지금 당신 에 게 적어도 모든 벽 을 완성 하려 면 몇 번 을 닦 아야 하 는 지 계산 해 달라 고 부 탁 했 습 니 다. 각 벽 은 n 개의 단락 이 있 고 각 단락 에 목표 색 ci 를 지정 하 였 습 니 다. 처음에 모든 벽 은 흰색 이 었 습 니 다. 우 리 는 지금 하 나 를 가지 고 있 습 니 다.브러시 의 길 이 는 k 입 니 다. 브러시 는 매번 한 가지 색 을 선택 한 다음 (1 ~ k) 연속 적 인 벽 구간 을 선택 하여 선택 한 색 으로 칠 할 수 있 습 니 다. 벽 을 목표 색 으로 바 꾸 기 위해 서 는 최소한 몇 번 을 칠 해 야 하 는 지 알 고 싶 습 니 다.
    * TIPS: 아 는 것 은 같은 문제 형 문 제 를 공 고 히 해 볼 수 있 습 니 다. 모 르 는 것 은 먼저 간단 한 버 전 NC 19909 를 만 들 수 있 습 니 다. 같은 유형 에 비해 이 문 제 는 간단 합 니 다.
    입력 설명: 모든 사례 에 대해 우리 의 첫 번 째 줄 은 두 개의 정수 n, k (1 < = n < = 100, 1 < = k < = 50, k 를 포함 합 니 다.
    출력 설명: 작은 이름 을 최소 몇 번 칠 하 는 지 를 나타 내 는 숫자 를 출력 합 니 다.
    샘플:
    3 3
    1 2 1
    
    2
    

    ++++++++++
    5 4
    5 4 3 3 4
    
    3
    

    ++++++++++
    DP 상태 분석: 저자: 돌아 오 는 꿈 의 링크: 사고의 출처 dp [i] [j] 는 i 번 째 위치 에서 j 번 째 위치 까지 필요 한 최소 걸음 수 (n 은 솔 의 길이, k 는 매 거 진 구간 단점) 의 첫 번 째 상황 을 나타 낸다. n > = len 일 때 (len 은 현재 매 거 진 길이) 우 리 는 직접 그 릴 수 있다. 우 리 는 매 거 진 단점 k 일 때 a [i] = a [k] 를 먼저 판단 할 수 있다.만약 에 똑 같이 dp [i] [j] = min (dp [i] [j], dp [i + 1] [k] + dp [k + 1] [j]) 이 있 으 면 최소 조작 횟수 는 i 번 째 와 k 번 째 를 함께 바 르 는 것 이기 때문에 구간 [i, j] 에서 점 i 를 하면 두 번 째 상황 을 고려 하지 않 아 도 된다.
    구체 적 분석 코드 참조
    ++++++++++
    AC 코드 (times = 4ms / 1000 ms):
    //  DP
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #define LL long long
    #define mem(x,y) memset(x,y,sizeof(x))
    const int maxn = 1e2 + 7;
    const int mod = 1e9 + 7;
    using namespace std;
    namespace needs {
    	template<typename T> inline void read(T& ans)
    	{
    		T x = 0, f = 1; char c = getchar();
    		while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
    		while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + c - '0', c = getchar();
    		ans = f * x;
    	}
    	inline void read(string& st1)
    	{
    		char ch = getchar(); st1 = "";
    		while (!((ch >= 'a') && (ch <= 'z'))) ch = getchar();
    		while ((ch >= 'a') && (ch <= 'z')) st1 += ch, ch = getchar();
    	}
    }using namespace needs;
    int date[maxn],dp[maxn][maxn];
    //i,j;   i     j          
    int main()
    {
    	int n, k; read(n), read(k);
    	mem(dp, 0);
    	for (int i = 1; i <= n; ++i)
    		read(date[i]), dp[i][i] = 1;//   
    	for (int len = 2; len <= n; ++len)//       
    		for (int i = 1; i <= n - len + 1; ++i)//        
    		{
    			int j = i + len - 1;//             
    			dp[i][j] = dp[i + 1][j] + 1;
    			//  date[i]!=date[j],        ,  dp[i+1][j]+1,
    			//             ,  
    			if (k >= len) //           
    				for (int k = i + 1; k <= j; ++k)//    
    				{if (date[i] == date[k]) 
    					dp[i][j] = min(dp[i][j], dp[i + 1][k] + dp[k + 1][j]);}//    
    					//   date[i] == date[k],       ,
    					//   date[i+1][k]        date[i][k],
    					//   date[i][k]   ,      datep[i+1][k]   ,
    					//                     
    			else//    ,    ,    ,    
    				for (int k = i + 1; k <= j; ++k)
    					dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
    		}
    	printf("%d
    "
    , dp[1][n]);// return 0; }

    NC 19973 ([HAOI] 완구 작명)
    제목 설명: 누 군 가 는 장난감 한 벌 을 가지 고 장난감 의 이름 을 붙 이려 고 한다. 먼저 그 는 WING 네 글자 중 하 나 를 장난감 의 기본 이름 으로 선택한다. 그리고 그 는 자신의 취향 에 따라 이름 중 하 나 를 'WING' 로 사용한다.중 임의의 두 글자 로 대체 하여 자신의 이름 을 매우 길 게 늘 릴 수 있 게 한다. 지금 그 는 어떤 긴 이름 이 처음에는 어떤 자모 로 변 형 됐 는 지 맞 춰 보고 싶 어 한다.
    입력 설명: 첫 줄 의 네 개의 정수 W, I, N, G 입 니 다. 각 자 모 는 몇 개의 두 글자 로 대체 할 수 있 음 을 의미 합 니 다. 다음 줄 의 W 줄 은 두 글자 로 W 를 대체 할 수 있 음 을 의미 합 니 다. 다음 줄 의 I 줄 은 두 글자 로 대체 할 수 있 음 을 의미 합 니 다. 다음 줄 의 N 줄 은 두 글자 로 N 을 대체 할 수 있 음 을 의미 합 니 다. 다음 G 줄 은 두 글자 로 대체 할 수 있 음 을 의미 합 니 다.줄, 줄 마다 두 글자, G 는 이 두 글자 로 대체 할 수 있 음 을 표시 합 니 다. 마지막 줄 의 길 이 는 Len 을 초과 하지 않 는 문자열 입 니 다. 이 장난감 의 이름 을 표시 합 니 다.
    출력 설명: 한 줄 의 문자열 입 니 다. 이 이름 은 어떤 자모 로 변 형 될 수 있 습 니까? (WING 순서대로 출력) 주어진 이름 이 어떤 자모 로 변 형 될 수 없 으 면 "The name is wrong!" 을 출력 합 니 다.
    샘플:
    1 1 1 1
    II
    WW
    WW
    IG
    IIII
    
      :
    IN
    

    ++++++++++
    DP 상태 분석: 저자: 회귀 의 꿈 링크: 사고의 출처 dp [i] [j] [k] 는 구간 [i, j] 이 k 에서 전 환 된 것 인지, is [i] [j] [k] 는 i 가 구간 [l, r] 을 j, k 두 글자 로 대체 할 수 있 는 지, 중간 점 은 k, z, z1, z2 는 1 ~ 4 로 WING (z, z1, z2 는 코드 에 따라 더 잘 이해 할 수 있 음) 를 나타 낸다. 만약 왼쪽 구간 [l, k + 1] 이 z1 에서 전 환 될 수 있다 면(즉, 코드 dp [l] [k] [z1]), 오른쪽 구간 [k + 1, r] 은 z2 로 전환 할 수 있 습 니 다 (코드 dp [k + 1] [r] [z2]), z 는 z1 과 z2 로 대체 할 수 있 습 니 다 (코드 (원작 자 오류, 변경) is [z] [z1] [z2]). 그것 은 구간 [l, r] 이 z 로 전환 되 었 음 을 설명 하 는 것 입 니까? (z1, z2 는 중간 역 으로 구간 [l, r] 과 z 를 연결 하 는 데 사 용 됩 니 다) tips: 코드 를 결합 하여 이해 합 니 다.
    구체 적 인 내용 은 코드 참조
    ++++++++++
    AC 코드 (time = 74ms / 1000 ms)
    //  DP
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #define LL long long
    #define mem(x,y) memset(x,y,sizeof(x))
    const int maxn = 2e2 + 7;
    const int mod = 1e9 + 7;
    using namespace std;
    namespace needs {
        template<typename T> inline void read(T& ans)
        {
            T x = 0, f = 1; char c = getchar();
            while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
            while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + c - '0', c = getchar();
            ans = f * x;
        }
        inline void read(string& st1)
        {
            char ch = getchar(); st1 = "";
            while (!((ch >= 'a') && (ch <= 'z')) && !((ch >= 'A') && (ch <= 'Z'))) ch = getchar();
            while (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) st1 += ch, ch = getchar();
        }
    }using namespace needs;
    bool dp[maxn][maxn][5], is[5][5][5];
    //dp[i][j][k]    [i , j]    k      ,is[i][j][k]  i      j , k       
    int n[5];
    int getnum(char date)//        
    {
        if (date == 'W') return 1;
        if (date == 'I') return 2;
        if (date == 'N') return 3;
        if (date == 'G') return 4;
    }
    int main()
    {
        mem(dp, false), mem(is, false);//   
        for (int i = 1; i <= 4; ++i) read(n[i]);//    
        for (int i = 1; i <= 4; ++i)
            for (int j = 1; j <= n[i]; ++j)
            {
                char x, y;
                x = getchar(), y = getchar(), getchar();
                is[i][getnum(x)][getnum(y)] = true;
                //     ,    ,  i        x,y    ,   true
            }
        string date; read(date);//    
        int length = (int)date.size();
        date = ' ' + date;//    -1   ,  
        for (int i = 1; i <= length; ++i) dp[i][i][getnum(date[i])] = true;
        //           ,  :W->W
        for (int len = 2; len <= length; ++len)//      
            for (int i = 1; i <= length - len + 1; ++i)//       
                for (int k = i, j = i + len - 1; k < j; ++k)//       j,      
                    for (int z = 1; z <= 4; ++z)//      
                        for (int z1 = 1; z1 <= 4; ++z1)//       
                            for (int z2 = 1; z2 <= 4; ++z2)//       
                                if (is[z][z1][z2] && dp[i][k][z1] && dp[k + 1][j][z2])
                                //  z   z1 z2    ,              z1  ,
                                //      z2  ,    ,        z    
                                    dp[i][j][z] = true;
        bool tag = false;
        if (dp[1][length][1]) printf("W"), tag = true;
        if (dp[1][length][2]) printf("I"), tag = true;
        if (dp[1][length][3]) printf("N"), tag = true;
        if (dp[1][length][4]) printf("G"), tag = true;
        if (!tag) printf("The name is wrong!");
        //    
        return 0;
    }
    

    NC 20238 ([SCOI 2003] 문자열 접 기)
    제목 설명: 접 는 정 의 는 다음 과 같 습 니 다. 하나의 문자열 은 그 자체 의 접 는 것 으로 볼 수 있 습 니 다. S = S X (S) 는 X (X > 1) 개의 S 가 연 결 된 문자열 의 접 는 것 입 니 다. X (S) = SSSS... S (X 개 S) 로 기록 합 니 다. A = A ', B = B' 라면 AB = A 'B' 예 를 들 어 3 (A) = AAA, 2 (B) = BB 이기 때문에 3 (A) C2 (B) = AAACBB, 2 (A) C) 2 (B)= AAAAAAACBB 는 가장 짧 은 접 기 를 원 하 는 문자열 을 줍 니 다. 예 를 들 어 AAAAAAAAAABABABCCD 의 가장 짧 은 접 기 는 9 (A) 3 (AB) CCD 입 니 다.
    입력 설명: 한 줄, 즉 문자열 S 입 니 다. 길 이 는 100 을 초과 하지 않 습 니 다.
    출력 설명: 한 줄, 즉 가장 짧 은 접 기 길이 입 니 다.
    샘플:
      :
    NEERCYESYESYESNEERCYESYESYES
    
    14
    

    ++++++++++
    DP 상태 분석: dp [l] [r] 는 l ~ r 의 최 단 접 는 길이 dp [l] [r] = min (r - l + 1, dp [l] [k] + dp [k + 1] [r]) (l < = k 당 구간 [k + 1, r] 이 구간 [l, k] 에서 중복 할 수 있 을 때 dp [l] [r] = min (dp [l] [r], dp [l] [l] [l] [l] [l] [k] + 2 + getnum (r - l + 1) / (k - l + 1) -) 2 는 괄호 가 차지 하 는 자릿수 를 말 하 며 getnum 은 계수 가 차지 하 는 자릿수 를 말한다.
    구체 적 분석 코드 참조
    ++++++++++
    AC 코드 (times = 3ms / 1000 ms)
    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #define LL long long
    #define mem(x,y) memset(x,y,sizeof(x))
    const int maxn = 1e2 + 7;
    const int mod = 1e9 + 7;
    using namespace std;
    namespace needs {
        template<typename T> inline void read(T& ans)
        {
            T x = 0, f = 1; char c = getchar();
            while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
            while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + c - '0', c = getchar();
            ans = f * x;
        }
        inline void read(string& st1)
        {
            char ch = getchar(); st1 = "";
            while (!((ch >= 'a') && (ch <= 'z')) && !((ch >= 'A') && (ch <= 'Z'))) ch = getchar();
            while (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) st1 += ch, ch = getchar();
        }
    }using namespace needs;
    int dp[maxn][maxn];
    string date;
    inline bool check(int l, int k, int r)//          
    //     ,                      
    {
        if ((r - l + 1) % (k  - l + 1)) return false;
        for (int i = k + 1; i <= r; ++i,++l)
            if (date[l] != date[i]) return false;
        return true;
    }
    inline int getnum(int x)//        
    {
        if (x < 10) return 1;
        if (x < 100) return 2;
        return 3;
    }
    int main()
    {
        mem(dp, 0x6f);//   ,      100
        read(date);
        int len = (int)date.size();
        date = ' ' + date;//    -1,     
        for (int i = 1; i <= len; ++i) dp[i][i] = 1;//       
        for (int length = 2; length <= len; ++length)//      
            for (int i = 1; i <= len - length + 1; ++i)//       
            {
                int j = i + length - 1;//    ,          
                for (int k = i; k < j; ++k)//      
                {
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
                    //       (  dp    ,min       )
                    if (check(i, k, j)) dp[i][j] = min(dp[i][j], dp[i][k] + 2 + getnum(length / ((k - i) + 1)));
                    //      ,      +    +      
                }
            }
        printf("%d
    "
    , dp[1][len]);// return 0; }

    요약: 문 첫머리 에 따라 문제 유형 을 판단 하여 구간 DP 로 확인 한 후 DP 방정식 을 무조건 에서 조건 부 제약 으로 전달 하여 상태 전이 방정식 을 도 출하 고 템 플 릿 을 채 우 면 된다.
    * 첫 접촉 구간 DP, 오류 가 있 으 면 큰 사람 이 지적 해 주세요.

    좋은 웹페이지 즐겨찾기