括号序列
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
string s;
int cntl, cntr, cnt;
ll dp[5010][5010], pre[5010], minn[5010];//minn是i号前至少需要的左括号个数,pre是i号前所填左括号小于等于的个数
//函数参数(需要填充的对应括号数,是否为左括号填充)
ll solve(int ans, bool isl){
if(ans == 0) return 1;
//初始化
memset(dp,0,sizeof(dp));
memset(pre,0,sizeof(pre));
memset(minn,0,sizeof(minn));
//括号反转,方向反且顺序反
if(!isl){//
for(int i = 0; s[i]; i++){
if(s[i] == '(') s[i] = ')';
else s[i] = '(';
}
reverse(s.begin(), s.end());
}
//计算第pos个左(右)括号前最少需要的右(左)括号数minn[pos]
int pos1 = 0, pos2 = 0;
for(int i = 0; s[i]; i++){//初始化minn
if(s[i] == ')')
minn[++pos1] = pos2;//minn[pos]为前面左括号个数
else
pos2++;
}
//dp,pre初始化
if(minn[1] > 0) pre[0] = dp[1][0] = 1;//第一个前就要填,更新第一个前填0个左括号的方案
for(int i = 1; i <= ans; i++){
dp[1][i] = 1; pre[i] = pre[i-1] + 1;//第一个前填的各种方案
//多填一个多出一种方案
}
//第一维:从2到所含有的最大对应括号数
for(int i = 2; i <= pos1; i++){//pos1右括号个数
//第二维
//小于最小填充数部分前缀和更新为0
for(int j = 0; j < i-minn[i]; j++) pre[j] = 0;
for(int j = i-minn[i]; j <= ans; j++){//减去i号前必填的左括号,剩下要填的左括号
dp[i][j] = pre[j];//i号前填j个左括号的方案数为填数小于等于j个的方案数
printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
//在填充范围内部分更新前缀和
if(j - 1 < 0) pre[j] = dp[i][j];//填左括号为0的方案数为i号前填j个左括号的方案数
else pre[j] = (pre[j-1] + dp[i][j]) % mod;//填左括号不为0的情况是将i号前填j个和填j-1的方案数相加
printf("pre[%d]=%d\n",j,pre[j]);
}
}
return dp[pos1][ans];
}
int main(void) {
cin >> s;
//计算所需括号数量
cnt = cntl = cntr = 0;
for (int i = 0; s[i]; i++) {
if (s[i] == '(') cnt++;
else cnt--;
if (cnt < 0) {
cntl++;
cnt = 0;
}
}
cntr = cnt;
//调用函数输出结果
cout << solve(cntl, true) * solve(cntr, false) % mod;
return 0;
}
背包问题
二维01
#include<bits/stdc++.h>
int v[1000],w[1000],f[100][1000];// f[i][j], j体积下前i个物品的最大价值
int main(void){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<v[i])f[i][j]=f[i-1][j];//装不下不装,防止下一步越界
else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[n][m];
}
一维01
#include<bits/stdc++.h>
#include<iostream>
using namespace std;
int v,w,f[1000],m,n;//背包体积为i时的还能装下的最大价值
int main(void){
cin>>n>>m;
memset(f,0,sizeof(f));
for(int i=0;i<n;i++){
cin>>v>>w;
for(int j=m;j>=v;j--){
f[j]=max(f[j],f[j-v]+w);//第一个物品的放入更新了各种
printf("f[%d]:%d",j,f[j]);
cout<<" "<<f[j-v]+w<<" ";
}
}
cout<<f[m];
}
完全背包问题
想法:用while(j - v[i] >= 0)循环可以降低为一维数组
#include<bits/stdc++.h>
using namespace std;
int v[1000], w[1000], f[1001][1001]; // f[i][j], j体积下前i个物品的最大价值
int main(void) {
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= m; j++) {
f[i][j] = f[i-1][j];
if(j - v[i] >= 0)//取代了物品数量挨个试的循环
f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);//状态转移,放入两个物品即放入一个再更新
cout << "f[" << i << "][" << j << "]: " << f[i][j] << endl;
}
}
cout << f[n][m];
return 0;
}
多重背包问题
在01基础上增补
for(int i=0;i<n;i++){
cin>>a>>b>>c;
for(int j=0;j<c;j++){
v[cnt]=a;
w[cnt]=b;
cnt++;
}
}
#include<bit/stdc++.h>
#include<iostream>
using namespace std;
int v[100],w[100],f[1000],m,n,a,b,c;//背包体积为i时的还能装下的最大价值
int main(void){
cin>>n>>m;
memset(f,0,sizeof(f));
int cnt=0;
for(int i=0;i<n;i++){
cin>>a>>b>>c;
for(int j=0;j<c;j++){
v[cnt]=a;
w[cnt]=b;
cnt++;
}
}
for(int i=0;i<cnt;i++)
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);//第一个物品的放入更新了各种
printf("f[%d]:%d",j,f[j]);
cout<<" "<<f[j-v[i]]+w[i]<<" ";
}
cout<<f[m];
}
二进制优化
原因在于分成1,2,4...个打包,每个数都可以通过不同的打包组合拼成
#include <bits/stdc++.h>
using namespace std;
const int N = 11 * 1000 + 10, M = 2010;
int v[N], w[N];
int f[M];
int main()
{
int n, m;
scanf("%d %d", &n, &m);
int cnt = 0; // 将物品重新分组后的顺序
for (int i = 1; i <= n; i ++)
{
int a, b, s; // a 体积, b 价值, s 每种物品的个数
scanf("%d %d %d", &a, &b, &s);
int k = 1; // 二进制拆分 打包时每组中有 k 个同种物品
while (k <= s) // 即y总说的: 最后一组的物品个数 < 2^(n+1) 1 2 4 8 16 ... 2^n 2^(n+1)
{
cnt ++;
v[cnt] = a * k; // 每组的体积
w[cnt] = b * k; // 每组的价值
s -= k;
k *= 2; // 注意是 k * 2,每次增长一倍,不是k * k
}
if (s > 0) // 二进制拆分完之后 剩下的物品个数分为新的一组
{
cnt ++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt; // 所有的组数即为 01背包中的物品个数
// 写01背包模板
for (int i = 1; i <= n; i ++)
for (int j = m; j >= v[i]; j --)
f[j] = max(f[j], f[j - v[i]] + w[i]);
printf("%d", f[m]);
return 0;
}
分组背包问题
想法:换成一维数组,输入s在i循环内,与01问题写法相同。因为 f[j-v[k]]继承的一定是上一组的值(由体积决定)
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N][N]; //只从前i组物品中选,当前体积小于等于j的最大值
int v[N][N],w[N][N],s[N]; //v为体积,w为价值,s代表第i组物品的个数
int n,m,k;
int main(void){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
for(int j=0;j<s[i];j++){
cin>>v[i][j]>>w[i][j]; //读入
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j]; //不选 不选表示不选第 i 组物品的所有物品,只从前 i−1 组物品里面选
for(int k=0;k<s[i];k++){
if(j>=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout<<f[n][m]<<endl;
}
质数判断
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
if(gcd(a,b)==1)printf("yes");
回路计数
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[1<<21][25];//dp[ i ][ j ]表示从状态 i 到 j 的路径数
//1<<21记录这些楼有没有走过
inline int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
int main(void) {
ll res = 0;
dp[1][0] = 1;//初始化
for (int i = 1; i < (1 << 21); i++) {//
for (int j = 0; j < 21; j++) {
if (!(i >> j & 1)) continue;//i的第j位为1,走过了
for (int k = 0; k < 21; k++) {
if ((i >> k & 1) || gcd(j + 1, k + 1) != 1) continue;
//i的第k位为1,或互质,走不通
dp[i + (1 << k)][k] += dp[i][j];//第k楼标记走过,加上原来的路径
}
}
}
for (int i = 0; i < 21; i++)
res += dp[(1 << 21) - 1][i];//i是最后终止的楼
cout << res;
return 0;
}