codeforces的dp专题

1.(467C)http://codeforces.com/problemset/problem/467/C

题意:有一个长为n的序列,选取k个长度为m的子序列(子序列中不能有位置重复),求所取的k个序列求和的最大值是多少

分析:设dp[i][j]在[j,n]范围内取了i个子序列求和所得的最大值,用sum[i]表示[1,i]的求和。转移方程为dp[i][j]=max(dp[i-1][j+m]+sum[j+m-1]-sum[j-1],dp[i][j+1]),表示要不要选择[j,j+m-1]这段为其中一个子序列

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm> 
 4 using namespace std;
 5 typedef long long ll;
 6 const ll maxn=5050;
 7 ll dp[maxn][maxn],a[maxn],sum[maxn];
 8 
 9 int main()
10 {
11     ll n,m,k,i,j,x,y,z,ans;
12     while ( scanf("%lld%lld%lld",&n,&m,&k)!=EOF ) {
13         for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]);
14         sum[0]=0;
15         for ( i=1;i<=n;i++ ) sum[i]=sum[i-1]+a[i];
16         memset(dp,0,sizeof(dp));
17         ans=0;
18         for ( i=1;i<=k;i++ ) {
19             dp[i][n-i*m+1]=sum[n]-sum[n-i*m];
20             for ( j=n-i*m;j>0;j-- ) {
21                 dp[i][j]=max(dp[i-1][j+m]+sum[j+m-1]-sum[j-1],dp[i][j+1]);
22             }
23         }
24         for ( i=1;i<=n-k*m+1;i++ ) ans=max(ans,dp[k][i]);
25         printf("%lld\n",ans);
26     }
27     return 0;
28 }
467C

 

2.(706C)http://codeforces.com/problemset/problem/706/C

题意:有n个字符串,每个字符串反转需要a[i]的费用,先求花费最少使得对于任意第i个字符串的字典序都大于等于第i-1个字符串,若不存在输出-1

分析:设dp[i][j],j=0表示第i个字符串不反转,j=1表示第i个字符串反转,dp[i][j]表示前i个字符串都已经按顺序排好的最小花费(初始化除第一项外都为inf)。

转移方程有4条:

if ( s[i]>=s[i-1] ) dp[i][0]=min(dp[i][0],dp[i-1][0]);  
if ( s[i]>=s_[i-1] ) dp[i][0]=min(dp[i][0],dp[i-1][1]);
if ( s_[i]>=s[i-1] ) dp[i][1]=min(dp[i][1],dp[i-1][0]+a[i]);
if ( s_[i]>=s_[i-1] ) dp[i][1]=min(dp[i][1],dp[i-1][1]+a[i]);

注意:审题!操作是整体反转,字典序的顺序是大于等于(不要遗漏等于)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<string>
 5 #include<iostream>
 6 using namespace std;
 7 typedef long long ll;
 8 const ll maxn=1e5+10;
 9 const ll inf=1e15;
10 ll dp[maxn][2],a[maxn];
11 string s[maxn],s_[maxn];
12 
13 int main()
14 {
15     ll n,m,i,j,k,x,y,z,ans;
16     while ( scanf("%lld",&n)!=EOF ) {
17         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
18         for ( i=1;i<=n;i++ ) {
19             cin>>s[i];
20             s_[i]=s[i];
21             reverse(s_[i].begin(),s_[i].end());
22         }
23         //for ( i=1;i<=n;i++ ) cout<<s[i]<<" "<<s_[i]<<endl;
24         for ( i=1;i<=n;i++ ) dp[i][0]=dp[i][1]=inf;
25         dp[1][0]=0;
26         dp[1][1]=a[1];
27         for ( i=2;i<=n;i++ ) {
28             if ( s[i]>=s[i-1] ) dp[i][0]=min(dp[i][0],dp[i-1][0]);
29             if ( s[i]>=s_[i-1] ) dp[i][0]=min(dp[i][0],dp[i-1][1]);
30             if ( s_[i]>=s[i-1] ) dp[i][1]=min(dp[i][1],dp[i-1][0]+a[i]);
31             if ( s_[i]>=s_[i-1] ) dp[i][1]=min(dp[i][1],dp[i-1][1]+a[i]);
32         }
33         ans=min(dp[n][0],dp[n][1]);
34         if ( ans==inf ) printf("-1\n");
35         else printf("%lld\n",ans);
36     }
37     return 0;
38 }
706C

 

3.(611C)http://codeforces.com/problemset/problem/611/C

题意:有一个n*m的地图,地图上有“.”和“#”,当相邻的两块都为“.”时可以放置一个多米诺骨牌,现给定一个矩形区域,问在这个矩形区域内有多少种放置骨牌的方法

分析:因为有横着放和竖着放两种方式,所以我们将这两种方式分开来看。用row[i][j]表示对于第i行前j个方格多米诺骨牌横放有多少种可能。col[j][i]表示对于第j列前i个方格多米诺骨牌竖着放有多少可能。

最后对于左上角为(x1,y1)和右下角为(x2,y2)大小的矩阵来说,该方格中的方案数为:

for ( i=x1;i<=x2;i++ ) ans+=(row[i][y2]-row[i][y1]);
for ( i=y1;i<=y2;i++ ) ans+=(col[i][x2]-col[i][x1]);

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const ll maxn=510;
 7 char s[maxn][maxn];
 8 ll mp[maxn][maxn],row[maxn][maxn],col[maxn][maxn];
 9 
10 int main()
11 {
12     ll n,m,i,j,k,x,y,z,x1,x2,y1,y2,ans,q;
13     while ( scanf("%lld%lld",&n,&m)!=EOF ) {
14         for ( i=1;i<=n;i++ ) scanf("%s",s[i]+1);
15         memset(mp,0,sizeof(mp));
16         memset(row,0,sizeof(row));
17         memset(col,0,sizeof(col));
18         for ( i=1;i<=n;i++ ) {
19             for ( j=1;j<=m;j++ ) {
20                 if ( s[i][j]=='.' ) mp[i][j]=1;
21             }
22         }
23         for ( i=1;i<=n;i++ ) {
24             for ( j=1;j<=m;j++ ) {
25                 row[i][j]=row[i][j-1];
26                 if ( mp[i][j] && mp[i][j-1] ) row[i][j]++;
27             }
28         }
29         for ( j=1;j<=m;j++ ) {
30             for ( i=1;i<=n;i++ ) {
31                 col[j][i]=col[j][i-1];
32                 if ( mp[i][j] && mp[i-1][j] ) col[j][i]++;
33             }
34         }
35         scanf("%lld",&q);
36         while ( q-- ) {
37             scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
38             ans=0;
39             for ( i=x1;i<=x2;i++ ) ans+=(row[i][y2]-row[i][y1]);
40             for ( i=y1;i<=y2;i++ ) ans+=(col[i][x2]-col[i][x1]);
41             printf("%lld\n",ans);
42         }
43     } 
44     return 0;
45 }
611C

 

4.(835C)http://codeforces.com/problemset/problem/835/C

题意:有一个最大范围为100*100的方格,规定星星最大的亮度为m,现有n个星星,每颗星星坐标为(x,y),初始亮度为s。现有q个询问,每个询问有t秒(时间)和一个矩形大小,矩形的左下角为 (x1,y1) 右上角为(x2,y2)。现求在这个矩形区域内的所有星星的亮度之和。对于每颗星星每过1s,亮度+1,当亮度为m+1时亮度变为0。

分析:对于一个矩形面积中的总亮度为这个矩形中的星星数量*他们各自的亮度求和。因为亮度的范围最大为10,所以我们考虑将不同的初始亮度的星星存入不同的数组中,每个亮度的用一个二维数组维护数量。对于每次访问的矩形范围,每个亮度都扫一次再相加

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const ll maxn=105;
 7 const ll maxk=12;
 8 const ll N=100;
 9 ll num[maxk][maxn][maxn];
10 
11 int main()
12 {
13     ll n,q,m,i,j,k,x,y,z,ans,cnt,s,t,x1,x2,y1,y2,now;
14     while ( scanf("%lld%lld%lld",&n,&q,&m)!=EOF ) {
15         memset(num,0,sizeof(num));
16         for ( i=1;i<=n;i++ ) {
17             scanf("%lld%lld%lld",&x,&y,&s);
18             num[s][x][y]++;
19         }
20         for ( i=0;i<=m;i++ ) {
21             for ( j=0;j<=N;j++ ) {
22                 for ( k=1;k<=N;k++ ) num[i][j][k]+=num[i][j][k-1];
23             }
24         }
25         for ( i=0;i<=m;i++ ) {
26             for ( j=0;j<=N;j++ ) {
27                 for ( k=1;k<=N;k++ ) num[i][j][k]+=num[i][j-1][k];
28             }
29         }
30         while ( q-- ) {
31             scanf("%lld%lld%lld%lld%lld",&t,&x1,&y1,&x2,&y2);
32             ans=0;
33             for ( i=0;i<=m;i++ ) {
34                 x=num[i][x2][y2]-num[i][x1-1][y2]-num[i][x2][y1-1]+num[i][x1-1][y1-1];
35                 now=((i+t)%(m+1))*x;
36                 ans+=now;
37             }
38             printf("%lld\n",ans);
39         }
40     }
41     return 0;
42 }
835C

 

5.(711C)http://codeforces.com/problemset/problem/711/C

题意:有n颗树,初始时每棵树可能没有颜色(c[i]==0),可能已经有颜色了(c[i]!=0),先要从m([1,m])种颜色中给那些未上色的树上色,若要给第i颗树上第j个颜色,则需要花费cost[i][j]的颜料。现在要使从左到右的n颗树分成k组(一组的定义是连续的树具有相同的颜色算作一组),现要求恰好分成k组需要的颜料的最小值

分析:设dp[i][j][k]表示前i颗树已经分成k组,第i颗树的颜色是j的情况花费的最小费用。将数组初始化为inf。

当a[i]确定时 dp[i][a[i]][k]=max(dp[i][a[i]][k],dp[i-1][j][k-1])   (j!=a[i])

dp[i][a[i]][k]=max(dp[i][a[i]][k],dp[i-1][j][k])   (j==a[i])

当a[i]不确定时 dp[i][j][k]=max(dp[i][j][k],dp[i-1][h][k-1])+cost[i][j]  (h!=j)

dp[i][j][k]=max(dp[i][j][k],dp[i-1][h][k])+cost[i][j]  (h==j)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const ll maxn=102;
 7 const ll inf=1e15;
 8 ll a[maxn],dp[maxn][maxn][maxn],cost[maxn][maxn];
 9 
10 int main()
11 {
12     ll n,m,p,i,j,k,h,x,y,z,ans;
13     while ( scanf("%lld%lld%lld",&n,&m,&p)!=EOF ) {
14         for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]);
15         for ( i=1;i<=n;i++ ) {
16             for ( j=1;j<=m;j++ ) scanf("%lld",&cost[i][j]);
17         }
18         for ( i=1;i<=n;i++ ) {
19             for ( j=1;j<=m;j++ ) {
20                 for ( k=1;k<=p;k++ ) dp[i][j][k]=inf;
21             }
22         }
23         if ( a[1]==0 ) {
24             for ( j=1;j<=m;j++ ) dp[1][j][1]=cost[1][j];
25         }
26         else dp[1][a[1]][1]=0;
27         for ( i=2;i<=n;i++ ) {
28             if ( a[i]!=0 ) {
29                 for ( k=1;k<=m;k++ ) {
30                     for ( h=1;h<=p;h++ ) {
31                         if ( dp[i-1][k][h]!=-1 ) {
32                             if ( a[i]==k ) dp[i][a[i]][h]=min(dp[i][a[i]][h],dp[i-1][k][h]);
33                             else dp[i][a[i]][h+1]=min(dp[i][a[i]][h+1],dp[i-1][k][h]); 
34                         }
35                     }
36                 }
37             }
38             else {
39                 for ( j=1;j<=m;j++ ) {
40                     for ( k=1;k<=m;k++ ) {
41                         for ( h=1;h<=p;h++ ) {
42                             if ( dp[i-1][k][h]!=-1 ) {
43                                 if ( j==k ) dp[i][j][h]=min(dp[i][j][h],dp[i-1][k][h]+cost[i][j]);
44                                 else dp[i][j][h+1]=min(dp[i][j][h+1],dp[i-1][k][h]+cost[i][j]);
45                             }
46                         }
47                     }
48                 }
49             }
50         }
51         ans=inf;
52         for ( j=1;j<=m;j++ ) {
53             ans=min(dp[n][j][p],ans);
54         }
55         if ( ans!=inf ) printf("%lld\n",ans);
56         else printf("-1\n");
57     }
58     return 0;
59 }
711C

 

6.(567C)http://codeforces.com/problemset/problem/567/C

题意:给你一个序列,求序列中长度为3的公比为m的子序列的个数。

分析:设置一个num数组记录某个数已经出现的个数,设置cnt数组,cnt[i]表示当i出现时,ans+=cnt[i](即cnt[i]表示i作为等比数列的第三项时,当前有多少个子序列满足条件)。特别注意当m==1时需要特别判断。还有当序列中出现0时需要记录并且单独考虑

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<map>
 5 #include<set>
 6 using namespace std;
 7 typedef long long ll;
 8 const ll maxn=2e5+10;
 9 map<ll,ll>num,cnt;
10 
11 int main()
12 {
13     ll n,m,i,j,k,x,y,z,ans;
14     while ( scanf("%lld%lld",&n,&m)!=EOF ) {
15         num.clear();
16         cnt.clear();
17         if ( m==1 ) {
18             ans=0;
19             set<int>s;
20             for ( i=1;i<=n;i++ ) {
21                 scanf("%lld",&x);
22                 num[x]++;
23                 s.insert(x);
24             }
25             set<int>::iterator it;
26             for ( it=s.begin();it!=s.end();it++ ) {
27                 x=*it;
28                 y=num[x];
29                 ans+=y*(y-1)*(y-2)/6;
30             }
31             printf("%lld\n",ans);
32             continue;
33         }
34         ans=0;
35         z=0;
36         for ( i=1;i<=n;i++ ) {
37             scanf("%lld",&x);
38             num[x]++;
39             if ( x%m==0 && x!=0 ) {
40                 cnt[x*m]+=num[x/m];
41             }
42             else if ( x==0 ) z++;
43             ans+=cnt[x];
44         }
45         ans+=z*(z-1)*(z-2)/6;
46         printf("%lld\n",ans);
47     }
48     return 0;
49 }
567C

 

7.(339C)http://codeforces.com/problemset/problem/339/C

题意:有不同质量的砝码无限多个,现在只让你在天平两边放入m个,要求

1.每次在天平放一个,一次左边,下一次右边

2.相邻两次质量必须不同

3.放入砝码后,保证放入的这边总重严格大于另外一边。

分析:DFS,将往天平的左右加砝码,转变成往一个地方加质量和减质量。DFS时记录sum,last,id,分别表示当前求值之和,上一个砝码是哪一个,这是第几次放砝码(决定是加质量还是减质量)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1005;
 6 const int maxm=12;
 7 int path[maxn],d[maxm];
 8 char s[maxm];
 9 int cnt,m;
10 
11 bool f(int last,int sum,int id)
12 {
13     if ( id==m+1 ) return true;
14     bool ok;
15     if ( id%2==1 ) ok=true;
16     else ok=false;
17     int x,y,z;
18     for ( int i=1;i<=cnt;i++ ) {
19         if ( d[i]==last ) continue;
20         if ( ok ) {
21             x=sum+d[i];
22             if ( x>0 ) {
23                 if ( f(d[i],x,id+1) ) {
24                     path[id]=d[i];
25                     return true;
26                 }
27             }
28         }
29         else {
30             x=sum-d[i];
31             if ( x<0 ) {
32                 if ( f(d[i],x,id+1) ) {
33                     path[id]=d[i];
34                     return true;
35                 }
36             }
37         }
38     }
39     return false;
40 }
41 
42 int main()
43 {
44     int i,j,k,x,y,z;
45     bool flag;
46     while ( scanf("%s",s+1)!=EOF ) {
47         scanf("%d",&m);
48         cnt=0;
49         for ( i=1;i<=10;i++ ) {
50             if ( s[i]=='1' ) d[++cnt]=i;
51         }
52         flag=f(0,0,1);
53         if ( flag ) {
54             printf("YES\n");
55             for ( i=1;i<=m;i++ ) {
56                 printf("%d",path[i]);
57                 if ( i!=m ) printf(" ");
58                 else printf("\n");
59             }
60         }
61         else printf("NO\n");
62     }
63     return 0;
64 }
339C

 

8.(713C)http://codeforces.com/problemset/problem/713/C

题意:给定一个序列,可以进行若干次操作,每次操作选择一个数让它+1或-1,问最少要几次操作使得序列变为严格单调递增序列

分析:有两个性质:1.若为非严格递增序列,一定可以做到在最少的操作次数下使得改变后的序列中的元素是原序列的元素

2.若改为严格递增序列,则将每个数-i,再进行以上操作,就能保证改变后的是非严格递增,最后每个数+i则能保证严格递增

设dp[i][j]表示将原序列前i个数变成按递增顺序并且第i个数最大为j(离散后的j)的序列需要的最小花费,

有dp[i][j]=min(dp[i][j-1]+abs(a[i]-c[j]))  i==1

             =min(dp[i][j-1],dp[i-1][j]+abs(a[i]-c[j]))  i!=1

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cmath>
 5 using namespace std;
 6 typedef long long ll;
 7 const ll maxn=3010;
 8 const ll inf=1e15;
 9 ll a[maxn],b[maxn],dp[maxn][maxn],c[maxn];
10 
11 bool cmp1(int x,int y)
12 {
13     return x<y;
14 }
15 
16 bool cmp2(int x,int y)
17 {
18     return x>y;
19 }
20 
21 int main()
22 {
23     ll n,m,i,j,k,x,y,z,cnt,ans;
24     while ( scanf("%lld",&n)!=EOF ) {
25         for ( i=1;i<=n;i++ ) {
26             scanf("%lld",&a[i]);
27             a[i]-=i;
28             b[i]=a[i];
29         }
30         sort(b+1,b+1+n);
31         cnt=0;
32         for ( i=1;i<=n;i++ ) {
33             if ( i==1 ) c[++cnt]=b[i];
34             else {
35                 if ( b[i]==b[i-1] ) continue;
36                 else c[++cnt]=b[i];
37             }
38         }
39         for ( i=0;i<=n;i++ ) {
40             for ( j=0;j<=cnt;j++ ) dp[i][j]=inf;
41         }
42         for ( j=1;j<=cnt;j++ ) {
43             if ( a[1]>=c[j] ) x=a[1]-c[j];
44             else x=c[j]-a[1];
45             dp[1][j]=min(dp[1][j-1],x);
46         }
47         for ( i=2;i<=n;i++ ) {
48             for ( j=1;j<=cnt;j++ ) {
49                 if ( a[i]>=c[j] ) x=a[i]-c[j];
50                 else x=c[j]-a[i];
51                 dp[i][j]=min(dp[i][j-1],dp[i-1][j]+x);
52             }
53         }
54         ans=dp[n][cnt];
55         printf("%lld\n",ans);
56     }
57     return 0;
58 }
713C

 

9.(4D)http://codeforces.com/problemset/problem/4/D

题意:有n个信封,有一个信纸大小为w*h。现在让你从n个信封中取出一些信封,使其满足一些条件(后一个信封的大小(长/宽)都要大于前一个信封的大小(长/宽);并且都需要比信纸的大小要大)。先求最多能取出多少信封。

分析:类似于求最长上升子序列。先将所有信封排序(长为第一关键字,宽为第二关键字进行排序),然后对于第i个信封来说,访问它前面所有的信封看是否满足条件,用类似于最长上升子序列的方式更新该信封的“最长长度”并且需要记录上一个信封的编号。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=5050;
 6 struct node{
 7     int w,h,id,pre,len;
 8     bool ok;
 9 }arr[maxn];
10 int d[maxn];
11 
12 bool cmp(node a,node b)
13 {
14     if ( a.w==b.w ) return a.h<b.h;
15     return a.w<b.w;
16 }
17 
18 bool cmp1(node a,node b)
19 {
20     return a.id<b.id;
21 }
22 
23 int main()
24 {
25     int n,w,h,i,j,k,m,ans,x,y,z,cnt,num,len;
26     while ( scanf("%d%d%d",&n,&w,&h)!=EOF ) {
27         for ( i=1;i<=n;i++ ) {
28             scanf("%d%d",&arr[i].w,&arr[i].h);
29             arr[i].id=i;
30             arr[i].pre=-1;
31             arr[i].len=1;
32             if ( arr[i].w>w && arr[i].h>h ) arr[i].ok=true;
33             else arr[i].ok=false;
34         }
35         sort(arr+1,arr+1+n,cmp);
36         ans=0;
37         cnt=-1;
38         for ( i=1;i<=n;i++ ) {
39             if ( !arr[i].ok ) continue;
40             for ( j=1;j<i;j++ ) {
41                 if ( !arr[j].ok ) continue;
42                 if ( arr[i].w>arr[j].w && arr[i].h>arr[j].h ) {
43                     len=arr[j].len+1;
44                     if ( len>arr[i].len ) {
45                         arr[i].len=len;
46                         arr[i].pre=j;
47                     }
48                 }
49             }
50             if ( arr[i].len>ans ) {
51                 ans=arr[i].len;
52                 cnt=i;
53             }
54         }
55         if ( cnt==-1 ) printf("0\n");
56         else {
57             m=0;
58             while ( cnt!=-1 ) {
59                 d[++m]=arr[cnt].id;;
60                 cnt=arr[cnt].pre;
61             }
62             printf("%d\n",m);
63             for ( i=m;i>0;i-- ) {
64                 printf("%d",d[i]);
65                 if ( i!=1 ) printf(" ");
66                 else printf("\n");
67             }
68         }
69     }
70     return 0;
71 }
4D

 

10.(761C)http://codeforces.com/problemset/problem/761/C

题意:有 n 个字符串,每个字符串的长度是m,如果认定一个字符串是一个密码,则必须满足:

1:至少有1个数字。2:至少有一个小写字母。3:至少有一个 #、*或&

现在有n个光标,每个光标指向这 n 个字符串。现在可以移动光标,最后使得所有光标指向的字符能组成一个密码。(字符串是循环的)

分析:费用流,设置超级源点s和超级汇点t,对于每个字符串设置一个点,对于要求的三类符号各设置一个点,计算每个字符串分别获得这三类符号所需要的最小费用,然后s到各个字符串建容量为1费用为0的边。对于每个字符串到三类字符的点,若费用不为Inf(初始值),建容量为1,费用为每个字符串得到该类字符的最少花费的费用。最后将三类字符串的点和t建费用为0,容量为1的边。跑一遍最小费用流

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #include<queue>
  5 #include<vector>
  6 using namespace std;
  7 const int maxn=55;
  8 const int inf=1e9;
  9 struct edge{
 10     int from,to,flow,cost;
 11     edge(int u,int v,int f,int c):from(u),to(v),flow(f),cost(c) {}
 12 };
 13 int n,s,t;
 14 long long cost;
 15 vector<edge>E;
 16 vector<int>G[maxn];
 17 bool vis[maxn];
 18 int d[maxn],p[maxn],a[maxn]; 
 19 char S[maxn][maxn];
 20 int dp[maxn][3];
 21 
 22 void init()
 23 {
 24     for ( int i=0;i<=n;i++ ) G[i].clear();
 25     E.clear();
 26 }
 27 
 28 void addedge(int u,int v,int f,int c)
 29 {
 30     E.push_back(edge(u,v,f,c));
 31     E.push_back(edge(v,u,0,-c));
 32     int m=E.size();
 33     G[u].push_back(m-2);
 34     G[v].push_back(m-1);
 35 }
 36 
 37 bool bellmanford(int& flow)
 38 {
 39     for ( int i=0;i<=n;i++ ) d[i]=inf;
 40     memset(vis,false,sizeof(vis));
 41     d[s]=0;
 42     vis[s]=true;
 43     p[s]=0;
 44     a[s]=inf;
 45     queue<int>que;
 46     que.push(s);
 47     while ( !que.empty() ) {
 48         int u=que.front();
 49         que.pop();
 50         vis[u]=false;
 51         for ( int i=0;i<G[u].size();i++ ) {
 52             edge& e=E[G[u][i]];
 53             if ( e.flow>0 && d[e.to]>d[u]+e.cost ) {
 54                 d[e.to]=d[u]+e.cost;
 55                 p[e.to]=G[u][i];
 56                 a[e.to]=min(a[u],e.flow);
 57                 if ( !vis[e.to] ) {
 58                     que.push(e.to);
 59                     vis[e.to]=true;
 60                 }
 61             }
 62         }
 63     }
 64     if ( d[t]==inf ) return false;
 65     flow+=a[t];
 66     cost+=(long long)d[t]*(long long)a[t];
 67     for ( int u=t;u!=s;u=E[p[u]].from ) {
 68         E[p[u]].flow-=a[t];
 69         E[p[u]^1].flow+=a[t];
 70     }
 71     return true;
 72 }
 73 
 74 int mincost() 
 75 {
 76     int flow=0;
 77     cost=0;
 78     while ( bellmanford(flow));
 79     return flow;
 80 }
 81 
 82 int main()
 83 {
 84     int N,M,i,j,k,ans,x;
 85     char c;
 86     while ( scanf("%d%d",&N,&M)!=EOF ) {
 87         s=0;
 88         n=N+4;
 89         t=n;
 90         init();
 91         for ( i=1;i<=N;i++ ) scanf("%s",S[i]+1);
 92         for ( i=1;i<=N;i++ ) {
 93             for ( j=0;j<3;j++ ) dp[i][j]=inf;
 94         }
 95         for ( i=1;i<=N;i++ ) {
 96             for ( j=1;j<=M;j++ ) {
 97                 c=S[i][j];
 98                 x=min(j-1,M+1-j);
 99                 if ( c>='0' && c<='9' ) dp[i][0]=min(dp[i][0],x);
100                 else if ( c>='a' && c<='z' ) dp[i][1]=min(dp[i][1],x);
101                 else dp[i][2]=min(dp[i][2],x);
102             }
103         }
104         for ( i=1;i<=N;i++ ) addedge(s,i,1,0);
105         for ( i=1;i<=N;i++ ) {
106             for ( j=0;j<3;j++ ) {
107                 if ( dp[i][j]!=inf ) addedge(i,j+N+1,1,dp[i][j]);
108             }
109         }
110         for ( i=0;i<3;i++ ) addedge(i+1+N,t,1,0);
111         mincost();
112         printf("%lld\n",cost);
113     }
114     return 0;
115 }
761C

 

11.(269B)http://codeforces.com/problemset/problem/269/B

题意:给出一个实数轴,上面散布着n个点,总共有m种,每个点属于一种,问最少挪多少个点能将数轴上的点按各自的种类从小到大排序。

分析:从反面出发,考虑最多有多少个点可以保持不动,即求最长非严格递增子序列。设dp[i]表示从[1,i]包含第i个点的最长非严格递增子序列的最长长度。假设第i个点的种类为s[i],dp[i]=max(dp[j])+1(s[i]>=s[j] && i>j),同时设置ans记录最长的非严格递增子序列的长度,最后的答案为n-ans

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=5010;
 6 struct node{
 7     int s;
 8     double x;
 9 }arr[maxn];
10 int dp[maxn];
11 
12 int main()
13 {
14     int n,m,i,j,k,ans;
15     while ( scanf("%d%d",&n,&m)!=EOF )
16     {
17         for ( i=1;i<=n;i++ ) scanf("%d%lf",&arr[i].s,&arr[i].x);
18         memset(dp,0,sizeof(dp));
19         ans=0;
20         for ( i=1;i<=n;i++ )
21         {
22             for ( j=1;j<i;j++ ) 
23             {
24                 if ( arr[i].s>=arr[j].s ) dp[i]=max(dp[i],dp[j]);
25             }
26             dp[i]++;
27             ans=max(ans,dp[i]);
28         }
29         printf("%d\n",n-ans);
30     }
31     return 0;    
32 } 
269B

 

12.(977F)http://codeforces.com/problemset/problem/977/F

题意:求最长递增子串,要求串中的元素大小是连续的

分析:因为a[i]在[1,1e9]的范围内,所以采用map<>dp,dp[i]表示以i为结尾的最长长度。转移方程为dp[a[i]]=max(dp[a[i]],dp[a[i-1]+1]);

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<map>
 5 using namespace std;
 6 const int maxn=2e5+10;
 7 map<int,int>dp;
 8 int a[maxn];
 9 
10 int main()
11 {
12     int n,i,j,k,x,y,z,m,p,ans,t;
13     while ( scanf("%d",&n)!=EOF )
14     {
15         dp.clear();
16         ans=0;
17         k=0;
18         for ( i=1;i<=n;i++ )
19         {
20             scanf("%d",&a[i]);
21             dp[a[i]]=max(dp[a[i]],dp[a[i]-1]+1);
22             if ( dp[a[i]]>ans )
23             {
24                 ans=dp[a[i]];
25                 k=i;
26             }
27         }
28         t=a[k]-ans+1;
29         printf("%d\n",ans);
30         for ( i=1;i<=n;i++ )
31         {
32             if ( a[i]==t ) 
33             {
34                 printf("%d",i);
35                 if ( i!=k ) printf(" ");
36                 else 
37                 {
38                     printf("\n");
39                     break;
40                 }
41                 t++;
42             }
43         }
44     }
45     return 0;
46 } 
977F

 

13.(721C)http://codeforces.com/problemset/problem/721/C

题意:给定一个有n个点m条边的有向图,给定初始费用c。初始点在1,结束点在n,每条边都有边权,每经过一条边都要花费边权的费用。先求从起到到终点最多能经过几条边

分析:DAG上的dp,设dp[i][j]表示从1到i这个点,中间经过j座城市的最少时间花费。dp[1][1]=0,其他初始化为Inf(尽可能大)。采用bfs边拓步排序边状态转移,每次将入度为0的点放入栈中。对于边(u,v),边权为cost的边来说,转移方程为dp[v][j]=min(dp[v][j],dp[u][j-1]+cost) (j为[2,n]),当一个点的入度减为0时再将其放入栈中。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 #include<queue>
 6 using namespace std;
 7 typedef long long ll;
 8 const int maxn=5050;
 9 const int inf=0x3f3f3f3f;
10 struct edge{
11     int v,cost;
12     edge(int _v=0,int _cost=0):v(_v),cost(_cost) {}
13 };
14 vector<edge>G[maxn];
15 int dp[maxn][maxn];
16 int pre[maxn][maxn],ind[maxn],n;
17 bool vis[maxn];
18 vector<int>pos;
19 
20 void bfs()
21 {
22     queue<int>que;
23     dp[1][1]=0;
24     for ( int i=1;i<=n;i++ )
25     {
26         if ( !ind[i] ) 
27         {
28             vis[i]=true;
29             que.push(i);
30         }
31     }
32     while ( !que.empty() )
33     {
34         int u=que.front();
35         que.pop();
36         for ( int i=0;i<G[u].size();i++ )
37         {
38             int v=G[u][i].v;
39             int cost=G[u][i].cost;
40             if ( ind[v] )
41             {
42                 ind[v]--;
43                 for ( int j=2;j<=n;j++ )
44                 {
45                     if ( dp[v][j]>dp[u][j-1]+cost )
46                     {
47                         dp[v][j]=dp[u][j-1]+cost;
48                         pre[v][j]=u;
49                     }
50                 }
51                 if ( !ind[v] ) que.push(v);
52             }
53         }
54     }
55 }
56 
57 int main()
58 {
59     int m,c,i,j,k,x,y,z,ans,now,p;
60     while ( scanf("%d%d%d",&n,&m,&c)!=EOF )
61     {
62         for ( i=1;i<=n;i++ ) G[i].clear();
63         for ( i=1;i<=n;i++ )
64             for ( j=1;j<=n;j++ ) dp[i][j]=inf;
65         memset(ind,0,sizeof(ind));
66         memset(vis,false,sizeof(vis));
67         for ( i=1;i<=m;i++ ) 
68         {
69             scanf("%d%d%d",&x,&y,&z);
70             G[x].push_back(edge(y,z));
71             ind[y]++;
72         }
73         bfs();
74         ans=0;
75         for ( i=1;i<=n;i++ )
76         {
77             if ( dp[n][i]<=c ) ans=max(i,ans);
78         }
79         printf("%d\n",ans);
80         pos.clear();
81         now=n;
82         p=ans;
83         while ( p )
84         {
85             pos.push_back(now);
86             now=pre[now][p--];
87         }
88         for ( i=pos.size()-1;i>=0;i-- )
89         {
90             printf("%d",pos[i]);
91             if ( i!=0 ) printf(" ");
92             else printf("\n");
93         }
94     }
95     return 0;
96 }
721C

 

14.(463D)http://codeforces.com/problemset/problem/463/D

题意:给出m个排列,每个排列长度均为n,求最长公共子串

分析:求最长路,具体做法是:以第一个排列为基准,设置bool型的数组line[maxn][maxn],初始化为true,当line[i][[j]=true表示对于所有排列来说i和j的相对位置都是一样的,都与第一个排列相同,这样可以建一条i到j的边(i<j),边长为1。设置起点s=0,终点t=n+1,s到所有点建边长为1的边,所有点到t建边长为0的边。对建好的图跑一次最长路,取反即为最后的答案。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 #include<queue>
 6 using namespace std;
 7 const int maxn=1010;
 8 const int maxm=6;
 9 const int inf=1e9;
10 bool line[maxn][maxn];
11 int a[maxm][maxn],pos[maxn];
12 struct edge{
13     int v,cost;
14     edge(int _v=0,int _cost=0):v(_v),cost(_cost) {}
15 };
16 vector<edge>G[maxn];
17 bool vis[maxn];
18 int dist[maxn];
19 
20 void addedge(int u,int v,int w)
21 {
22     G[u].push_back(edge(v,-w));
23 }
24 
25 void SPFA(int start,int n)
26 {
27     memset(vis,false,sizeof(vis));
28     for ( int i=0;i<=n;i++ ) dist[i]=inf;
29     vis[start]=true;
30     dist[start]=0;
31     queue<int>que;
32     que.push(start);
33     while ( !que.empty() )
34     {
35         int u=que.front();
36         que.pop();
37         vis[u]=false;
38         for ( int i=0;i<G[u].size();i++ )
39         {
40             int v=G[u][i].v;
41             int cost=G[u][i].cost;
42             if ( dist[v]>dist[u]+cost )
43             {
44                 dist[v]=dist[u]+cost;
45                 if ( !vis[v] )
46                 {
47                     vis[v]=true;
48                     que.push(v);
49                 }
50             }
51         }
52     }
53 }
54 
55 int main()
56 {
57     int n,m,i,j,k,x,y,z,u,v,t,s;
58     while ( scanf("%d%d",&n,&m)!=EOF )
59     {
60         memset(line,true,sizeof(line));
61         for ( i=1;i<=m;i++ )
62             for ( j=1;j<=n;j++ ) scanf("%d",&a[i][j]);
63         for ( i=1;i<=n;i++ ) pos[a[1][i]]=i;
64         for ( k=2;k<=m;k++ )
65             for ( i=1;i<=n;i++ )
66                 for ( j=1;j<i;j++ )
67                 {
68                     u=pos[a[k][i]];
69                     v=pos[a[k][j]];
70                     if ( v>u ) line[u][v]=line[v][u]=false;
71                 }
72         s=0;
73         t=n+1;
74         for ( i=0;i<=t;i++ ) G[i].clear();
75         for ( i=1;i<=n;i++ ) 
76         {
77             for ( j=i+1;j<=n;j++ )
78             {
79                 if ( line[i][j] ) addedge(i,j,1);
80             }
81             addedge(s,i,1);
82             addedge(i,t,0);
83         }
84         SPFA(s,t);
85         printf("%d\n",-dist[t]);
86     }
87     return 0;
88 }
463D

 

15.(873B)http://codeforces.com/problemset/problem/873/B

题意:给出一段长度为n的01串,定义一段序列平衡,要求该序列中0和个数和1的个数相等,求最长的平衡序列长度有多长

分析:“0”转化为-1,“1”转化为1,记录前缀和。用map记录前缀和每个值第一次出现的位置(特别地mp[0]=0),然后对于位置i有前缀和sum[i]=x,从[1,i]最长的平衡串长度为i-mp[x],即x第一次出现的位置到i这段的长度。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<map>
 5 using namespace std;
 6 const int maxn=1e5+10;
 7 char s[maxn];
 8 int sum[maxn],a[maxn];
 9 map<int,int>mp;
10 
11 int main()
12 {
13     int n,m,i,j,k,x,y,z,ans,now;
14     while ( scanf("%d",&n)!=EOF )
15     {
16         mp.clear();
17         scanf("%s",s+1);
18         sum[0]=0;
19         for ( i=1;i<=n;i++ ) 
20         {
21             if ( s[i]=='0' ) a[i]=-1;
22             else a[i]=1;
23             sum[i]=a[i]+sum[i-1];
24             if ( mp[sum[i]]==0 ) mp[sum[i]]=i;
25         }
26         mp[0]=0;
27         ans=0;
28         for ( i=1;i<=n;i++ )
29         {
30             x=sum[i];
31             y=mp[x];
32             now=i-y;
33             ans=max(ans,now);
34         }
35         printf("%d\n",ans);
36     }    
37     return 0;
38 } 
873B

 

16.(479E)http://codeforces.com/problemset/problem/479/E

题意:有一栋大楼,总共有n层,一开始你处于第a层,第b层你是无法到达的,总共有k次坐电梯的机会每次坐电梯你可以从第x层到第y层,但是需要满足x!=y&&|x-b|>|x-y|,求最多有几种坐电梯的方案

分析:设dp[i][j]表示第j次坐电梯到第i层的方案数,dp[i][j]=sum(dp[k][j-1])(k为能一次坐到第i层的层号)。注意当a>b时要维护前缀和,a<b时要维护后缀和

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=5010;
 7 const ll mod=1e9+7;
 8 ll dp[maxn][maxn],sum[maxn];
 9 
10 int main()
11 {
12     int n,a,b,m,i,j,k,x,y,z,l,r;
13     ll ans;
14     while ( scanf("%d%d%d%d",&n,&a,&b,&m)!=EOF )
15     {
16         memset(dp,0,sizeof(dp));
17         memset(sum,0,sizeof(sum));
18         dp[a][0]=1;
19         ans=0;
20         if ( b>a )
21         {
22             for ( i=1;i<b;i++ ) sum[i]=sum[i-1]+dp[i][0];
23             for ( k=1;k<=m;k++ )
24             {
25                 for ( i=1;i<b;i++ )
26                 {
27                     r=(b+i-1)/2;
28                     dp[i][k]=(sum[r]-dp[i][k-1])%mod;
29                 }
30                 for ( i=1;i<b;i++ ) sum[i]=sum[i-1]+dp[i][k];
31             }
32             for ( i=1;i<b;i++ ) ans=(ans+dp[i][m])%mod;
33         }
34         else 
35         {
36             for ( i=n;i>b;i-- ) sum[i]=sum[i+1]+dp[i][0];
37             for ( k=1;k<=m;k++ )
38             {
39                 for ( i=n;i>b;i-- )
40                 {
41                     l=(b+i)/2+1;
42                     dp[i][k]=(sum[l]-dp[i][k-1])%mod;
43                 }
44                 for ( i=n;i>b;i-- ) sum[i]=sum[i+1]+dp[i][k];
45             }
46             for ( i=b+1;i<=n;i++ ) ans=(ans+dp[i][m])%mod;
47         }
48         printf("%lld\n",ans);
49     }
50     return 0;
51 }
479E

 

17.http://codeforces.com/problemset/problem/687/C

题意:给出n个数,从中取一些数使得他们的和为m。将取出的数看作一个集合,求这个集合的子集元素之和有哪些可能

分析:d[i][j]表示取出集合中的和为i,该集合的子集可能的和为j,最后的答案为d[m][i]==true(0<=i<=m)的i值.

中间的状态转移过程类似于01背包,一个数只能用一次。当值为a[k]时,可以由d[i][j]转移到d[i+a[k]][j]和d[i+a[k]][j+a[k]]。同时注意,三层循环的最外层是每些数的值,第二层要从大到小(保证只能取一个)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1010;
 6 int a[maxn],ans[maxn];
 7 bool d[maxn][maxn],vis[maxn];
 8 
 9 int main()
10 {
11     int n,m,i,j,k,x,y,z,cnt;
12     while ( scanf("%d%d",&n,&m)!=EOF )
13     {
14         memset(d,false,sizeof(d));
15         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
16         d[0][0]=true;
17         for ( k=1;k<=n;k++ )
18         {
19             for ( i=m;i>=0;i-- )
20             {
21                 for ( j=0;j<=i;j++ )
22                 {
23                     if ( !d[i][j] ) continue;
24                     d[i+a[k]][j]=true;
25                     d[i+a[k]][j+a[k]]=true;
26                 }
27             }    
28         }
29         cnt=0;
30         for ( i=0;i<=m;i++ ) 
31         {
32             if ( d[m][i] ) ans[++cnt]=i;
33         }
34         printf("%d\n",cnt);
35         for ( i=1;i<=cnt;i++ )
36         {
37             printf("%d",ans[i]);
38             if ( i!=cnt ) printf(" ");
39             else printf("\n");
40         }
41     }
42     return 0;
43 }
687C

 

18.(219D)http://codeforces.com/problemset/problem/219/D

题意:图中有n个城市,n-1条单向边,选一个城市为首都,使得所需要反转的边最少,输出反转边的条数和边的编号

分析:首先对本来已经存在的边建变成为0的边,反向边建变成为1的边。设置dp[2][maxn],dp[0][i]表示以i为根到所有叶子节点的总路径之和。dp[1][i]表示i到i上半部分所有点的总路径之和。

跑两次dfs,第一次可以求出dp[0][i]。第二次跑dfs,若存在u到v的边长为w的边则有dp[1][v]=dp[0][u]-dp[0][v]-w+dp[1][u]+(w^1) (最后的括号不能落)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 using namespace std;
 6 const int maxn=2e5+10;
 7 struct edge{
 8     int v,w;
 9     edge(int _v=0,int _w=0):v(_v),w(_w) {}
10 };
11 int dp[2][maxn],ans[maxn],cnt[maxn];
12 vector<edge>G[maxn];
13 
14 void dfs0(int u,int fa)
15 {
16     for ( int i=0;i<G[u].size();i++ )
17     {
18         int v=G[u][i].v;
19         int w=G[u][i].w;
20         if ( v==fa ) continue;
21         dfs0(v,u);
22         dp[0][u]+=(w+dp[0][v]);
23     }
24 }
25 
26 void dfs1(int u,int fa)
27 {
28     for ( int i=0;i<G[u].size();i++ )
29     {
30         int v=G[u][i].v;
31         int w=G[u][i].w;
32         if ( v==fa ) continue;
33         dp[1][v]=dp[0][u]-dp[0][v]-w+dp[1][u]+(w^1);
34         dfs1(v,u);
35     }
36 }
37 
38 int main()
39 {
40     int n,i,j,k,x,y,z,u,v,w,res;
41     while ( scanf("%d",&n)!=EOF )
42     {
43         for ( i=1;i<=n;i++ ) G[i].clear();
44         for ( i=1;i<n;i++ )
45         {
46             scanf("%d%d",&u,&v);
47             G[u].push_back(edge(v,0));
48             G[v].push_back(edge(u,1));
49         }
50         memset(dp,0,sizeof(dp));
51         dfs0(1,-1);
52         dfs1(1,-1);
53         res=n;
54         for ( i=1;i<=n;i++ ) 
55         {
56             ans[i]=dp[0][i]+dp[1][i];
57             res=min(ans[i],res);
58         }
59         int num=0;
60         for ( i=1;i<=n;i++ )
61         {
62             if ( ans[i]==res ) cnt[++num]=i;
63         }
64         printf("%d\n",res);
65         for ( i=1;i<=num;i++ )
66         {
67             printf("%d",cnt[i]);
68             if ( i!=num ) printf(" ");
69             else printf("\n");
70         }
71     }
72     return 0;
73 }
219D

 

20.(710E)http://codeforces.com/problemset/problem/710/E

题意:起初没有字符,要求构造n个a,添加或者删除一个a耗费a单位的时间,成倍添加a耗费b单位的时间

分析:直接从[2,n]一个个遍历过来,i可以由i-1和i/2(若为奇数则为(i+1)/2)转移过来

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=1e7+10;
 7 ll dp[maxn];
 8 
 9 int main()
10 {
11     int n,a,b,i,j,k,x,y,z;
12     while ( scanf("%d%d%d",&n,&a,&b)!=EOF )
13     {
14         dp[0]=0;
15         dp[1]=a;
16         for ( i=2;i<=n;i++ )
17         {
18             dp[i]=dp[i-1]+a;
19             if ( i&1 ) dp[i]=min(dp[i],dp[(i+1)/2]+b+a);
20             else dp[i]=min(dp[i],dp[i/2]+b);
21         }
22         printf("%lld\n",dp[n]);
23     }
24     return 0;
25 }
710E

 

21.(255C)http://codeforces.com/problemset/problem/255/C

题意:求一个最长的序列长度,该序列满足a1=p,an=a1+(-1)^(n+1)*q

分析:设dp[i][j]表示该序列最后一项为i,倒数第二项为j,当a[j]==a[k]时dp[k][i]=dp[i][j]+1,本来是n^3的复杂度,但是可以通过一定的方法优化(优化过程见代码),优化的大致就是第一重循环代表i,设置变量k,记录于第i项相等的位置的下标,初始值为-1。第二重循环j从1到i,当k==-1时,即还没有遇到相同的值dp[i][j]=2,否则dp[i][j]=dp[j][k]+1,当遇到a[i]==a[j]时,k==j,因为对于最长的序列来说,于i相等的值距离i越近越好

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=4010;
 6 int dp[maxn][maxn];
 7 int a[maxn];
 8 
 9 int main()
10 {
11     int n,m,i,j,k,x,y,z,ans;
12     while ( scanf("%d",&n)!=EOF )
13     {
14         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
15         ans=1;
16         for ( i=1;i<=n;i++ )
17         {
18             k=-1;
19             for ( j=1;j<i;j++ )
20             {
21                 if ( k==-1 ) dp[i][j]=2;
22                 else dp[i][j]=dp[j][k]+1;
23                 if ( a[i]==a[j] ) k=j;
24                 ans=max(ans,dp[i][j]);
25             }
26         }
27         printf("%d\n",ans);
28     }
29     return 0;
30 } 
255C

 

22.(478D)http://codeforces.com/problemset/problem/478/D

题意:用n个红色块和m个绿色块,去构建一个塔,塔每层的块的数量一定比上一层(第一层只有一个)多一个,同时处于塔的同一层的色块颜色必须相同,求用给定的色块去构建这样的红绿塔,一共有多少种方案。

分析:因为每层的数量是从1每次增加一个的,所以最后的塔的最大高度是确定的(只与数量有关,与颜色无关)(不太会证明..)。所有设dp[i][j]为构建前i层时,总共需要j个绿色色块的数量的方案有多少。

for循环的第一层表示层数,第二层表示j,注意每次枚举j时都要满足红绿色块的要求,dp[i][j]=dp[i-1][j]+dp[i-1][j-i] (j>=i时有后一项),不满足条件的j下dp[i][j]=0。但是因为所给数据较大会爆空间,考虑到最多只会用到数组的两层,所以考虑弄成滚动数组,即将第一维压成两层。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=4e5+10;
 7 const int maxm=105;
 8 const ll mod=1e9+7;
 9 ll dp[2][maxn];
10 
11 int main()
12 {
13     int n,m,i,j,k,x,y,z,h,sz,p;
14     ll ans;
15     while ( scanf("%d%d",&n,&m)!=EOF )
16     {
17         for ( h=0;(1+h)*h/2<=(n+m);h++ );
18         h--;
19         memset(dp,0,sizeof(dp));    
20         dp[0][0]=1;
21         for ( i=1;i<=h;i++ )
22         {
23             if ( i%2==0 ) x=0;
24             else x=1;
25             sz=(i+1)*i/2;
26             p=min(sz,m);
27             for ( j=0;j<=p;j++ ) dp[x][j]=0;
28             for ( j=0;j<=p;j++ )
29             {
30                 int jj=sz-j;
31                 if ( jj>n ) continue;
32                 if ( j>m ) break;
33                 dp[x][j]=dp[1-x][j]%mod;
34                 if ( j>=i ) dp[x][j]=(dp[x][j]+dp[1-x][j-i])%mod; 
35             }
36         }
37         if ( h%2==0 ) x=0;
38         else x=1;
39         ans=0;
40         sz=(1+h)*h/2;
41         for ( i=0;i<=sz;i++ ) 
42         {
43             if ( sz-i>n ) continue;
44             if ( i>m ) break;
45             ans=(ans+dp[x][i])%mod;
46         }
47         printf("%lld\n",ans);
48     }
49     return 0;    
50 }
478D

 

23.(459E)http://codeforces.com/problemset/problem/459/E

参考此博客:https://blog.csdn.net/qq_24451605/article/details/48877073

题意:给出n个点,m条边的有向图,每个边有边权,求一条最长的边权上升的路径的长度。

分析:先对所有边按权值从小到大进行排序

定义dp[i]表示第i条边结尾的情况下的最长路径。定义g[i]表示点i结尾的情况的最长路径。所以dp[i] = g[e[i].u]+1。然后只需要特殊考虑一下边相等的情况,更新g[i]即可

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 using namespace std;
 6 const int maxn=3e5+10;
 7 struct Edge{
 8     int u,v,w;
 9 }edge[maxn];
10 int dp[maxn],g[maxn];
11 
12 bool cmp(Edge a,Edge b)
13 {
14     return a.w<b.w;
15 }
16 
17 int main()
18 {
19     int n,m,i,j,k,x,y,z,t,u,v,w,ans;
20     while ( scanf("%d%d",&n,&m)!=EOF )
21     {
22         memset(dp,0,sizeof(dp));
23         memset(g,0,sizeof(g));
24         t=1;
25         for ( i=1;i<=m;i++ ) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
26         sort(edge+1,edge+1+m,cmp);
27         edge[m+1].w=0;
28         for ( i=1;i<=m;i++ )
29         {
30             u=edge[i].u;
31             v=edge[i].v;
32             w=edge[i].w;
33             dp[i]=g[u]+1;
34             if ( edge[i].w!=edge[i+1].w )
35             {
36                 for ( j=t;j<=i;j++ ) g[edge[j].v]=max(g[edge[j].v],dp[j]);
37                 t=i+1;
38             }
39         }
40         ans=0;
41         for ( i=1;i<=m;i++ ) ans=max(ans,dp[i]);
42         printf("%d\n",ans);
43     }
44     return 0;
45 }
459E

 

24.(933A)http://codeforces.com/problemset/problem/933/A

题意:给定一串序列只含数字1和2,现在可以选择一段任意长度的序列将它进行反转,求能够得到的最长非递减子序列的长度是多少

分析:因为原串只含数字1和2,所以反转的部分2222211111(连续的1中可能含少许2,连续的2中可能含少许1,忽略不计,下同。类似于这样2在前1在后的部分),而整个序列最有可能的样子是1111222211112222,我们只需要将其中的22221111这部分进行反转就能得到最大的长度。所有我们定义dp[2][n] ,dp[0][i]表示[1,i]上的最长非递减子序列的长度,dp[1][i]表示[i,n]上的最长非递减子序列的长度。这样最后的答案是dp[0][k]+dp[1][k+1](1<=k<=n)中的最大值

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=2010;
 6 int dp[2][maxn];
 7 int a[maxn];
 8 
 9 int main()
10 {
11     int n,i,j,k,m,x,y,z,ans;
12     while ( scanf("%d",&n)!=EOF )
13     {
14         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
15         a[0]=a[n+1]=0;
16         memset(dp,0,sizeof(dp));
17         for ( i=1;i<=n;i++ )
18         {
19             dp[0][i]=1;
20             for ( j=1;j<i;j++ )
21             {
22                 if ( a[j]<=a[i] ) dp[0][i]=max(dp[0][i],dp[0][j]+1);
23             }
24             if ( a[i]==a[i-1] ) dp[0][i]=max(dp[0][i],dp[0][i-1]);
25         }
26         for ( i=n;i>=1;i-- )
27         {
28             dp[1][i]=1;
29             for ( j=n;j>i;j-- )
30             {
31                 if ( a[j]>=a[i] ) dp[1][i]=max(dp[1][i],dp[1][j]+1);
32             }
33             if ( a[i+1]==a[i] ) dp[1][i]=max(dp[1][i],dp[1][i+1]);
34         }
35         ans=0;
36         for ( i=1;i<=n;i++ ) 
37         {
38             if ( dp[0][i]+dp[1][i+1]>ans ) ans=dp[0][i]+dp[1][i+1];
39         }
40         printf("%d\n",ans);
41     }
42     return 0;
43 }
933A

 

25.(180C)http://codeforces.com/problemset/problem/180/C

题意:给定一串字符串,字符串只包含大写字母和小写字母,现在我们可以使某一个位置的大写字母变成小写字母,使某一个位置的小写字母变成大写字母,先求最小的操作次数,使得左边全为大写字母,右边全为小写字母。

分析:dp[0][i]记录[1,i]个中有多少的小写字母,dp[1][i]记录[i,n]中有多少个大写字母,枚举分界点,最后ans=min(dp[0][i]+dp[1][i+1])(0<=i<=n)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1e5+10;
 6 char s[maxn];
 7 int dp[2][maxn];
 8 
 9 int main()
10 {
11     int n,i,j,k,x,y,z,ans,cnt0,cnt1;
12     while ( scanf("%s",s+1)!=EOF )
13     {
14         n=strlen(s+1);
15         memset(dp,0,sizeof(dp));
16         cnt0=cnt1=0;
17         for ( i=1;i<=n;i++ )
18         {
19             if ( s[i]>='a' && s[i]<='z' ) cnt0++;
20             if ( s[n-i+1]>='A' && s[n-i+1]<='Z' ) cnt1++;
21             dp[0][i]=cnt0;
22             dp[1][n-i+1]=cnt1;
23         }
24         ans=n;
25         for ( i=0;i<=n;i++ ) ans=min(ans,dp[0][i]+dp[1][i+1]);
26         printf("%d\n",ans);
27     }
28     return 0;
29 }
180C

 

26.(407B)http://codeforces.com/problemset/problem/407/B

题意:有n+1个房间,刚开始在第1个房间,刚开始第一个房间的墙上有一个X,其他房间没有,每到一个房间都在这个房间的墙上打一个X,当墙上X的个数为奇数时,那么这个人会到第a[i]个房间,当墙上X的个数为偶数时,那么这个人会到第i+1个房间,现在求需要走几步才能到第n+1个房间

分析:dp[i]表示从第0个点第二次走到i需要消耗的时间,初始化dp[1]=2,即假设刚开始在第0个房间,第0个房间只能到第一个房间。

状态转移方程为:dp[i]=(dp[i-1]+1+dp[i-1]-dp[a[i]-1]+1+mod)%mod;  dp[i-1]+1为第一次到达需要的时间,dp[i-1]-dp[a[i]-1]+1为第二次到达所需要的时间

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=1050;
 7 const ll mod=1e9+7;
 8 int a[maxn];
 9 ll dp[maxn];
10 
11 int main()
12 {
13     int i,j,k,x,y,z,m,n,ans;
14     while ( scanf("%d",&n)!=EOF )
15     {
16         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
17         memset(dp,0,sizeof(dp));
18         dp[1]=2;
19         for ( i=2;i<=n;i++ )
20         {
21             dp[i]=(dp[i-1]+1+dp[i-1]-dp[a[i]-1]+1+mod)%mod;
22         }
23         printf("%lld\n",dp[n]);
24     }
25     return 0;
26 }
407B

 

27.(163A)http://codeforces.com/problemset/problem/163/A

题意:给定两个串s1,s2,求s1的子串(连续)和s2的子序列(不连续)相同的个数

分析:dp[i][j]表示s1从[1,i]的子串和s2从[1,j]的子序列中相同的个数。

dp[i][j]=dp[i][j-1]  (s1[i]!=s2[j])

dp[i][j]=dp[i][j-1]+dp[i-1][j-1]+1 (s1[i]==s2[j])

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=5050;
 7 const ll mod=1e9+7;
 8 ll dp[maxn][maxn];
 9 char s1[maxn],s2[maxn];
10 
11 int main()
12 {
13     int n,m,i,j,k,x,y,z;
14     ll ans;
15     while ( scanf("%s%s",s1+1,s2+1)!=EOF )
16     {
17         n=strlen(s1+1);
18         m=strlen(s2+1);
19         memset(dp,0,sizeof(dp));
20         for ( i=1;i<=n;i++ )
21         {
22             for ( j=1;j<=m;j++ )
23             {
24                 dp[i][j]=dp[i][j-1];
25                 if ( s1[i]==s2[j] ) dp[i][j]=(dp[i][j]+dp[i-1][j-1]+1)%mod;
26             }
27         }
28         ans=0;
29         for ( i=1;i<=n;i++ ) ans=(ans+dp[i][m])%mod;
30         printf("%lld\n",ans);
31     }
32     return 0;
33 }
163A

 

28.(936B)http://codeforces.com/problemset/problem/936/B

题意:给定一个有向图,问从s点出发能否走奇数条边到一个没有出度的点。

分析:搜索。设置变量dp[maxn][2],vis[maxn][2]第二维为0表示走的下一条边为奇数次,第二维为1表示走的下一条边为偶数次。dp[i]代表的是从i出发能发到达一个没有出度的点。vis[i]==1表示到过这个点,但是这个点还未处理完(当搜索过程中遇到其他vis[j]==1的点表示存在环),vis[i]==2表示这个点已经处理完。最后判断dp[s][0]是否为true即可,若不是再判断有无环

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 using namespace std;
 6 const int maxn=2e5+10;
 7 bool dp[maxn][2];
 8 int vis[maxn][2],out[maxn],nxt[maxn][2];
 9 vector<int>G[maxn];
10 bool cir,flag;    
11 
12 bool dfs(int u,int op)
13 {
14     if ( !out[u] ) 
15     {
16         dp[u][op]=true;
17         vis[u][op]=2;
18         return op==1;
19     }
20     if ( vis[u][op]==2 ) return dp[u][op];
21     vis[u][op]=1;
22     for ( int i=0;i<G[u].size();i++ )
23     {
24         int v=G[u][i];
25         if ( vis[v][1-op]==1 ) 
26         {
27             cir=true;
28             continue;
29         }
30         if ( dfs(v,1-op) ) 
31         {
32             dp[u][op]=true;
33             nxt[u][op]=v;
34         }
35     }
36     vis[u][op]=2;
37     return dp[u][op];
38 }
39 
40 void print(int u)
41 {
42     int op=1;
43     u=nxt[u][0];
44     while ( u!=-1 )
45     {
46         printf(" %d",u);
47         u=nxt[u][op];
48         op=1-op;
49     }
50 }
51 
52 int main()
53 {
54     int n,m,i,j,k,x,y,z,s;
55     while ( scanf("%d%d",&n,&m)!=EOF )
56     {
57         for ( i=1;i<=n;i++ ) G[i].clear();
58         memset(out,0,sizeof(out));
59         memset(vis,0,sizeof(vis));
60         memset(dp,false,sizeof(dp));
61         memset(nxt,-1,sizeof(nxt));
62         cir=false;
63         for ( i=1;i<=n;i++ )
64         {
65             scanf("%d",&k);
66             for ( j=1;j<=k;j++ )
67             {
68                 scanf("%d",&x);
69                 G[i].push_back(x); 
70                 out[i]++;
71             }
72         }
73         scanf("%d",&s);
74         if ( out[s]==0 )
75         {
76             printf("Lose\n");
77             continue;
78         }
79         dfs(s,0);
80         if ( dp[s][0] ) 
81         {
82             printf("Win\n");
83             printf("%d",s);
84             print(s);
85             printf("\n");
86         }
87         else if ( cir ) printf("Draw\n");
88         else printf("Lose\n");
89     }
90     return 0;
91 }
936B

 

29.(264C)http://codeforces.com/problemset/problem/264/C

题意:有n个气球,每个气球都有一个价值和一种颜色。现在有q个询问,每次询问给个a、b,让你从原气球中按顺序取出一些气球。对于一个气球若前面一个气球和它的颜色相同那么加上这个气球的值乘a,其他情况(第一个气球或者前一个气球和它颜色不同)加上这个气球的值乘以b,求这个值最大是多少

分析:对于每个询问要单独考虑,设置dp[i]表示对于当前位置往前的所有气球来说取出的气球颜色为i的情况中最多的值是多少,dp初始化为-inf表示不存在

对于一个气球在取出的气球中对整体所加的值的贡献不是a*val[i]就是b*val[i]那么我们分别考虑,

要是a*val[i],我们就去找前一个和它颜色一样气球即dp[col[i]]+a*val[i]

要是b*val[i],我们就要使这个气球是第一个气球,或者去找前面和它颜色不同气球中的最大值(即dp[x]max的值且x!=col[i]),这时候我们就应该记录前面气球中的最大值和次大值(要求颜色不同),这样就可以方便的更新了。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=1e5+10;
 7 const ll inf=1e17;
 8 ll dp[maxn];
 9 ll val[maxn],col[maxn];
10 
11 int main()
12 {
13     ll n,m,i,j,k,x,y,z,q,a,b,v,c;
14     ll ans,sum;
15     while ( scanf("%lld%lld",&n,&q)!=EOF )
16     {
17         for ( i=1;i<=n;i++ ) scanf("%lld",&val[i]);
18         for ( i=1;i<=n;i++ ) scanf("%lld",&col[i]);
19         while ( q-- )
20         {
21             scanf("%lld%lld",&a,&b);
22             sum=0;
23             for ( i=0;i<=n;i++ ) dp[i]=-inf;
24             int id1=0,id2=0;
25             for ( i=1;i<=n;i++ )
26             {
27                 v=val[i];
28                 c=col[i];
29                 ans=max(b*v,dp[c]+a*v);
30                 if ( id1!=c ) ans=max(ans,dp[id1]+b*v);
31                 else ans=max(ans,dp[id2]+b*v);
32                 if ( ans>dp[id1] )
33                 {
34                     if ( id1!=c )
35                     {
36                         id2=id1;
37                         id1=c;
38                     }
39                     else id1=c;
40                 }
41                 else
42                 {
43                     if ( ans>dp[id2] && c!=id1 ) id2=c;
44                 }
45                 dp[c]=max(dp[c],ans);
46                 sum=max(sum,ans);
47             }
48             printf("%lld\n",sum);
49         }
50     }
51     return 0;
52 }
264C

 

30.(234F)http://codeforces.com/problemset/problem/234/F

题意:给出n个栅栏,需要涂上红色和绿色。红色的面积不能超过啊a,绿色的面积不能超过b。给出每个栅栏的高度,宽度都是1。所得的价值等于相邻栅栏不同颜色的面积。求最小的价值。

分析:首先注意是文件输入,而不是标准的输入输出。设dp[i][j][k]第一维为前i行(可以设置为动态数组,因为只用到两行,但此题没关系),第二维为已经用了几个绿色的面积是多少,第三维为0表示第i行全为红,为1表示第i行全为绿。

转移时dp[i][j][0]=dp[i-1][j][0] (相邻两行都为红)

dp[i][ j+h[i] ][1]=dp[i-1][j][1] (相邻两行都为绿)

dp[i][j][0]=dp[i-1][j][1]+min(h[i],h[i-1]) (前一行为绿,这一行为红)

dp[i][ j+h[i] ][0]=dp[i-1][j][0]+min(h[i],h[i-1]) (前一行为红,这一行为绿)

注意:红色的面积和绿色的面积要满足题目的要求

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<set>
 5 #include<fstream>
 6 using namespace std;
 7 const int maxn=210;
 8 const int maxm=4e4+10;
 9 const int inf=1e9;
10 int dp[maxn][maxm][2];
11 int h[maxn],num[maxn];
12 set<int>st;
13 set<int>::iterator it;
14 
15 int main()
16 {
17     int n,a,b,x,y,z,ans,sum,i,j,k,val;
18     freopen("input.txt","r",stdin);  
19     freopen("output.txt","w",stdout);  
20     scanf("%d",&n);
21         sum=0;
22         num[0]=0;
23         scanf("%d%d",&a,&b);
24         for ( i=1;i<=n;i++ ) 
25         {
26             scanf("%d",&h[i]);
27             sum+=h[i];
28             num[i]=num[i-1]+h[i];
29         }
30         if ( sum>(a+b) ) 
31         {
32             printf("-1\n");
33             return 0;
34         }
35         ans=inf;
36         for ( i=0;i<=n;i++ )
37         {
38             for ( j=0;j<=a+b;j++ )
39             {
40                 for ( k=0;k<2;k++ ) dp[i][j][k]=inf;
41             }
42         }
43         st.clear();
44         st.insert(0);
45         dp[0][0][0]=dp[0][0][1]=0;
46         for ( i=1;i<=n;i++ )
47         {
48             for ( it=st.begin();it!=st.end();it++ )
49             {
50                 x=*it;
51                 val=dp[i-1][x][0];
52                 if ( val!=inf )
53                 {
54                     if ( x+h[i]<=b ) 
55                     {
56                         dp[i][x+h[i]][1]=min(dp[i][x+h[i]][1],val+min(h[i],h[i-1]));
57                         st.insert(x+h[i]);
58                     }
59                     if ( num[i]-x<=a ) dp[i][x][0]=min(dp[i][x][0],val);
60                 }
61                 val=dp[i-1][x][1];
62                 if ( val!=inf )
63                 {
64                     if ( x+h[i]<=b ) 
65                     {
66                         dp[i][x+h[i]][1]=min(dp[i][x+h[i]][1],val);
67                         st.insert(x+h[i]);
68                     }
69                     if ( num[i]-x<=a ) dp[i][x][0]=min(dp[i][x][0],val+min(h[i],h[i-1]));
70                 }
71             }
72         }
73         for ( it=st.begin();it!=st.end();it++ )
74         {
75             x=*it;
76             ans=min(ans,min(dp[n][x][0],dp[n][x][1]));
77         }
78         if ( ans==inf ) printf("-1\n");
79         else printf("%d\n",ans);
80     
81     return 0;
82 }
234F

 

31.http://codeforces.com/problemset/problem/404/D

题意:一维的扫雷,"*"表示雷,“0”表示左右两边没有雷,“1”表示左右两边有一个雷,“2”表示左右两个都是雷,“?”表示不确定任意填(但是需要满足左右两个方格内的条件),先求方案数

分析;dp[maxn][5],第一维为前i个。

假设第一维为i,

第二维为0表示第i位为雷,第i-1位和第i+1位任意。

第二维为1表示第i位没有雷,第i-1位和第i+1位都没有雷。

第二维为2表示第i位没有雷,第i-1位没有雷和第i+1位有雷。

第二维为3表示第i位没有雷,第i-1位有雷和第i+1位没有雷。

第二维为4表示第i位没有雷,第i-1位和第i+1位都有雷。

初始化都为0,dp[0][1]=dp[0][2]=1即第0位没有雷和第-1为没有雷的情况数都为1

转移时根据s[i]内的字符不同有不同的转移方式.

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1e6+10;
 6 const int mod=1e9+7;
 7 const int inf=1e9;
 8 int dp[maxn][5];
 9 char s[maxn];
10 
11 int main()
12 {
13     int n,m,i,j,k,x,y,z,ans;
14     while ( scanf("%s",s+1)!=EOF )
15     {
16         n=strlen(s+1);
17         memset(dp,0,sizeof(dp));
18         dp[0][1]=dp[0][2]=1;
19         for ( i=1;i<=n;i++ )
20         {
21             if ( s[i]=='?' )
22             {
23                 dp[i][0]=((dp[i-1][2]+dp[i-1][4])%mod+dp[i-1][0])%mod;
24                 dp[i][1]=(dp[i-1][1]+dp[i-1][3])%mod;
25                 dp[i][2]=(dp[i-1][1]+dp[i-1][3])%mod;
26                 dp[i][3]=dp[i-1][0];
27                 dp[i][4]=dp[i-1][0];
28             }
29             else if ( s[i]=='0' ) 
30             {
31                 dp[i][1]=(dp[i-1][3]+dp[i-1][1])%mod;
32             }
33             else if ( s[i]=='1' ) 
34             {
35                 dp[i][3]=dp[i-1][0];
36                 dp[i][2]=(dp[i-1][1]+dp[i-1][3])%mod;
37             }    
38             else if ( s[i]=='2' ) 
39             {
40                 dp[i][4]=dp[i-1][0];
41             }
42             else if ( s[i]=='*' ) 
43             {
44                 dp[i][0]=((dp[i-1][2]+dp[i-1][4])%mod+dp[i-1][0])%mod;
45             }
46         }
47         ans=((dp[n][0]+dp[n][1])%mod+dp[n][3])%mod;
48         printf("%d\n",ans);
49     }
50     return 0;
51 } 
404D

 

32.(918D)http://codeforces.com/contest/918/problem/D

题意:给定一个 DAG,每个边的权值为一个字母。两人初始各占据一个顶点(可以重合),轮流移动(沿着一条边从一个顶点移动到另一个顶点),要求每次边上的权值 ≥ 上一次的权值。无法移动者输。要求:对所有可能的初始情况,给出一张胜负表。

分析:组合游戏+dp。参考以下这篇文章https://www.cnblogs.com/kkkkahlua/p/8386936.html

特殊情况:两个点都在同一点上,那么先手必输

设置dp[i][j][k]表示先手在第i个点上,后手在第j个点上,上一条边的权值为k下先手能否获胜(能获胜为true,不能获胜为false),设置bool型vis数组判断是否被访问过

每次转移时有点类似与博弈论中的求sg函数,下一步中能走到必败态则该点就是必胜态,下一步无论怎么走都走不到必败态,则该点为必败态.

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 using namespace std;
 6 const int maxn=105;
 7 const int maxm=30;
 8 struct Edge{
 9     int v,w;
10     Edge(int _v=0,int _w=0):v(_v),w(_w) {}
11 };
12 bool dp[maxn][maxn][maxm];
13 bool vis[maxn][maxn][maxm];
14 vector<Edge>G[maxn];
15 
16 int dfs(int u,int v,int w)
17 {
18     int x,y,z;
19     if ( vis[u][v][w] ) return dp[u][v][w];
20     vis[u][v][w]=true;
21     if ( u==v ) return false;
22     bool flag=false;
23     for ( int i=0;i<G[u].size();i++ )
24     {
25         x=v;
26         y=G[u][i].v;
27         z=G[u][i].w;
28         if ( z>=w )
29         {
30             if ( !dfs(x,y,z) ) flag=true;
31         }
32     }
33     if ( flag )  dp[u][v][w]=1;
34     else dp[u][v][w]=0;
35     return dp[u][v][w];
36 }
37 
38 int main()
39 {
40     int n,m,i,j,k,x,y,z,u,v,w;
41     char s[10];
42     while ( scanf("%d%d",&n,&m)!=EOF )
43     {
44         for ( i=1;i<=n;i++ ) G[i].clear();
45         memset(dp,false,sizeof(dp));
46         memset(vis,false,sizeof(vis));
47         for ( i=1;i<=m;i++ )
48         {
49             scanf("%d%d%s",&u,&v,s);
50             w=s[0]-'a'+1;
51             G[u].push_back(Edge(v,w));
52         }
53         for ( i=1;i<=n;i++ )
54         {
55             for ( j=1;j<=n;j++ ) dfs(i,j,0);
56         }
57         for ( i=1;i<=n;i++ )
58         {
59             for ( j=1;j<=n;j++ )
60             {
61                 if ( dp[i][j][0] ) printf("A");
62                 else printf("B");
63             }
64             printf("\n");
65         }
66     }
67     return 0;
68 }
918D

 

33.(918C)http://codeforces.com/contest/918/problem/C

题意:找符合要求的区间个数

分析:

一般的括号匹配

 1 bool check(string s)
 2 {
 3     int top=0;
 4     for ( int i=0;i<s.size();i++ )
 5     {
 6         if ( s[i]=='(' ) top++;
 7         else top--;
 8         if ( top<0 ) break;
 9     }
10     return top==0;
11 }
一般的括号匹配

 

因为此题出现了“?”可以任意替代,所以采用维护一般括号匹配中top的范围(上届为high,下届为low),枚举区间长度,不断累加符合要求的区间数

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=5010;
 6 char s[maxn];
 7 
 8 int main()
 9 {
10     int n,m,i,j,k,x,y,z,ans,low,high;
11     while ( scanf("%s",s+1)!=EOF )
12     {
13         n=strlen(s+1);
14         ans=0;
15         for ( i=1;i<=n;i++ )
16         {
17             low=high=0;
18             for ( j=i;j<=n;j++ )
19             {
20                 if ( s[j]=='(' ) low++,high++;
21                 else if ( s[j]==')' ) low--,high--;
22                 else low--,high++;
23                 if ( high<0 ) break;
24                 if ( low<0 ) low=0;
25                 if ( (j-i)&1 && low==0 ) ans++;
26             }
27         }
28         printf("%d\n",ans);
29     }
30     return 0;
31 }
918C

 

34.(213C)http://codeforces.com/problemset/problem/213/C

题意:有n*n的方格,每个格子有一个数字,现在有两个人一个人从(1,1)走到(n,n)(但是他只能向右或者向下走)另外一个人从(n,n)走到(1,1)(他只能向左或者向上走),两个人每走到一个格子上就会累加上该格子内的分数,两个人走到同一个格子内时分数只累加一次

分析:该问题等效成两个人都从(1,1)出发往(n,n)走,因为只能往右或者往下走,所以走的步数,横坐标,纵坐标,三个中知道两个就能全部知道。所以设置dp[i][j][k]表示这两个人都走了i步,第一个人在第j行,第二个人在第k行所得到的最大分数。注意如果两个人会走到同一个格子上,那么这两个人一定是同时走到的。

考虑四种情况(往右往下分别组合).转移时只有当j==k时才加一个格子的数,否则都加两个格子的数

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=310;
 6 const int maxm=610;
 7 const int inf=1e9;
 8 int a[maxn][maxn];
 9 int dp[maxm][maxn][maxn];
10 int dir[4][2]={{0,0},{0,1},{1,0},{1,1}};
11 
12 int main()
13 {
14     int n,m,i,j,k,h,x,y,z,dx1,dx2,x1,x2,dy1,dy2;
15     while ( scanf("%d",&n)!=EOF )
16     {
17         for ( i=0;i<=2*n;i++ )
18         {
19             for ( j=1;j<=n;j++ )
20             {
21                 for ( k=1;k<=n;k++ ) dp[i][j][k]=-inf;
22             }
23         }
24         for ( i=1;i<=n;i++ )
25         {
26             for ( j=1;j<=n;j++ ) scanf("%d",&a[i][j]);
27         }
28         dp[0][1][1]=a[1][1];
29         for ( i=1;i<=2*n-2;i++ )
30         {
31             for ( j=1;j<=n;j++ )
32             {
33                 for ( k=1;k<=n;k++ )
34                 {
35                     for ( h=0;h<4;h++ )
36                     {
37                         dx1=j+dir[h][0];
38                         dx2=k+dir[h][1];
39                         dy1=i-dx1+2;
40                         dy2=i-dx2+2;
41                         if ( dy1<1 || dy1>n || dy2<1 || dy2>n || dx1<1 || dx1>n || dx2<1 || dx2>n || dp[i-1][j][k]==-inf ) continue;
42                         if ( dx1==dx2 ) dp[i][dx1][dx2]=max(dp[i][dx1][dx2],dp[i-1][j][k]+a[dx1][dy1]);
43                         else dp[i][dx1][dx2]=max(dp[i][dx1][dx2],dp[i-1][j][k]+a[dx1][dy1]+a[dx2][dy2]);
44                     }
45                 }
46             }
47         }
48         printf("%d\n",dp[2*n-2][n][n]);
49     }
50     return 0;
51 }
213C

 

35.(10D)http://codeforces.com/problemset/problem/10/D

题意:给定两个串求最长公共上升子序列

分析:经典dp题。详细介绍见:http://www.cnblogs.com/sasuke-/p/5396843.html

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=510;
 6 int a[maxn],b[maxn];
 7 int dp[maxn],pre[maxn];
 8 
 9 void print(int x)
10 {
11     if ( x==0 ) return;
12     print(pre[x]);
13     printf("%d ",b[x]);
14 }
15 
16 int main()
17 {
18     int n,m,i,j,k,x,y,z,ans,ans2,pos;
19     while ( scanf("%d",&n)!=EOF )
20     {
21         for ( i=1;i<=n;i++ ) scanf("%d",&a[i]);
22         scanf("%d",&m);
23         for ( i=1;i<=m;i++ ) scanf("%d",&b[i]);
24         memset(dp,0,sizeof(dp));
25         memset(pre,0,sizeof(pre));
26         for ( i=1;i<=n;i++ )
27         {
28             pos=0;
29             for ( j=1;j<=m;j++ )
30             {
31                 if ( a[i]==b[j] ) 
32                 {
33                     dp[j]=dp[pos]+1;
34                     pre[j]=pos;
35                 }
36                 if ( a[i]>b[j]&&dp[pos]<dp[j] ) pos=j;
37             }
38         }
39         ans=ans2=0;
40         for ( i=1;i<=m;i++ )
41         {
42             if ( dp[i]>ans2 )
43             {
44                 ans2=dp[i];
45                 ans=i;
46             }
47         }
48         printf("%d\n",ans2);
49         print(ans);
50         printf("\n");
51     }
52     return 0;
53 }
10D

 

36.(587B)http://codeforces.com/problemset/problem/587/B

题意:给出一个有l个数的序列,序列每n个数一循环,在每个循环里取一个数,最多取m个数,要使得取出的子序列递增,求方案数。

分析:DP,dp[i][j]第一维表示取的是第i个,第二维表示最后一个数的下标编号为j,可以得到转移方程 dp[i][j]=sum(dp[i-1][k]) (a[k]<=a[j])

设置sum[i](1<=i<=m)表示第i层所有的方案数之和(因为此题需要用到前缀和),f[i]表示最后一项所选的下标编号是i(0<=i<n*m)的方案数。

f[i]=sum[i/n-1]即对于当前的i来说方案数为上一层满足条件的方案数之和(所以在操作前需要排序)

然后更新sum[i/n],sum[i/n]+=f[i]

当i<L时,该值是有效的。ans+=f[i]*[(l-i-1)/n+1] 所表示的含义是满足结尾是i(长度为i/n)这样的情况数有[(l-i-1)/n+1] (减一是因为所以数的下标是从0开始的,l-i-1是要求出后面区间的长度,除n表示有多少个同等情况存在,加一表示本身这种情况)那么多倍。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=1e6+10;
 7 const ll mod=1e9+7;
 8 ll a[maxn],f[maxn],sum[maxn];
 9 pair<ll,ll>p[maxn];
10 
11 void plu(ll &x,ll y)
12 {
13     x=(x+y)%mod;
14 }
15 
16 int main()
17 {
18     ll n,l,m,x,y,z,ans,cnt;
19     int i,j,k;
20     while ( scanf("%lld%lld%lld",&n,&l,&m)!=EOF )
21     {
22         for ( i=0;i<n;i++ ) scanf("%lld",&a[i]);
23         for ( i=0;i<n*m;i++ ) p[i]=make_pair(a[i%n],i);
24         sort(p,p+n*m);
25         ans=0;
26         memset(sum,0,sizeof(sum));
27         memset(f,0,sizeof(f));
28         for ( i=0;i<n*m;i++ )
29         {
30             if ( p[i].second<n ) f[p[i].second]=1;
31             else f[p[i].second]=sum[p[i].second/n-1];
32             plu(sum[p[i].second/n],f[p[i].second]);
33             if ( p[i].second<l )
34             {
35                 plu(ans,(f[p[i].second]*(((l-p[i].second-1)/n)%mod+1)%mod));
36             }
37         }
38         printf("%lld\n",ans);
39     }
40     return 0;
41 }
587B

 

转载于:https://www.cnblogs.com/HDUjackyan/p/8996136.html

区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值