B-01背包求方案数
写法一:二维
和第一场的A题同种做法。
注意:
- d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i个西瓜,能够拼凑出西瓜重量为 j j j的最大方案数,注意两维数组根据题目数据范围开得大一点!!(一开始样例都过但是答案没过就卡在这了)
- d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1
- 三种情况得到三种递推式子
(1) 第i个西瓜不选, d p [ i ] [ j ] + = d p [ i − 1 ] [ j ] dp[i][j]+=dp[i-1][j] dp[i][j]+=dp[i−1][j]
(2)选定第 i i i个西瓜,影响的是 j + w [ i ] j+w[i] j+w[i]和 j + w [ i ] / 2 j+w[i]/2 j+w[i]/2
第一种: d p [ i ] [ j + w [ i ] ] + = d p [ i − 1 ] [ j ] dp[i][j+w[i]] +=dp[i-1][j] dp[i][j+w[i]]+=dp[i−1][j]
第二种: d p [ i ] [ j + w [ i ] / 2 ] + = d p [ i − 1 ] [ j ] dp[i][j+w[i]/2] +=dp[i-1][j] dp[i][j+w[i]/2]+=dp[i−1][j]
#include<iostream>
using namespace std;
const int N=1100,mod=1e9+7;
long long dp[N][5000],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
dp[i][j+w[i]]=(dp[i][j+w[i]]+dp[i-1][j])%mod;
dp[i][j+w[i]/2]=(dp[i][j+w[i]/2]+dp[i-1][j])%mod;
}
for(int i=1;i<=m;i++) cout<<dp[n][i]%mod<<" ";
return 0;
}
写法二: 一维滚动数组
d
p
[
i
]
[
j
]
=
(
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
d
p
[
i
−
1
]
[
j
−
w
[
i
]
/
2
]
)
dp[i][j]=(dp[i-1][j]+dp[i-1][j-w[i]]+dp[i-1][j-w[i]/2])
dp[i][j]=(dp[i−1][j]+dp[i−1][j−w[i]]+dp[i−1][j−w[i]/2])%
m
o
d
mod
mod
可以转化为滚动数组,
f
o
r
for
for循环第二层
j
j
j要逆序写,
d
p
[
j
]
=
(
d
p
[
j
]
+
d
p
[
j
−
w
[
i
]
]
+
d
p
[
j
−
w
[
i
]
/
2
]
)
dp[j]=(dp[j]+dp[j-w[i]]+dp[j-w[i]/2])
dp[j]=(dp[j]+dp[j−w[i]]+dp[j−w[i]/2])%
m
o
d
mod
mod,
加
i
f
if
if判读:
i
f
(
j
≥
w
[
i
]
)
,
d
p
[
j
]
=
(
d
p
[
j
]
+
d
p
[
j
−
w
[
i
]
]
+
d
p
[
j
−
w
[
i
]
/
2
]
)
if(j≥w[i]),dp[j]=(dp[j]+dp[j-w[i]]+dp[j-w[i]/2])
if(j≥w[i]),dp[j]=(dp[j]+dp[j−w[i]]+dp[j−w[i]/2])%
m
o
d
mod
mod
i
f
(
j
≥
w
[
i
]
/
2
)
,
d
p
[
j
]
=
(
d
p
[
j
]
+
d
p
[
j
−
w
[
i
]
]
/
2
)
if(j≥w[i]/2),dp[j]=(dp[j]+dp[j-w[i]]/2)
if(j≥w[i]/2),dp[j]=(dp[j]+dp[j−w[i]]/2)%
m
o
d
mod
mod
#include<iostream>
using namespace std;
const int N=1100,mod=1e9+7;
long long dp[N][5000],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
dp[i][j+w[i]]=(dp[i][j+w[i]]+dp[i-1][j])%mod;
dp[i][j+w[i]/2]=(dp[i][j+w[i]/2]+dp[i-1][j])%mod;
}
for(int i=1;i<=m;i++) cout<<dp[n][i]%mod<<" ";
return 0;
}
C—背包/(逆向思维)已知方案数还原数据
- 如果发现 d p [ 1 ] = 1 dp[1]=1 dp[1]=1,因为西瓜的质量只能是偶数,那么重量 1 1 1一定是由重量为 2 2 2的西瓜切一半得到的。所以一定存在重量为 2 2 2的西瓜,我们把重量为 2 2 2的西瓜产生的所有方案数去除,如果去除后发现 d p [ 2 ] ≠ 0 dp[2]≠0 dp[2]=0,说明这个重量 2 2 2一定是由重量为4的西瓜切一半得到的,类推。
- 为什么当发现
d
p
[
i
]
≠
0
dp[i]≠0
dp[i]=0时认为
i
i
i一定是由重量为
i
∗
2
i*2
i∗2的西瓜切一半得到的,而不是重量为
i
i
i的整个西瓜得到的?
因为我们从前往后处理,如果存在当前重量 i i i,因为可以买半个,那么一定会影响到前面 i / 2 i/2 i/2的值导致错误,所以每个重量都认为从半个西瓜得到的,从而向后递推。 - 重量
j
j
j的方案数方程为:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
d
p
[
i
−
1
]
[
j
−
w
[
i
]
/
2
]
。
dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]]+dp[i-1][j-w[i]/2]。
dp[i][j]=dp[i−1][j]+dp[i−1][j−w[i]]+dp[i−1][j−w[i]/2]。即:
d
p
[
j
]
+
=
d
p
[
j
−
w
[
i
]
]
+
d
p
[
j
−
w
[
i
]
/
2
]
dp[j]+=dp[j-w[i]]+dp[j-w[i]/2]
dp[j]+=dp[j−w[i]]+dp[j−w[i]/2]
所以如果存在重量 j j j,消除j产生的方案数,即 d p [ j + w [ i ] ] − = d p [ j ] , d p [ i + w [ i ] / 2 ] − = d p [ j ] dp[j+w[i]]-=dp[j],dp[i+w[i]/2]-=dp[j] dp[j+w[i]]−=dp[j],dp[i+w[i]/2]−=dp[j]。
本题中,如果 d p [ i ] ≠ 0 dp[i]≠0 dp[i]=0,则一定存在 i ∗ 2 i*2 i∗2,则 d p [ j + 2 ∗ i ] − = d p [ j ] , d p [ j + i ] − = d p [ j ] dp[j+2*i]-=dp[j],dp[j+i]-=dp[j] dp[j+2∗i]−=dp[j],dp[j+i]−=dp[j]
#include<iostream>
#include<vector>
using namespace std;
const int N=10010,mod=1e9+7;
int dp[N];
vector<int>ans;
int main()
{
int m;
cin>>m;
for(int i=1;i<=m;i++) cin>>dp[i];
dp[0]=1;
for(int i=1;i<=m;i++)
while(dp[i])
{
ans.push_back(i*2);
for(int j=i;j<=m;j++)
{
if(j>=i) dp[j]-=dp[j-i];
if(j>=2*i) dp[j]-=dp[j-2*i];
while(dp[j]<0) dp[j]+=mod;
}
}
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();i++)
cout<<ans[i]<<" ";
return 0;
}
L-模拟/标记多元组的种类及个数
- 不同字符串代表着不同的种类,将字符串映射为整数标记—— m a p < s t r i n g , i n t > m p 1 map<string,int>mp1 map<string,int>mp1
- 多元组是一个整数集合,相当于 v e c t o r < i n t > vector<int> vector<int>, m a p < v e c t o r < i n t > , i n t > m p 2 map<vector<int>,int>mp2 map<vector<int>,int>mp2标记不同多元组的个数, m p 2. s i z e ( ) mp2.size() mp2.size()就是多元组的种类。
#include<vector>
#include<map>
#include<iostream>
using namespace std;
const int N=1100;
int a[N][N];
map<string,int>mp1;
map<vector<int>,int>mp2;
vector<int>p;
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++)
{
string s;
cin>>s;
mp1[s]=i;
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>a[i][j];
getchar();
string str;
getline(cin,str);
int j=36;
for(int i=37;i<str.size();i++)
if(str[i]==','||str[i]==';')
{
string ss=str.substr(j,i-j);
p.push_back(mp1[ss]);
j=i+1;
}
for(int i=0;i<n;i++)
{
vector<int>k;
for(auto j:p)
k.push_back(a[i][j]);
mp2[k]++;
}
cout<<mp2.size()<<endl;
for(auto i:mp2)
cout<<i.second<<" ";
return 0;
}
I-智乃的密码
方法1:尺取法/双指针
关键是要找到单调性,当右端点符合条件时,后面所有的点都可以符合条件了,并且此后左端点向后移动时,右端点也只会在上次移动的基础上继续向右移动,不会向左退。
- 先将串中每一位字符分类
- 每次固定左端点
i
i
i,找到第一个符合条件的右端点
j
j
j,就可以开始计算长度了,
(
j
,
n
)
(j,n)
(j,n)区间和
(
i
+
l
,
i
+
r
)
(i+l,i+r)
(i+l,i+r)区间取交集。
#include<iostream>
using namespace std;
const int N=1e5+100;
int a[N];
long long b[5]; //存储4类字符各有多少个
int main()
{
int n,l,r;
cin>>n>>l>>r;
for(int i=0;i<n;i++)
{
char c;
cin>>c;
if(c>='A'&&c<='Z') a[i]=1;
else if(c>='a'&&c<='z') a[i]=2;
else if(c>='0'&&c<='9') a[i]=3;
else a[i]=4;
}
int j=0;
long long ans=0;
for(int i=0;i<n;i++) //固定左端点
{
while(j<=n&&((b[1]>0)+(b[2]>0)+(b[3]>0)+(b[4]>0))<3) //右端点右移
{
b[a[j]]++;
j++;
}
int x=max(j,i+l);
int y=min(n,i+r);
if(x<=y) ans+=y-x+1;
b[a[i]]--;
}
cout<<ans<<endl;
return 0;
}
方法2:前缀和+二分
- 因为一共有四种类型的字符,建立四个前缀和数组分别存储四种类型字符的数目
- 固定左端点 i i i,在 ( i + l , i + r ) (i+l,i+r) (i+l,i+r)区间内找到符合条件的最右端点,得到答案区间
#include<iostream>
using namespace std;
typedef long long LL;
const int N=1e5+100;
int a[N],b[N],c[N],d[N];
char s;
bool check(int l,int r)
{
int x=a[r]-a[l-1],y=b[r]-b[l-1],z=c[r]-c[l-1],p=d[r]-d[l-1];
if(((x>=1)+(y>=1)+(z>=1)+(p>=1))>=3) return 1;
return 0;
}
int main()
{
int n,l,r;
cin>>n>>l>>r;
for(int i=1;i<=n;i++)
{
cin>>s;
if(s>='A'&&s<='Z') a[i]++;
else if(s>='a'&&s<='z') b[i]++;
else if(s>='0'&&s<='9') c[i]++;
else d[i]++;
a[i]+=a[i-1],b[i]+=b[i-1],c[i]+=c[i-1],d[i]+=d[i-1];
}
LL ans=0;
for(int i=1;i+l-1<=n;i++)
{
int ll=min(i+l-1,n),rr=min(i+r-1,n); //区间(i+l-1,i+r-1)
int y=rr; //答案区间右边界
while(ll<rr)
{
int mid=(ll+rr)/2;
if(check(i,mid)) rr=mid;
else ll=mid+1;
}
if(check(i,ll)) //注意二分之后的特判
ans+=y-ll+1;
}
cout<<ans<<endl;
return 0;
}
E-模拟
注意大数取模只能从最高位到最低位模拟,边乘边取模。不能从最低位从右向左
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int mod=1e9+7,N=1e5+100;
typedef long long LL;
int color[N],n,m,k;
char s[N];
bool cmp(char a,char b)
{
return a>b;
}
void put()
{
LL ans=0;
for(int i=0;i<n;i++)
{
ans=(ans*10+s[i]-'0')%mod; //大数取模
}
cout<<ans%mod<<endl;
}
void work()
{
for(int i=0;i<n-1;i++)
{
if(color[i]==color[i+1])
{
int j=i+1;
while(color[j]==color[i]&&j<n) j++;
sort(s+i,s+j,cmp);
i=j-1;
}
}
// cout<<s<<endl;
}
int main()
{
cin>>n>>m>>k;
cin>>s;
for(int i=0;i<n;i++) cin>>color[i];
work();
put();
// cout<<s<<endl;
while(k--)
{
int a,b;
cin>>a>>b;
for(int i=0;i<n;i++)
if(color[i]==a)
color[i]=b;
work();
put();
}
return 0;
}