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 }
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 }
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 }
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 }
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 }
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 }

浙公网安备 33010602011771号