7.29 dp动态规划
A题:
Description
Input
接下里T组测试数据
每组测试数据第一行为两个整数N, M(1 <= N, M <= 1000)代表方格网的大小
接下来N行,每一行M个数,代表a[i][j](1 <= a[i][j] <= 1000)
Output
Sample Input
1
2 2
100 1
50 1
Sample Output
151
很好列出状态转移方程 注意边界情况
#include <bits/stdc++.h> using namespace std; int a[1010][1010]; long long dp[1010][1010]; int main() { int t; cin>>t; while(t--) { int n,m; scanf("%d%d",&n,&m); int i,j; for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { scanf("%d",&a[i][j]); } } for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if(i==1&&j==1)dp[i][j]=a[i][j]; else if(i==1)dp[i][j]=a[i][j]+dp[i][j-1]; else if(j==1)dp[i][j]=a[i][j]+dp[i-1][j]; else dp[i][j]=a[i][j]+max(dp[i-1][j],dp[i][j-1]); } } printf("%lld\n",dp[n][m]); } return 0; }
B题:
Description
Input
接下来T组测试数据
每组测试数据第一行为两个整数N, M(1 <= N <= 1000, 1 <= M <= N)其含义如题目
接下来一行N个整数a[i](1 <= a[i] <= 1e9)
Output
Sample Input
2
3 2
1 2 3
3 2
3 2 1
Sample Output
3
0
HINT
第一组测试数据可以选1,2或者,1,3或者2,3一共三种选法
此题很难,据说是CCPC原题,要用到动态规划,树状数组,离散化
dp[i][j]=dp[a[k]<a[i]][j-1] 即以a[i]结束的,长度为J的递增子序列个数等于以比a[i]小的元素结尾的,长度为J-1的递增子序列之和)用树状数组可以简化求和
树状数组
更新单点值,查询前缀和
把值当位置传给update进行更新,update(a[i],1)将a[i]位置的元素个数+1(代表a[i]位置放了一个数),所有包含a[i]的区间和全部加1,查询query(a[i]-1)即可查询到 i 之前小于等于a[i]的元素个数
现在开一个tree[ ][ ],tree[i][j]代表以a[i]结尾长度为j的递增子序列的区间和,query(a[i]-1,d)即可查询长度为d的 以i之前 小于等于a[i]的元素为结束的 递增子序列个数之和
因为查询到的是小于等于,而题目要求的是严格递增,要把等于删去,所以我们对排序做一些修改,把 i 之前等于a[i]的元素放到 i 的后面
因为a[i]值很大,开不了那么大的数组,所以离散化用相对大小代表值
#include <bits/stdc++.h> using namespace std; #define LL long long const int maxn=1010; const int mod=1e9+7; LL tree[maxn][maxn];//树状数组,代表区间和 LL dp[maxn][maxn];//dp[i][j]表示考虑到第i个数,且以a[i]结尾,长度为j的递增序列个数 int a[maxn]; int n,m; struct node { int value; int id; bool operator<(const node &res)const { if(value==res.value)return id>res.id;//把值相同的编号按从大到小排,编号小的放到后面,这样算前缀和的时候就不会把值相等编号又比它小的算进去 else return value<res.value;//按值从小到大排序 //例如 5 5 8 ,算第二个5的时候就不会把第一个5算进去,算第一个5的时候后面那个5还没考虑进来呢 } }Node[maxn]; int Rank[maxn];//离散化后表示相对位置大小的数组(第一小,第二小...)把值的大小转化为相对位置的大小 int lowbit(int x) { return x&(-x); } void update(int loc,int d,int value) { for(int i=loc;i<=n;i+=lowbit(i)) { tree[i][d]=(tree[i][d]+value)%mod;//更新操作可以这样理解 将loc位置上的值加上了value,则包含这个位置的所有区间和tree[i]都要加上value } //关于哪个区间含有这个元素有详细的证明 } LL query(int loc,int d) { LL ans=0; for(int i=loc;i>=1;i-=lowbit(i))//查询操作是查询从1到loc的前缀区间和 { ans=(tree[i][d]+ans)%mod; } return ans; } int main() { int t; cin>>t; while(t--) { scanf("%d%d",&n,&m); int i,j; for(i=1;i<=n;i++) { scanf("%d",&Node[i].value); Node[i].id=i; } sort(Node+1,Node+n+1);//将元素按从小到大排好序 for(i=1;i<=n;i++)//离散化 { Rank[Node[i].id]=i; //Node已经排好序了,i即代表Node的相对位置大小 } //例如,第一个元素,它在未排序前的编号为Node[1].id,输入编号,即可知道它现在的值(相对位置大小,可代表值) for(i=1;i<=n;i++) //以a[i]->Rank[i]为终点。考虑到第i个数 { dp[i][1]=1;//终点为i,长度为1的肯定只有它本身这一个啊 update(Rank[i],1,1);//把以Rank[i]结束,长度为1且个数为1的子序列更新进去 for(j=2;j<=min(m,i);j++)//从长度为2的开始遍历,考虑i之前的数,长度肯定小于i,并且只考虑到长度为m就够了 { LL temp=query(Rank[i]-1,j-1);//树状数组求出以比Rank[i]小的数结尾,长度为j-1的递增序列有多少个 dp[i][j]=temp; update(Rank[i],j,dp[i][j]); //把以Rank[i]结尾,长度为j,个数为dp[i][j]的更新进去 } } LL ans=0; for(i=1;i<=n;i++) { ans=(ans+dp[i][m])%mod; } printf("%lld\n",ans); memset(dp,0,sizeof dp); memset(tree,0,sizeof tree); } return 0; }
C题:
Description
Input
接下来T组测试数据
每组测试数据第一行为一个整数N(1 <= N <= 1e5)代表序列的长度
接下来一行N个整数a[i](-1000 <= a[i] <= 1000)代表序列a[i]
Output
Sample Input
2
3
1 -100 3
4
99 -100 98 2
Sample Output
3
100
HINT
第一组测试样例,选择区间[3,3]和为3最大,第二组测试样例选择区间[3, 4]和为98 + 2 = 100最大
此题两种解法,可以用线段树,也可以dp
线段树求区间最大连续和
#include <bits/stdc++.h> #include<algorithm> using namespace std; const int maxn=1e5+10; int a[maxn]; struct node { int L,R; long long ms,rs,ls,s; }Node[maxn<<2]; void pushup(int i) { Node[i].ms=max(Node[i<<1].ms,Node[(i<<1)|1].ms); Node[i].ms=max(Node[i].ms,Node[i<<1].rs+Node[(i<<1)|1].ls); Node[i].ls=max(Node[i<<1].ls,Node[i<<1].s+Node[(i<<1)|1].ls); Node[i].rs=max(Node[(i<<1)|1].rs,Node[i<<1].rs+Node[(i<<1)|1].s); Node[i].s=Node[i<<1].s+Node[(i<<1)|1].s; return; } void build(int i,int l,int r) { Node[i].L=l; Node[i].R=r; if(r==l) { Node[i].s=a[l]; Node[i].ms=a[l]; Node[i].ls=a[l]; Node[i].rs=a[l]; return; } int mid=(l+r)>>1; build(i<<1,l,mid); build((i<<1)|1,mid+1,r); pushup(i); } long long queryR(int i,int l,int r) { if(Node[i].L==l&&Node[i].R==r) { return Node[i].rs; } int mid=(Node[i].L+Node[i].R)>>1; if(r<=mid) return queryR(i<<1,l,r); else if(l>mid) return queryR((i<<1)|1,l,r); else { long long lans=queryR(i<<l,l,mid); long long rans=queryR((i<<1)|1,mid+1,r); return max(rans,lans+Node[(i<<1)|1].s); } } long long queryL(int i,int l,int r) { if(Node[i].L==l&&Node[i].R==r) { return Node[i].ls; } int mid=(Node[i].L+Node[i].R)>>1; if(r<=mid) return queryL(i<<1,l,r); else if(l>mid) return queryL((i<<1)|1,l,r); else{ long long lans=queryL(i<<l,l,mid); long long rans=queryL((i<<1)|1,mid+1,r); return max(lans,rans+Node[i<<1].s); } } long long query(int i,int l,int r) { if(Node[i].L==l&&Node[i].R==r) { return Node[i].ms; } int mid=(Node[i].L+Node[i].R)>>1; if(r<=mid) return query(i<<1,l,r); else if(l>mid) return query((i<<1)|1,l,r); else { long long lans=query(i<<1,l,mid); long long rans=query((i<<1)|1,mid+1,r); long long ans=max(lans,rans); return max(ans,queryR(i<<1,l,mid)+queryL((i<<1)|1,mid+1,r)); } } int main() { int t; cin>>t; while(t--) { int n; scanf("%d",&n); int i; for(i=1;i<=n;i++) { scanf("%d",&a[i]); } build(1,1,n); long long ans=query(1,1,n); printf("%lld\n",ans); } return 0; }
dp
#include <bits/stdc++.h> using namespace std; const int maxn = 1000 + 10; typedef long long LL; const LL mod = 1e9 + 7; int N, M; int a[maxn]; LL Tree[maxn][maxn]; LL dp[maxn][maxn];//dp[i][j]表示考虑到第i个数,且以第a[i]个数结尾,长度为j的递增序列个数 struct node{ int value; int id; bool operator <(const node &res) const{ if(value == res.value) return id > res.id; else return value < res.value; } }Node[maxn]; int Rank[maxn]; void init() { memset(Tree, 0, sizeof(Tree)); memset(dp, 0, sizeof(dp)); } int lowbit(int x) { return x&(-x); } void add(int loc, int d, LL value) { for(int i = loc; i <= N; i += lowbit(i)) { Tree[i][d] = (Tree[i][d] + value) % mod; } } LL get(int loc, int d) { LL ans = 0; for(int i = loc; i >= 1; i -= lowbit(i)) { ans = (ans + Tree[i][d]) % mod; } return ans; } int main() { freopen("data.in", "r", stdin); freopen("data.out", "w", stdout); int T; scanf("%d", &T); while(T--) { scanf("%d%d", &N, &M); init(); for(int i = 1; i <= N; i++) { scanf("%d", &Node[i].value); Node[i].id = i; } sort(Node + 1, Node + N + 1); for(int i = 1; i <= N; i++) { Rank[Node[i].id] = i; } for(int i = 1; i <= N; i++) { dp[i][1] = 1; add(Rank[i], 1, 1); for(int j = 2; j <= min(M, i); j++) { LL temp = get(Rank[i] - 1, j - 1); dp[i][j] = (dp[i][j] + temp) % mod; add(Rank[i], j, dp[i][j]); } } LL ans = 0; for(int i = 1; i <= N; i++) { ans = (ans + dp[i][M]) % mod; } printf("%lld\n", ans); } return 0; }
D题:
Input
接下来T组测试数据
每组测试数据第一行为一个整数N(1 <= N <= 1e5)
接下来一行N个非负整数a[i]代表每一个节点上的一个苹果的营养价值(0 <= a[i] <= 1e6)
接下来N - 1行,每一行两个整数u, v代表u, v之间有一条边(1 <= u, v <= N)
Output
Sample Input
2
3
1 2 3
1 2
2 3
3
1 1 1
1 2
2 3
Sample Output
2 1 1
3 2 1
HINT
在第一组样例中,以1为根的子树包括节点1,2,3但是由于2号节点上的苹果营养价值为2不是奇数,所以以1为根的子树上一共有2个营养价值为奇数的苹果。以2为根的子树包括节点2, 3,所以只有1个营养价值为奇数的苹果.以3为根的子树就是3自身,所以也只有1个营养价值为奇数的苹果。所以最后输出2 1 1
此题就是树状dp,用前向星存图即可
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+10;//注意这里的maxn应该开题目给的双倍,因为边是双向的,加边还要开更多 int a[maxn]; int dp[maxn]; bool visit[maxn]; int n; int head[maxn]; //head[i]表示以i为起点的最后一条边的编号; struct edge { int to;//这条边的终点 int last; //与自己起点相同的上一条边的编号 }Edge[maxn*2];//边数组 int cnt; //记录当前边的编号 void add(int u,int v)//加边 //起点u,终点v;; { Edge[cnt].to=v; Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来, head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边 } void dfs(int root)//这棵树的根节点 { if(a[root]&1)dp[root]=1;//根节点自己 else dp[root]=0; visit[root]=true; //节点已访问过 for(int i=head[root];i!=-1;i=Edge[i].last)//把节点当起点, { int v=Edge[i].to;//子节点是终点 if(!visit[v]) { dfs(v);//把子节点当根节点去找它的子节点 dp[root]+=dp[v];//根节点的子节点数加上它的子节点的子节点数 } } } int main() { int t; cin>>t; while(t--) { scanf("%d",&n); int i; for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=n;i++)head[i]=-1; cnt=1; for(i=1;i<=n-1;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } memset(visit,false,sizeof visit);//记得一定要memset dfs(1); for(i=1;i<=n;i++)printf("%d ",dp[i]); printf("\n"); } return 0; }