先是B题,https://nanti.jisuanke.com/t/31711。
题意:
王子要破除m条魔咒冲出城堡,城堡一共有n个房间,每个房间都有一个权值,最初他手上有权值K,他要从第一个房间开始走到第n个房间,每次他可以选择是否破除这个魔咒,如果他要破除这个魔咒,他会得到的权值是x op[j] a[i] ,x为当前的权值,op[j]是当前马上要破除的魔咒,a[i]是房间权值,魔咒是+-*/四种操作之一,他要出城堡,必须要破除所有的魔咒,魔咒的打破必须按顺序,房间也必须按顺序走,要你求一个最后能得到的最大的权值。
做法:
在队友的建议下,我用了两个dp,一个存储当前的最小值,一个存储最大值,如果碰到负的a[i],那么就用最小值去更新最大值,用最大值去更新最小值,因为可能会碰到出现两个负数可以相互抵消的,所以需要两个dp来维护。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=(int)1e9+7;
const int maxn=1005;
ll a[maxn],dpbig[maxn][10],dpsmall[maxn][10];
char op[maxn];
int n,m,k;
ll deal(ll x,char op,ll y){
if(op=='+') return x+y;
if(op=='-') return x-y;
if(op=='*') return x*y;
if(op=='/') return x/y;
}
int main(){
int t;
cin>>t;
while(t--){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
scanf("%s",op+1);
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
dpbig[i][j]=-1e18-5;
}
}
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
dpsmall[i][j]=1e18+5;
}
}
for(int i=0;i<=n;i++){
dpbig[i][0]=dpsmall[i][0]=k;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i,m);j++){
if(a[i]>0||op[j]=='-'||op[j]=='+'){
ll now=deal(dpbig[i-1][j-1],op[j],a[i]);
dpbig[i][j]=max(dpbig[i][j],max(dpbig[i-1][j],now));
now=deal(dpsmall[i-1][j-1],op[j],a[i]);
dpsmall[i][j]=min(dpsmall[i][j],min(dpsmall[i-1][j],now));
}
else {
ll now=deal(dpbig[i-1][j-1],op[j],a[i]);
dpsmall[i][j]=min(dpsmall[i][j],min(dpsmall[i-1][j],now));
now=deal(dpsmall[i-1][j-1],op[j],a[i]);
dpbig[i][j]=max(dpbig[i][j],max(dpbig[i-1][j],now));
}
}
}
ll ans=-1e18-5;
for(int i=1;i<=n;i++){
ans=dpbig[i][m];
}
printf("%lld\n",ans);
}
return 0;
}
L
题意:
上帝是个吃货,现在他要吃n个小时的食物分别是鱼(f),肉(m),巧克力(c),如果连续三个小时吃同一种食物,他会不开心。有两种吃法会中毒,一是连续三个小时吃不同的食物,第二个小时吃了巧克力(即fcm和mcf);二是三个小时中在第一个和第三个小时都吃了巧克力的情况下,第二个小时吃了肉或者鱼(即cfc,cmc)。问这n个小时有多少种吃法会让他开心并且不中毒。
做法:
第二个小时的时候是不会有不合法的情况的,我们会发现末尾结尾只有9种可能,cc,cm,cf,mf,mc,mm,fc,fm,ff。然后这些可能的后继状态又是可知的,即cc之后只能出现ccm和ccf,然后变出一个cm和cf,那么他们之间必然有联系。顺着这个思路想,可以找到递推关系。我将fm和mf分为一类(f(x)),将cc为一类(g(x)),将cm和cf分为一类(k(x)),把mm和ff分为一类(a(x)),再把mc和fc分为一类(z(x)),你会发现,每一类的后继状态一定是另一类下一状态的组成部分,比如刚刚枚举的cc,他变出cm和cf,所以g(x)*2就会是k(x+1)的组成部分之一。
然后我们就可以得到矩阵啦;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
struct matrix{
ll aim[8][8];
void clea(){
memset(aim,0,sizeof(aim));
for(int i=1;i<=5;i++)aim[i][i]=1;
}
};
matrix mul(matrix a,matrix b){
matrix ans;
memset(ans.aim,0,sizeof(ans.aim));
for(ll i=1;i<=5;i++){
for(ll j=1;j<=5;j++){
for(ll k=1;k<=5;k++){
ans.aim[i][j]=(ans.aim[i][j]+a.aim[i][k]*b.aim[k][j])%mod;
}
}
}
return ans;
}
void pr(matrix a){
for(int i=1;i<=5;i++){
for(int j=1;j<=5;j++)
printf("%d ",a.aim[i][j]);
cout<<endl;
}
}
matrix quick(matrix a,ll k){
matrix ans;
ans.clea();
while(k){
if(k&1){
ans=mul(ans,a);
}
a=mul(a,a);
k/=2;
}
return ans;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
ll n;
scanf("%lld",&n);
if(n==1){
printf("3\n");
continue;
}
if(n==2){
printf("9\n");
continue;
}
matrix a;
memset(a.aim,0,sizeof(a.aim));
a.aim[1][1]=a.aim[1][3]=a.aim[1][4]=a.aim[2][5]=a.aim[3][5]=1;
a.aim[4][1]=a.aim[4][3]=a.aim[5][1]=a.aim[5][4]=1; a.aim[3][2]=2;
a=quick(a,n-2);
matrix b;
memset(b.aim,0,sizeof(b.aim));
b.aim[1][1]=b.aim[3][1]=b.aim[4][1]=2;
b.aim[2][1]=1; b.aim[5][1]=2;
a=mul(a,b);
ll ans=0;
for(int i=1;i<=6;i++)
ans=(ans+a.aim[i][1])%mod;
printf("%lld\n",ans);
}
return 0;
}
最后来一个K题。
题意:
给你n种船,每种船有两种参数,代表一共有条载重量为v的船。现在有q个询问,问给你s重量的货物,你能找到多少种不同的载法(注意:每条船使用就必须满载,如果相同种类的船用的数量都一样那么算是同一种方法)。
做法:
把数量用二进制展开,比如2的3次-1,就可以拆成1个2个和4个,就能够代表所有的容量了,这样的话最多只有20*20一共400个包,做一次01背包即可,非常方便的想法,队友在比赛的时候想到了,然而我完全没懂当时。。现在来补题了。。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=(int)1e9+7;
int dp[500][10005],wei[1005],n,q,num;
int main(){
int t;
cin>>t;
while(t--){
num=0;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
int c,w;
scanf("%d%d",&w,&c);
int tmp=1;
while(c--){
wei[++num]=tmp*w;
tmp*=2;
}
}
dp[0][0]=1;
for(int i=1;i<=num;i++){
for(int j=0;j<=10000;j++){
if(j<wei[i]) dp[i][j]=dp[i-1][j];
else {
dp[i][j]=(dp[i-1][j]+dp[i-1][j-wei[i]])%mod;
}
}
}
while(q--){
int aim;
scanf("%d",&aim);
printf("%d\n",dp[num][aim]);
}
}
return 0;
}