背包问题
https://vjudge.net/contest/256610#problem/Q
dp完全背包
现在一共有N个程序员,一个程序员(编号为i)写一行代码会有Ai个bug.目的是完成一个M行的程序。
第i个程序员会写Vi行代码,对应就会出现Vi*Ai个Bug.
问将这个M行的程序写完之后,Bug数量不超过b个的分配方案数。
设定dp【i】【j】表示一共写了i行代码,一共写出来了j个Bug的方案数。
那么很简单的递推方程:dp【i】【j】=dp【i-1】【j-Ai】;
那么ans=Σdp【m】【i】(0<=i<=b)
#include<stdio.h> #include<string.h> using namespace std; int dp[505][500*4]; int main() { int n,m,b,mod; while(~scanf("%d%d%d%d",&n,&m,&b,&mod)) { memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int z=0;z<n;z++) { int x; scanf("%d",&x); for(int i=1;i<=m;i++) { for(int j=x;j<=b;j++) { dp[i][j]+=dp[i-1][j-x]; dp[i][j]%=mod; } } } int ans=0; for(int i=0;i<=b;i++) { ans+=dp[m][i]; ans%=mod; } printf("%d\n",ans); } }
https://vjudge.net/contest/258242#problem/I
记忆化
可以一步一步跳,也可以跳到后面一个颜色相同的点。。
dp,然后要记录某个颜色最近的那个点的坐标,然后状态转移。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 2e5 + 5; int a[maxm], b[maxm], c[maxm]; int n, num; int main() { scanf("%d", &num); while(num) { memset(b, -1, sizeof(b)); scanf("%d", &n); for(int i = 1; i <= n; i++){ scanf("%d", &a[i]); } c[1] = 0; b[a[1]] = 0; for(int i = 2; i <= n; i++) { if(b[a[i]] != -1) { c[i] = min(c[i-1] + 1, b[a[i]] + 1); b[a[i]] = c[i]; } else { c[i] = c[i-1] + 1; b[a[i]] = c[i]; } } printf("%d\n", c[n]); num--; } return 0; }
https://vjudge.net/contest/258509#problem/I
抽屉原理,可以用dp
n个数字,是否存在一个序列是的模上m等于0,
根据抽屉原理,n > m是一定满足(看前缀和)
然后就是dp了(状态转移的那种dp(记录两个dp数组来转移))
#include<cstdio> #include<cstring> const int N = (int)1e6 + 5; int n, m; int a[N]; bool dp[2][1000];//滚动数组 bool work(){ if(n > m) return true;//多加这一句,TLE的代码说不定就能AC dp[0][a[0]] = true; for(int i = 1; i < n; i ++){ memset(dp[i & 1], 0, sizeof(bool)*1000); dp[i & 1][a[i]] = true; for(int j = 0; j < m; j ++){ if(dp[(i-1) & 1][j]){ dp[i & 1][(j + a[i]) % m] = true; dp[i & 1][j] = true; } } if(dp[i & 1][0]) return true; } return dp[(n-1) & 1][0]; } int main(){ scanf("%d%d", &n, &m); for(int i = 0; i < n; i ++){ scanf("%d", &a[i]); a[i] %= m; } puts(work() ? "YES" : "NO"); }
https://vjudge.net/contest/264760#problem/F
字符串dp
给定一个括号字符串,然后两头加上其他字符串,保证每个前缀的左括号数量大于等于右括号数量,并且总体点的左括号等于右括号数量。
把左括号看成+1,右括号看成-1.先统计字符串s中‘(’的最小前缀和为minn,s中前缀和为x,那么p只需要满足p的前缀和+minn>=0,符合这样的不等式的p都是可行解,那么我们预处理出一个dp[i][j],代表i个括号平衡度为j的个数,那么之后枚举p的长度i和p的平衡度j,在满足 p的前缀和(j)+minn>=0 和 q的前缀和(j+x)<=m-n-i(及q的长度) 其实在这里q的前缀和应该为-(j+x),因为总体前缀和应为0 但因为对称 所以可看成j+x
#include<cstdio>
#include<algorithm> using namespace std; const int maxn = 1e5 + 200; const int mod = 1e9 + 7; char s[maxn]; long long dp[2500][2500]; int main() { int n, m; scanf("%d%d", &n, &m); scanf("%s", s + 1); dp[0][0] = 1;
for (int i = 1; i <= n - m; i++)//预处理长度为n-m,平衡度为0~i的dp[i][j] for (int j = 0; j <= i; j++) if (j > 0)dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j + 1])%mod; else dp[i][j] = dp[i - 1][j+1]; int minn = m+1,x=0; for (int i = 1; i <= m; i++) { if (s[i] == '(')x++; else x--; minn = min(minn, x); } long long ans = 0; for (int i = 0; i <= n - m; i++) for (int j = 0; j <= i; j++) if (j + minn >= 0 && (j + x) <= n - m - i) ans = (ans + dp[i][j] * dp[n - m - i][(j + x)]) % mod; printf("%I64d\n", ans); }
多重背包
Cash Machine
多重背包题 , 二进制优化
给出一个金币值,然后给出每种金币的值和数量,然后求出小于该金币值的最大的值。
//这一种是加一个num数组,相当于代替了一套循环,来限制那种物品能拿多少个
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxm = 1e5 + 5; int n, m; int c[maxm], v[maxm]; int dp[maxm], num[maxm]; int main() { while(~scanf("%d%d", &n, &m)) { memset(dp, 0, sizeof(dp)); for(int i = 1; i <= m; i++) scanf("%d%d", &c[i], &v[i]); if(n == 0 || m == 0) { printf("0\n"); continue; } for(int i = 1; i <= m; i++) { memset(num, 0, sizeof(num)); for(int j = v[i]; j <= n; j++) { if(dp[j - v[i] ] + v[i] > dp[j] && num[j - v[i] ] < c[i]) { dp[j] = dp[j - v[i] ] + v[i]; num[j] = num[j - v[i] ] + 1; } } } printf("%d\n", dp[n]); } return 0; }
//这种做法是把有多个的物品按二进制分开,1个算一种,2个算一种,4个算一种等,然后按照01背包的解法来解
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxm = 1e5 + 5; int n, m; int c[maxm], v[maxm], w[maxm * 100]; int dp[maxm], num[maxm]; int cnt; void check(int u, int v) { int flag = 1; while(1) { if(u >= flag) { u -= flag; w[++cnt] = flag * v; flag *= 2; } else { w[++cnt] = u * v; break; } } } int main() { while(~scanf("%d%d", &n, &m)) { memset(dp, 0, sizeof(dp)); cnt = 0; for(int i = 1; i <= m; i++) scanf("%d%d", &c[i], &v[i]), check(c[i], v[i]); if(n == 0 || m == 0) { printf("0\n"); continue; } for(int i = 1; i <= cnt; i++) { for(int j = n; j >= w[i]; j--) { dp[j] = max(dp[j], dp[j - w[i] ] + w[i]); } } printf("%d\n", dp[n]); } return 0; }
https://blog.csdn.net/chuck001002004/article/details/50340819
https://blog.csdn.net/qq_40679299/article/details/81978770
Ant Counting
N中蚂蚁,每种蚂蚁有几个,然后每次排除1个2个或者全部,求s 到 r的方案数之和,注意是集合
这一种方法是按集合的思想来的,对每一种物品内来讨论,对于第一种蚂蚁,派0个,一个,两个,三个的方法都是1,对于第二种蚂蚁他派出蚂蚁就可以继承前面的某一个范围的和(更自己有多少个有关)。
#include<cstdio> #include<cstring> using namespace std; const int N = 1e5 + 5; const int MOD = 1000000; typedef long long ll; int dp[N]; int sum[N];//前缀和数组 int cnt[1005]; int main() { int T, A, S, B, id; while(~scanf("%d%d%d%d", &T, &A, &S, &B)) { int low; memset(dp, 0, sizeof(dp)); memset(cnt, 0, sizeof(cnt)); for(int i = 0; i < A; i++) { scanf("%d", &id); cnt[id]++; } for(int i = 0; i <= cnt[1]; i++) dp[i] = 1; sum[0] = 1; int cur = cnt[1]; for(int i = 2; i <= T; i++) { cur += cnt[i]; for(int j = 1; j <= cur; j++) { sum[j] = (sum[j - 1] + dp[j]) % MOD; } for(int j = cur; j >= 1; j--) { low = j - cnt[i] - 1; if(low >= 0) dp[j] += sum[j - 1] - sum[low]; else dp[j] += sum[j - 1]; dp[j] = (dp[j] % MOD + MOD) % MOD; } } ll ans = 0; for(int i = S; i <= B; i++) { ans += dp[i]; } printf("%lld\n", ans % MOD); } return 0; }
//这一种是按多重背包的思想来写的
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 1e3 + 5; const int mod = 1e6; int a[maxm]; int dp[2][maxm * 100]; int cur, pre; int main() { int T, A, S, B; while(~scanf("%d%d%d%d", &T, &A, &S, &B)) { int x; memset(dp, 0, sizeof(dp)); memset(a, 0, sizeof(a)); while(A--) { scanf("%d", &x); a[x]++; // printf("%d\n", a[x]); } dp[0][0] = 1; for(int i = 1; i <= T; i++) { cur = i & 1; pre = (i - 1) & 1; memset(dp[cur], 0, sizeof(dp[cur])); for(int k = 0; k <= a[i]; k++) { for(int j = 0; j <= B; j++) { if(j >= k) dp[cur][j] = (dp[cur][j] + dp[pre][j - k]) % mod; } } } int res = 0; // cur = T & 1; for(int i = S; i <= B; i++) { res = (res + dp[cur][i]) % mod; // printf("%d\n", res); } printf("%d\n", res); } return 0; }