动态规划一(上课)

1.状态怎样定义
2.怎样进行状态的转移
核心思想:讲一个问题拆成若干子问题,通过求解子问题,来推断原问题的解。
性质:
1.无后效性,”未来与过去无关“,如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。
2.最优子结构,大问题的最优解可以由小问题的最优解推出。
解题条件:能将大问题拆成几个小问题,且满足无后效性,最优子结构性质。
【例1】:
假设你有足够的1、5、11元面值的钞票。现在你的目标是凑出某个金额w,求需要用到尽量少的钞票数。
解:

#include<cstdio>
#include<iostream>
using namespace std;
int n;
int f[1005];
int main(){
    
    
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
    
    
        int cost=0x7f7f7f;
        if(i-1>=0) cost=min(cost,f[i-1]);
        if(i-5>=0) cost=min(cost,f[i-5]);
        if(i-11>=0) cost=min(cost,f[i-11]);
        f[i]=cost+1;
    }
    printf("%d",f[n]);
    return 0;
}

【思考1】:
如果要求出方案呢?
解:
用pre[i]记录f[i]是由哪一个转移过来的,然后再找f[i-pre[i]]是由哪一个转移过来的。

#include<cstdio>
#include<iostream>
using namespace std;
int n;
int f[1005],pre[1005];
int main(){
    
    
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
    
    
        int cost=0x7f7f7f;
        if(i-1>=0) cost=min(cost,f[i-1]),pre[i]=1;
        if(i-5>=0) cost=min(cost,f[i-5]),pre[i]=5;
        if(i-11>=0) cost=min(cost,f[i-11]),pre[i]=11;
        f[i]=cost+1;
    }
    printf("%d\n",f[n]);
    while(n){
    
    
        printf("%d\n",pre[n]);
        n-=pre[n];
    }
    return 0;
}

01背包问题:
有一个容量为m的背包和n个物品,物品i的体积为wi,价值为vi,
要求选一些物品放入背包中,在总体积不超过限制的前提下总价值最大。
法一:设f[i][j]:前i件物品放入容量为j的背包里在满足限制的前提下的最大价值。
空间复杂度O(nm)
f[i][j]–>f[n][m]
考虑第i个物品选与不选。
f[i][j]=max(f[i-1][j-w[i]]+v[i],f[i-1][j])

#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
int w[1005],v[1005],f[1005][1005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(Int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++){
    
    
        if(j-w[i]>=0) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
        else f[i][j]=f[i-1][j];
    }
    printf("%d",f[n][m]);
    return 0;
}

法二:滚动数组优化
设f[0/1][j],只在f[0][]和f[1][]之间转移。
f[t][j]=max(f[t1][j],f[t1][j-w[i]]+v[i])

#include<cstdio>
#include<iostream>
using namespace std;
int n,m,t;
int f[2][1005];
int w[1005],v[1005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++){
    
    
        t=i&1;
        if(j>=w[i]) f[t][j]=max(f[t^1][j],f[t^1][j-w[i]]+v[i]);
        else f[t][j]=f[t^1][j];
    }
    printf("%d",f[t][m]);
    return 0;
}

法三:一维数组优化:
j层枚举需要倒序枚举,这样才能保证用上一个阶段的状态更新当前阶段的状态,负责w[i]、v[i]可能会用很多次。

#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
int w[1005],v[1005],f[1005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--){
    
    
        if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+v[i]);
    }
    printf("%d",f[m]);
    return 0;
}

完全背包问题:
有一个容量为m的背包和n种物品,每种物品有无限个,物品i的体积为wi,价值为vi
要求选一些物品装入背包中,在总体积不超过限制的前提下总价值最大。
法一:一维做法
根据01背包中的一维做法可知,完全背包j层枚举只需要正序枚举即可,自动处理了每种物品有无限个。

#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
int w[1005],v[1005],f[1005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++){
    
    
        if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+v[i]);
    }
    printf("%d",f[m]);
    return 0;
}

法二:二维做法

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int w[1005],v[1005],f[1005][1005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++)
    for(int k=0;k*w[i]<=j;k++){
    
    
        f[i][j]=max(f[i-1][j],f[i-1][j-k*w[i]]+k*v[i]);
    }
    printf("%d",f[n][m]);
    return 0;
}

多重背包问题:
有一个容量为m的背包和n种物品,物品i的体积为wi,价值为vi,数量为si
要求选一些物品装入背包中,在总体积不超过限制的前提下总价值最大。

直接想法:
将其全拆成01背包来做,其时间复杂度为O(mΣsi)

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int w[1005],v[1005],f[1005][1005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++)
    for(int k=0;k*w[i]<=j&&k<=s[i];k++){
    
    
        f[i][j]=max(f[i-1][j],f[i-1][j-k*w[i]]+k*v[i]);
    }
    printf("%d",f[n][m]);
    return 0;
}

优化:二进制拆分:
将每种物品拆分成若干个物品,每个物品有一个系数,这个物品的费用和价值均为原物品费用和价值的系数倍。使这些系数分别为1,2,4,…,2 (k-1), si-2k+1,k是满足si-2^k+1>0的最大整数,例如si=11,将其拆分为1,2,4,4四个物品。
这些物品的系数可以凑出[0,si]中的任意一个整数,这样我们就可以把n个物品的多重背包问题转化为∑logsi个物品的01背包问题,复杂度O(m∑logsi)
【例题】洛谷 P1776 宝物筛选

#include<cstdio>
#include<iostream>
using namespace std;
int v[1000005],w[1000005],f[1000005];
int n,ww,cnt;
int main(){
    
    
    scanf("%d%d",&n,&ww);
    for(int i=1;i<=n;i++){
    
    
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        for(int j=1;j<=c;j*=2){
    
    
            v[++cnt]=a*j;
            w[cnt]=b*j;
            c-=j;
        }
        if(c!=0) v[++cnt]=c*a, w[cnt]=c*b;
    }
    for(int i=1;i<=cnt;i++)
    for(int j=ww;j>=w[i];j--)
    f[j]=max(f[j],f[j-w[i]]+v[i]);
    printf("%d",f[ww]);
    return 0;
}

单调队列优化:

#include<cstdio>
#include<iostream>
using namespace std;
int n,ww,v,w,num,ans;
int f[1000005],q[1000005],pp[1000006];
int main(){
    
    
	scanf("%d%d",&n,&ww);
	for(int i=1;i<=n;i++){
    
    
		scanf("%d%d%d",&v,&w,&num);
		if(w==0) ans+=v*num;
		num=min(ww/w,num);
		for(int j=0;j<w;j++){
    
    
			int head=0,tail=0;
			int k=(ww-j)/w;
			for(int p=0;p<=k;p++){
    
    
				while(head<tail&&f[j+p*w]-p*v>=q[tail-1]) tail--;
				q[tail]=f[j+p*w]-p*v; pp[tail++]=p;
				while(head<tail&&pp[head]<p-num) head++;
				f[j+p*w]=max(f[j+p*w],q[head]+p*v);
			}
		}
	}
	printf("%d",f[ww]+ans);
	return 0;
}

可行性背包:
有一个容量为m的背包和n个物品,物品i的体积为wi,问能凑出哪些体积
01背包简化,只是一个0/1的问题
转移方程:f[j]|=f[j-w[i]]
f[1~m]的每一位都是0/1。

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int w[10050];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=1;i<=n;i++)
    for(int j=w[i];j>=0;j--) f[j]|=f[j-w[i]];
    return 0;
}

【例题】洛谷 P1064 金明的预算方案(有依赖性背包问题)
对于每一个主件(A)及其附件(B,C),一共有四种选择方案:
1.A
2.A.B
3.A.C
4.A.B.C
所以,可将其拆分成以上的物品,然后做01背包。
时间复杂度为O(nm),如果最多有k个附件,复杂度为O((2^k)nm)

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int w[100005][5],v[100005][5],f[1000005];
int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
    
    
        int vv,pp,qq;
        scanf("%d%d%d",&vv,&pp,&qq);
        if(qq==0){
    
    
            w[i][0]=vv;
            v[i][0]=pp*vv;
        }
        else if(w[qq][1]==0){
    
    
            w[qq][1]=vv;
            v[qq][1]=pp*vv;
        }
        else{
    
    
            w[qq][2]=vv;
            v[qq][2]=pp*vv;
        }
    }
    for(int i=1;i<=m;i++)
    for(int j=n;j>=w[i][0];j--){
    
    
        if(j>=w[i][0]) f[j]=max(f[j],f[j-w[i][0]]+v[i][0]);
        if(j>=w[i][0]+w[i][1]){
    
    
            int t1=w[i][0]+w[i][1];
            int t2=v[i][0]+v[i][1];
            f[j]=max(f[j],f[j-t1]+t2);
        }
        if(j>=w[i][0]+w[i][2]){
    
    
            int t1=w[i][0]+w[i][2];
            int t2=v[i][0]+v[i][2];
            f[j]=max(f[j],f[j-t1]+t2);
        }
        if(j>=w[i][0]+w[i][1]+w[i][2]){
    
    
            int t1=w[i][0]+w[i][2]+w[i][1];
            int t2=v[i][0]+v[i][2]+v[i][1];
            f[j]=max(f[j],f[j-t1]+t2);
        }
    }
    printf("%d",f[n]);
    return 0;
}

【例题】飞扬的小鸟
(洛谷 P1941)
0/1背包+完全背包。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=10005;
int n,m,k;
int x[N],y[N],flag[N],low[N],high[N];
int f[N][N/5];
int main(){
    
    
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]);
	for(int i=1;i<=n;i++) low[i]=0,high[i]=m+1;
	for(int i=1;i<=k;i++){
    
    
		int p;
		scanf("%d",&p);
		flag[p]=1;
		scanf("%d%d",&low[p],&high[p]);
	}
	memset(f,0x3f3f3f,sizeof(f));
	for(int i=1;i<=m;i++) f[0][i]=0;
	for(int i=1;i<=n;i++){
    
    
		for(int j=x[i]+1;j<=x[i]+m;j++) f[i][j]=min(f[i-1][j-x[i]]+1,f[i][j-x[i]]+1);
		for(int j=m+1;j<=x[i]+m;j++) f[i][m]=min(f[i][m],f[i][j]);
		for(int j=1;j<=m-y[i];j++) f[i][j]=min(f[i][j],f[i-1][j+y[i]]);
		for(int j=1;j<=low[i];j++) f[i][j]=0x3f3f3f;
		for(int j=high[i];j<=m;j++) f[i][j]=0x3f3f3f;
	}
	int ans=0x3f3f3f;
	for(int i=1;i<=m;i++) ans=min(ans,f[n][i]);
	if(ans<0x3f3f3f){
    
    
		printf("1\n%d\n",ans);
		return 0;
	}
	int i,j;
	for(i=n;i>=1;i--){
    
    
		for(j=1;j<=m;j++){
    
    
			if(f[i][j]<0x3f3f3f) {
    
    
				break;	
			}
		}
		if(j<=m) break;
	}
	ans=0;
	for(j=1;j<=i;j++) if(flag[j]) ans++;
	printf("0\n%d\n",ans);
	return 0;
}

线性DP:
动态规划中比较简单的一种情况,一般在一个一维的序列上进行。
一、最大子段和:(连续)
一个长度为n的序列,第i个数为ai,要求选择连续且非空的一段,使该段数字之和最大。
f[i]表示以i为结尾的最大子段和,转移有两种情况:
1.和前面的最大子段和连起来
2.自己作为单独一段的开头
f[i]=a[i]+max(f[i-1], 0),答案是max(f[i]),时间复杂度为O(n)。

#include<cstdio>
#include<iostream>
using namespace std;
int f[10005],a[10005];
int n,maxn;
int main(){
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
    
    
		f[i]=a[i]+max(f[i-1],0);
		maxn=max(f[i],maxn); 
	}
	printf("%d",maxn);
	return 0;
}

二、最长上升子序列(LIS)(不连续)
一个长度为n的数列,第i个数为ai,求最长上升子序列的长度。
上升子序列即存在p1<p2<…<pm,满足ap1<ap2<…<apm
1.O(n^2)做法:
f[i]表示以i为结尾的最长上升子序列的长度
f[i]=max(f[j])+1,j<i且aj<ai
时间复杂度O(n^2)

#include<cstdio>
#include<iostream>
using namespace std;
int n,maxx;
int a[10005],f[10006];
int main(){
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++) f[i]=1;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<i;j++){
    
    
		if(a[j]<a[i]){
    
    
			f[i]=max(f[i],f[j]+1);
			maxx=max(maxx,f[i]);
		}
	}
	printf("%d",maxx);
	return 0;
}

2.O(nlogn)做法:
令g[i]表示长度为i的LIS中,结尾最小的数
例如,a={6,7,1,5,4,3,4,2,8}
g[1]=6
g[1]=6, g[2]=7
g[1]=1, g[2]=7
g[1]=1, g[2]=5
g[1]=1, g[2]=4
g[1]=1, g[2]=3
g[1]=1, g[2]=3, g[3]=4
g[1]=1, g[2]=2, g[3]=4,
g[1]=1, g[2]=2, g[3]=4, g[4]=8
容易看出,每次g数组只会修改一个值或新加入一个值,且g数字保持单调递增。显然,当LIS长度相等时,结尾的数越小越好,这样才更可能与后面的数连接成更长的LIS。每次考虑以i为结尾的上升子序列时,只要在g数组中找到最大的小于a[i]的位置j,令g[j+1]=a[i]即可。由于g单调,可以二分来找位置j,复杂度O(nlogn),最后g数组的长度即为LIS的长度。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int g[10005],a[10005];
int n,len;
int main(){
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	memset(g,0x7f7f7f,sizeof(g));
	g[1]=a[1]; len=1;
	for(int i=1;i<=n;i++){
    
    
		if(a[i]>g[len]) g[++len]=a[i];
		else{
    
    
			int pos=lower_bound(g+1,g+len+1,a[i])-g;
			g[pos]=a[i];
		}
	}
	printf("%d",len);
	return 0;
}

三、最长公共子序列(LCS)(不连续)
有两个序列a、b,长度分别为n、m,求最长公共子序列LCS
LCS:从两个序列中选出长度相等的子序列,这两个序列相等
O(n^2):
f[i][j]表示序列a的前i个数、序列b的前j个数,LCS的长度
若a[i]=b[j],则f[i][j]=f[i-1][j-1]+1
否则f[i][j]=max(f[i-1][j], f[i][j-1])
时间复杂度O(n^2)

#include<cstdio>
#include<iostream>
using namespace std;
int f[1005][1005];
int a[1005],b[1005];
int n,m;
int main(){
    
    
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int j=1;j<=m;j++) scanf("%d",&b[j]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
    
    
		if(a[i]==b[j]) f[i][j]=max(f[i-1][j-1]+1,f[i][j]);
		f[i][j]=max(f[i][j],max(f[i-1][j],f[i][j-1]));
	}
	printf("%d",f[n][m]);
	return 0;
}

【例题】导弹拦截(洛谷 P1020):

【例题】线段覆盖:
数轴上有n个线段,线段的两端都是整数坐标,每条线段有一个价值,从n条线段中挑若干条线段,使得这些线段两两不覆盖(端点可以重合),且线段价值之和最大。n<=1000, 坐标范围[0,1000000]

设第i条线段的左右端点为li、ri,价值为vi
将线段按右端点升序排序
f[i]表示以第i条线段为末尾,所选线段价值最大
f[i]=max(f[j])+v[i], rj<=li
O(n^2)

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1005;
struct node{
    
    
	int l,r,v;
}edge[N];
int f[N];
bool cmp(node a,node b){
    
    
	return a.r<b.r;
}
int n;
int main(){
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
    
    
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		edge[i].l=a; edge[i].r=b; edge[i].v=c;
	}
	sort(edge+1,edge+n+1,cmp);
	for(int i=1;i<=n;i++){
    
    
		f[i]=max(f[i-1],edge[i].v);
		for(int j=i-1;j>=1;j--){
    
    
			if(edge[j].r<=edge[i].l) f[i]=max(f[i],f[j]+edge[i].v);
		}
	}
	printf("%d",f[n]);
	return 0; 
}

树状数组优化?
logn的时间复杂度从前几个中选取最大值

其他优化?
rjj为坐标+离散化

【例题】Flowers
土拨鼠爱吃花,每次他可能吃1朵红花或k朵白花,若他最后吃花的总数在a到b之间,求总共有几种吃法,多次询问。

f[i]表示吃i朵花的方案数
f[i]=f[i-1]+f[i-k](类似上楼梯问题)
对f做一个前缀和即可

#include<cstdio>
#include<iostream>
using namespace std;
int f[1005],s[1005],k,a,b,t;
int main(){
    
    
	scanf("%d",&k);
	f[1]=1; f[0]=1;
	for(int i=2;i<=1000;i++){
    
    
		if(i>=k) f[i]=f[i-1]+f[i-k];
		else f[i]=f[i-1];
		s[i]=s[i-1]+f[i];
	}
	scanf("%d",&t);
	while(t--){
    
    
		scanf("%d%d",&a,&b);
		printf("%d",s[b]-s[a-1]);
	}
	return 0;
}

区间DP:
区间DP是对一个区间进行动态规划,一般来说,每段区间的最优值都是由两段或更多段小区间的最优值得到,是分治思想的一种应用。
一般定义状态f[i][j]表示区间[i,j]的最优值,转移时枚举中间点k,用f[i][k]和f[k+1][j]来合并。

【例题】:合并石子:
有n堆石子排成一列,每堆石子重量wi
每次可以合并相邻两堆石子,合并代价为两堆石子重量和,求合并顺序使代价最小
n<=100, w<=100
f[i][j]=min(f[i][k]+f[k+1][j]+sum(i,j))
O(n^3)
1.外层枚举区间长度,内层枚举i。
2.若不枚举区间长度,则可以改变枚举区间端点的方法
3.记忆化搜索

#include<cstdio>
#include<iostream>
using namespace std;
//test 3
int dfs(int i,int j){
    
    
	if(f[i][j]!=-1) return f[i][j];
	if(i==j){
    
    
		f[i][j]=a[i];
		return;
	}
	for(int k=i;k<j;k++)
	f[i][j]=min(f[i][j],dfs(i,k)+dfs(k+1,j)+sum[j]-sum[i-1]);
	return f[i][j];
}
int main(){
    
    
	//test 1
	for(int l=1;l<=n;l++)
	for(int i=1;i<=n-l+1;i++){
    
    
		int j=i+l-1;
		for(int k=i;k<j;k++)
		f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
	}
	//test 2 
	for(int i=n;i>=1;i--)
	for(int j=i;j<=n;j++)
	for(int k=i;k<j;k++)
	f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
}

【例题】合并石子(环形)
有n堆石子排成环形,每堆石子重量wi
每次可以合并相邻两堆石子,合并代价为两堆石子重量和,求合并顺序使代价最小
n<=100, w<=100
把原排列复制一遍,扩展到2n-1个石子
f[i][j]=min(f[i][k]+f[k+1][j]+sum(i,j))
答案是min(f[i][i+n-1]),1<=i<=n

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,a[205],sum[205];
int maxx,minn=0x7f7f7f;
int g[205][205],f[205][205];
int main(){
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
    
    
		scanf("%d",&a[i]);
		a[i+n]=a[i];
	}
	for(int i=1;i<=n*2;i++) sum[i]=sum[i-1]+a[i];
	for(int l=1;l<=n;l++)
	for(int i=1;i+l<=2*n;i++){
    
    
		int j=i+l;
		g[i][j]=0x3f3f3f3f;
		for(int k=i;k<j;k++){
    
    
			g[i][j]=min(g[i][j],g[i][k]+g[k+1][j]+sum[j]-sum[i-1]);
			f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
		}
	}
	for(int i=1;i<=n;i++){
    
    
		int j=i+n-1;
		maxx=max(maxx,f[i][j]);
		minn=min(minn,g[i][j]);
	}
	printf("%d\n%d",minn,maxx);
	return 0;
}

【例题】:玩具取名
(洛谷 P4290)
类似合并果子,但不仅要考虑能否合并,而且要考虑能合并成什么。
设f[i][j][0/1/2/3]分别表示[i,j]区间能否合并成W/I/N/G。
枚举断点k,f[i][j][0/1/2/3]=(f[i][k][0/1/2/3]&f[k+1][j][0/1/2/3]&g[0/1/2/3][0/1/2/3][0/1/2/3])
0/1/2/3需要枚举。

#include<cstdio>
#include<iostream>
using namespace std;
const int N=305;
int f[N][N][4];
int g[N][N][4];
int num[10];
int t1,t2,len;
char a,b;
bool flag;
string s;
int get(char x){
    
    
	if(x=='W') return 1;
	if(x=='I') return 2;
	if(x=='N') return 3;
	if(x=='G') return 4;
}
int main(){
    
    
	for(int i=1;i<=4;i++) scanf("%d",&num[i]);
	for(int i=1;i<=4;i++)
	for(int j=1;j<=num[i];j++){
    
    
		cin>>a>>b;
		t1=get(a); t2=get(b);
		g[t1][t2][i]=1;
	}
	cin>>s;
	len=s.length();
	for(int i=0;i<len;i++) f[i][i][get(s[i])]=1;
	for(int i=len-1;i>=0;i--)
	for(int j=i;j<len;j++)
	for(int k=i;k<j;k++)
	for(int t=1;t<=4;t++)
	for(int t1=1;t1<=4;t1++)
	for(int t2=1;t2<=4;t2++){
    
    
		if(g[t1][t2][t]&&f[i][k][t1]&&f[k+1][j][t2]) f[i][j][t]=1;
	}
	for(int i=1;i<=4;i++){
    
    
		if(f[0][len-1][i]){
    
    
			flag=1;
			if(i==1) printf("W");
			if(i==2) printf("I");
			if(i==3) printf("N");
			if(i==4) printf("G");
		}
	}
	if(!flag) printf("The name is wrong!");
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/qq_33151931/article/details/113762792
今日推荐