dp选做

P1356 数列的整除性

考虑暴力,每个数可能被加上或减去,所以复杂度 $O(2^n)$。

所以考虑dp,令 $f_{i,j}$ 为选取了前 $i$ 个数后除 $k$ 是否有余数为 $j$。

所以目标状态是 $f_{n,0}$。

那么我们考虑转移:

因为 $a \bmod b+c \bmod b=(a+c) \bmod b.$

所以我们易得 $f_{i,j}=f_{i-1,(j-a[i]) \bmod k} || f_{i-1,(j+a[i]) \bmod k}.$ 

最后处理一下边界即可。

代码:

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cmath>
 4 #include<cstring>
 5 
 6 using namespace std;
 7 
 8 const int N=1e4+10;
 9 const int M=1e2+10;
10 
11 int a[N],n,k,t;
12 bool f[N][M];
13 
14 int main(){
15     scanf("%d",&t);
16     while(t--){
17         memset(f,0,sizeof(f));
18         scanf("%d %d",&n,&k);
19         for(int i=1;i<=n;i++) scanf("%d",&a[i]);
20         f[0][0]=1;
21         f[1][(a[1]%k+k)%k]=1;f[1][(-a[1]%k+k)%k]=1;
22         for(int i=2;i<=n;i++){
23             for(int j=0;j<k;j++){
24 
25                 f[i][j]=(f[i-1][((j+a[i]%k)+k)%k])||(f[i-1][(j-a[i]%k+k)%k]);
26             }
27         }
28         if(f[n][0]==1) printf("Divisible\n");
29         else printf("Not divisible\n");
30     }
31     return 0;
32 }
View Code

CF467C George and Job

我们定义状态 $f_{i,j}$ 为前i个数选了j组后的最大值。

对于一个区间 $x-{i-m},x_{i-m+1}…x_i$ 我们如果选择不选,则直接继承上一个的状态。

否则我们的状态就应从 $f_{i-m,j-1}$ 处转移,因为我们比这个位置的数多了 $m$ 个数,$1$ 个区间。

然后加上 $\sum_{k=(i-m+1)}^{i} x_k $,而这个值很容易用前缀和维护。

最后,在枚举时注意下标的起始,避免出现负下标。

时间复杂度 $O(nk)$。

 1 #include<cstdio>
 2 #include<iostream>
 3 
 4 using namespace std;
 5 
 6 #define ll long long 
 7 const int N=5e3+10;
 8 ll sum[N],f[N][N];
 9 ll n,m,k,x[N];
10 
11 int main(){
12     scanf("%lld %lld %lld",&n,&m,&k);
13     for(int i=1;i<=n;i++){
14         scanf("%lld",&x[i]);
15         sum[i]=sum[i-1]+x[i];
16     }
17     for(int j=1;j<=k;j++){
18         for(int i=j*m;i<=n;i++){
19             f[i][j]=max(f[i-1][j],f[i-m][j-1]+sum[i]-sum[i-m]);
20         }
21     }
22     printf("%lld",f[n][k]);
23     return 0;
24 }
View Code

P1352 没有上司的舞会

树形dp基础题。

定义一个状态 $f_{i,0/1}$ 为第 $i$ 个点选或不选时它的最大贡献。

我们考虑第 $i$ 个点贡献不选为 $0$,选了就为本身的点权,这就是初始化。

接着从根节点开始枚举,如果选的话子树都不能选,不选的话子树有选或不选两种状态。

枚举到 $max$ 后向上合并即可,最后状态即为根节点选或不选。

 1 #include<cstdio>
 2 #include<iostream>
 3 
 4 using namespace std;
 5 
 6 const int N=6e3+10; 
 7 int cnt,n,r[N],fa[N],u,v,root;
 8 int f[N][2];
 9 struct edge{
10     int to,nxt;
11 }e[N];
12 int head[N];
13 
14 void add(int u,int v){
15     cnt++;
16     e[cnt].to=v;
17     e[cnt].nxt=head[u];
18     head[u]=cnt;
19 }
20 
21 void sou(int node){
22     for(int i=head[node];i;i=e[i].nxt){
23         int k=e[i].to;
24         sou(k);
25         f[node][0]+=max(f[k][1],f[k][0]); 
26         f[node][1]+=f[k][0];
27     }
28 }
29 
30 int main(){
31     scanf("%d",&n);
32     for(int i=1;i<=n;i++){
33         scanf("%d",&r[i]);
34         f[i][1]=r[i]; 
35     } 
36     for(int i=1;i<n;i++){
37         scanf("%d %d",&u,&v);
38         fa[u]++;
39         add(v,u);
40     }
41     for(int i=1;i<=n;i++) if(!fa[i]) root=i;
42     sou(root);
43     printf("%d",max(f[root][1],f[root][0]));
44     return 0;
45 }
View Code

 P3478 [POI2008] STA-Station

算换根dp板子罢(

两遍dfs算子树和父亲的贡献。函数名致敬同级大佬(

 1 #include<cstdio>
 2 #include<iostream> 
 3 
 4 #define int long long
 5 
 6 using namespace std;
 7 
 8 const int N=1e6+10;
 9 int dis[N<<1],dep[N<<1],siz[N<<1]; 
10 int n,u,v,cnt,ans,maxn=-1;
11 struct tree{
12     int to,nxt;
13 }e[N<<1];
14 int head[N<<1];
15 
16 void add(int u,int v){
17     cnt++;
18     e[cnt].to=v;
19     e[cnt].nxt=head[u];
20     head[u]=cnt; 
21 }
22 
23 void sou1(int u,int fa){
24     siz[u]=1;
25     for(int i=head[u];i;i=e[i].nxt){
26         int to=e[i].to;
27         if(to==fa) continue;
28         dep[to]=dep[u]+1;
29         sou1(to,u);    
30         siz[u]+=siz[to];
31     }
32 }
33 
34 void sou2(int u,int fa){
35     for(int i=head[u];i;i=e[i].nxt){
36         int to=e[i].to;
37         if(to==fa) continue;
38         dis[to]=dis[u]-2*siz[to]+siz[1];
39         sou2(to,u);
40     }
41 }
42 
43 signed main(){
44     scanf("%lld",&n);
45     for(int i=1;i<n;i++){
46         scanf("%lld %lld",&u,&v);
47         add(u,v);
48         add(v,u);
49     }
50     sou1(1,0);
51     for(int i=1;i<=n;i++)dis[1]+=dep[i];
52     sou2(1,0);
53     for(int i=1;i<=n;i++){
54         if(dis[i]>maxn){
55             ans=i;
56             maxn=dis[i];
57         }
58     }
59     printf("%lld",ans);
60     return 0;
61 }
View Code

P2899 [USACO08JAN]Cell Phone Network G

树形dp。

对于树上的一个点而言他可以被父亲覆盖,被自己覆盖,被儿子覆盖。

被父亲覆盖:那么他的儿子就不能被父亲覆盖(因为没信号。

被自己覆盖:儿子怎样都可以。

被儿子覆盖:它的儿子也不能选父亲,但它的儿子中至少要有一个自己有信号,所以加个变量处理。

式子可以推出变量 $k$ 可以处理掉儿子中没有一个有的的情况并转变为选他自己。

 1 #include<cstdio>
 2 #include<iostream>
 3 
 4 using namespace std;
 5 
 6 const int N=1e4+10;
 7 const int inf=0x7ffffff;
 8 struct edge{
 9     int nxt,to;
10 }e[N<<1];
11 int head[N];
12 int n,u,v,cnt;
13 int dp[N][3];
14 
15 void add(int u,int v){
16     cnt++;
17     e[cnt].nxt=head[u];
18     e[cnt].to=v;
19     head[u]=cnt;
20 }
21 
22 
23 int minn(int x,int y,int z){
24     int k=x<y?x:y;
25     return k<z?k:z;
26 }
27 
28 void sou(int fa,int u){
29     int k=inf;
30     for(int i=head[u];i;i=e[i].nxt){
31         int v=e[i].to;
32         if(v==fa) continue;
33         sou(u,v);
34         dp[u][0]+=min(dp[v][1],dp[v][2]);
35         dp[u][1]+=minn(dp[v][1],dp[v][0],dp[v][2]);
36         dp[u][2]+=min(dp[v][1],dp[v][2]);
37         k=min(k,dp[v][1]-min(dp[v][2],dp[v][1]));
38     }
39     dp[u][2]+=k;
40     dp[u][1]+=1;
41 }
42 
43 int main(){
44     scanf("%d",&n);
45     for(int i=1;i<n;i++){
46         scanf("%d %d",&u,&v);
47         add(u,v);
48         add(v,u);
49     }
50     sou(0,1);
51     printf("%d",min(dp[1][1],dp[1][2])); 
52     return 0; 
53 } 
View Code

P3146 [USACO16OPEN]248 G

区间dp

定义状态 $f_{l,r}$ 表示区间 $[l,r)$ 合并后能得到的最大值。

枚举断点即可。

注意初始化和细节。

时间复杂度 $O(n^3)$ 可以跑过。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cmath>
 4 #include<algorithm>
 5 
 6 using namespace std;
 7 
 8 const int N=250;
 9 int f[N][N];
10 int n,a[N],ans=0;
11 
12 int main(){
13     scanf("%d",&n);
14     for(int i=1;i<=n;i++){
15         scanf("%d",f[i]+i);
16         ans=max(ans,f[i][i]);//初始化ans防止不能合并
17     } 
18     for(int len=2;len<=n;len++){
19         for(int i=1;i+len-1<=n;i++){//枚举区间 
20             int j=len+i-1;
21             for(int k=i;k<j;k++){//枚举断点 
22                 if(f[i][k]==f[k+1][j]&&f[i][k]/*防止没更新到也算进去*/){ 
23                     f[i][j]=max(f[i][j],f[i][k]+1);
24                     ans=max(ans,f[i][j]);//由于答案不一定全部能合并,所以每一次记录一下最小值 
25                 }
26             }
27         }
28     }
29     printf("%d",ans);
30     return 0;
31 }
View Code

 

 


 

 

 

  

 

posted @ 2022-08-04 14:25  々Trouvaille々  阅读(27)  评论(0)    收藏  举报