这次比赛名为欢乐赛,实际上毒瘤得一批。
时间依然不合理,但不能怪我没分配好啊,每道题一推就是1h,还分配啥呀。
_ (:3」∠) _.真的没办法啊,dp方程又是改又是想的,一来二去耗费的时间也是可以,在草稿纸上写一写,一看时间半小时过去了,敲完10min,又调个半小时 (- _ -)
T1
题目描述:
1.接苹果
(bcatch.pas/c/cpp)
【问题描述】
奶牛喜欢吃苹果。约翰有两棵苹果树,有 N 只苹果会从树上陆续落下。如果掉苹果的时候,贝西在那棵树下,她就能接住苹果。贝西一开始在第一棵树下。在苹果掉落之前,她有足够的时间来回走动,但她很懒,最多只愿意移动 K 次。请计算一下她最多可以接住几只苹果。
【输入】
• 第一行:两个整数 N 和 K,1 ≤ N ≤ 1000; 1 ≤ K ≤ 30
• 第 i + 1 行有一个整数 Ti,表示第 i 只苹果从哪棵树上掉落,1 表示从第一棵树,2 表示从第二棵树
【输出】
单个整数:表示能接住的最大苹果数量
【输入输出样例1】
bcatch. in | bcatch. out |
---|---|
7 2 | 6 |
2 | |
1 | |
1 | |
2 | |
2 | |
1 | |
1 |
解释
先待在第一棵树下接住两个,然后移动到第二棵树下接住两个,再返回第一棵树接住最后两个
省略一堆奇奇怪怪的背景,它的实质就是一个指针在来回指的过程。
那么很显然地可以以第几个苹果为阶段,设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为前i个苹果,花费了j次移动机会,且此时在k处时(k=1、2) 的最多苹果数。
易得方程:
- f [ i ] [ j ] [ 1 ] = m a x ( f [ i − 1 ] [ j − 1 ] [ 2 ] , f [ i − 1 ] [ j ] [ 1 ] ) + 1 ; f[i][j][1]=max(f[i-1][j-1][2],f[i-1][j][1])+1; f[i][j][1]=max(f[i−1][j−1][2],f[i−1][j][1])+1;
-
f
[
i
]
[
j
]
[
2
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
1
]
[
1
]
,
f
[
i
−
1
]
[
j
]
[
2
]
)
;
f[i][j][2]=max(f[i-1][j-1][1],f[i-1][j][2]);
f[i][j][2]=max(f[i−1][j−1][1],f[i−1][j][2]);
( a [ i ] = = 1 ) (a[i]==1) (a[i]==1) - f [ i ] [ j ] [ 1 ] = m a x ( f [ i − 1 ] [ j − 1 ] [ 2 ] , f [ i − 1 ] [ j ] [ 1 ] ) ; f[i][j][1]=max(f[i-1][j-1][2],f[i-1][j][1]); f[i][j][1]=max(f[i−1][j−1][2],f[i−1][j][1]);
-
f
[
i
]
[
j
]
[
2
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
1
]
[
1
]
,
f
[
i
−
1
]
[
j
]
[
2
]
)
+
1
;
f[i][j][2]=max(f[i-1][j-1][1],f[i-1][j][2])+1;
f[i][j][2]=max(f[i−1][j−1][1],f[i−1][j][2])+1;
( a [ i ] = = 2 ) (a[i]==2) (a[i]==2)
这题算是所有题里我想出正解最容易的一道题。其实它也十分显然,因为本着求什么设什么的态度,可以直接想到这个三维状态。
简单易懂的转移。
代码:
#include<bits/stdc++.h>
#define N 1010
#define K 35
using namespace std;
int n,k;
int a[N]={};
int f[N][3][K]={};
//#define getchar() (S==T&&(T=(S=BB)+fread(BB,1,1<<15,stdin),S==T)?EOF:*S++)
char BB[1<<15],*S=BB,*T=BB;
int read()
{
int f=0,w=1; char c;
while(c=getchar(),c<=47||c>=58)if(c='-')w=-1;
f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
return f*w;
}
int main()
{
freopen("bcatch.in","r",stdin);
freopen("bcatch.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
memset(f,-0x3f,sizeof(f));
f[0][1][1]=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=k+1;++j){//偏移量,为了防止数组越界RE
if(a[i]==1){
f[i][1][j]=max(f[i-1][1][j],f[i-1][2][j-1])+1;
f[i][2][j]=max(f[i-1][2][j],f[i-1][1][j-1]);
}
if(a[i]==2){
f[i][1][j]=max(f[i-1][1][j],f[i-1][2][j-1]);
f[i][2][j]=max(f[i-1][2][j],f[i-1][1][j-1])+1;
}
}
printf("%d",max(f[n][1][k+1],f[n][2][k+1]));//同偏移量1
return 0;
}
T2
T2我推了好久好久……(省略无数个)
2.奶牛飞盘队
(fristeam.pas/c/cpp)
【问题描述】
农夫顿因开始玩飞盘之后,约翰也打算让奶牛们享受飞盘的乐趣.他要组建一只奶牛飞盘队.
他的N(1≤N≤2000)只奶牛,每只部有一个飞盘水准指数Ri(1≤Ri≤100000).约翰要选出1只或多于1只奶牛来参加他的飞盘队.
约翰比较迷信,他的幸运数字是F,所以他要求队伍的总能力必须是F的倍数。请帮他算一下,符合这个要求的队伍组合有多少?由于这个数字很大,只要输出答案除以10^8 的余数就可以了。
【输入】
第一行:两个用空格分开的整数:N和F,1 ≤ N ≤ 2000,1 ≤ F ≤ 1000
第二行到N + 1行:第i + 1行有一个整数R i ,表示第i头奶牛的能力,1 ≤ R i ≤ 10^5
【输出】
【输入输出样例1】
fristeam.in | fristeam.out |
---|---|
4 5 | 3 |
1 | |
2 | |
8 | |
2 |
FJ has four cows whose ratings are 1, 2, 8, and 2. He will only accept a team whose rating sum is a multiple of 5.
FJ can pair the 8 and either of the 2’s (8 + 2 = 10), or he can use both 2’s and the 1 (2 + 2 + 1 = 5).
花了大约1h,一开始没思路,后来想到了两种方式。
- f [ i ] [ j ] f[i][j] f[i][j]表示前i数模j为零方案数
- f [ i ] [ j ] f[i][j] f[i][j]表示前i个数模f为j所能的方案数。
在一番排除之后,发现第一种无法有效转移,排除之后开始想第二种。
loding————————————————
- 8:51(保持微笑)
- 9:20(笑容渐渐模糊)
- 9:30(笑容缓缓消失)
- 10:00( _ (:3」∠) _)
原本在九点半的时候就不想调了,但好像已经想出转移了,删来改去,又tm耗到10点了(=_=),终究做出来了,于是火速开始往下看。
咳咳,不废话了,讲一下吧:
因为是倍数,所以我们可以用%。
从上一个转移过来,直接减会有负数,所以就加一个k*f(k为大一些的随便一个数,如1999999)。
-
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j ] ; f [ i ] [ j ] % = m o d ; f[i][j]=f[i-1][j]+f[i][j];f[i][j] \% = mod; f[i][j]=f[i−1][j]+f[i][j];f[i][j]%=mod;
-
f [ i ] [ j ] + = f [ i − 1 ] [ ( j + 199999 ∗ F − a [ i ] ) % F ] ; f [ i ] [ j ] % = m o d ; f[i][j]+=f[i-1][(j+199999*F-a[i])\%F];f[i][j]\%=mod; f[i][j]+=f[i−1][(j+199999∗F−a[i])%F];f[i][j]%=mod;
分别表示不用到啊 a [ i ] a[i] a[i]与用到 a [ i ] a[i] a[i]。
贴上代码,细节详见下面:
#include<bits/stdc++.h>
#define N 2010
#define mod 100000000
using namespace std;
int n,F;
int a[N]={};
int f[N][1010]={};
char BB[1<<15],*S=BB,*T=BB;
int read()
{
int f=0,w=1; char c;
while(c=getchar(),c<=47||c>=58)if(c='-')w=-1;
f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
return f*w;
}
int fa[N]={};
bool in[N]={};
int main()
{
// freopen("fristeam.in","r",stdin);
// freopen("fristeam.out","w",stdout);
n=read();F=read();
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int j=0;j<F;++j){f[1][j]=(a[1]%F==j);}
for(int i=2;i<=n;++i){
for(int j=0;j<F;++j)f[i][j]=(a[i]%F==j);
//注意这个初始化,表示至少每个数a[i]能凑成(a[i]%F)
for(int j=0;j<F;++j){
f[i][j]=f[i-1][j]+f[i][j];f[i][j]%=mod;
f[i][j]+=f[i-1][(j+199999*F-a[i])%F];f[i][j]%=mod;
}
}
printf("%d",f[n][0]%mod);
return 0;
}
原本我的代码有n行,去掉一些注释和被否决的写法后只剩上面这么点了,唉。
这可是我1.5h的心血啊,然后老师就轻描淡写地过去了。。。
不过,这场考试真正精彩的还在下面。
T3
干嘛?精彩的不是这道。这道我就hehe了。
从第一眼看到,原本以为多几个循环应该可以水到好多分。
——>(–_%)我还是太天真了。
股票市场
(stock.pas/c/cpp)
【问题描述】
【输入输出样例1】
stock .in | stock. out |
---|---|
2 3 10 | 24 |
10 15 15 | |
13 11 20 |
T3我不会,后来讲 的时候才懂。
这实际是一个完全背包,因为每个股票都可以无限制买进卖出。
我们关心的只是获得更大收益。所以我们对于每一天是一个完全背包,总容量为剩余资金,物体价格为每股当天价格,价值为第二天价格-当天价格。
f [ i ] [ j ] 表 示 前 i 个 股 票 使 用 资 金 j 能 获 得 的 最 大 收 益 f[i][j]表示前i个股票使用资金j能获得的最大收益 f[i][j]表示前i个股票使用资金j能获得的最大收益
- f [ i ] [ j ] = f [ i − 1 ] [ j − a [ i ] ] + a [ i + 1 ] − a [ i ] ; f[i][j]=f[i-1][j-a[i]]+a[i+1]-a[i]; f[i][j]=f[i−1][j−a[i]]+a[i+1]−a[i];
显然可以简化成一维;
- f [ j ] = m a x ( f [ j ] , f [ j − a [ i ] [ k ] ] + a [ i ] [ k + 1 ] − a [ i ] [ k ] ) ; f[j]=max(f[j],f[j-a[i][k]]+a[i][k+1]-a[i][k]); f[j]=max(f[j],f[j−a[i][k]]+a[i][k+1]−a[i][k]);
hehe反正我是想不到这玩意是完全背包。
老刘说这题难度和摆渡车差不多。我:……
贴个代码,不要被吓到:
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<bits/stdc++.h>
using namespace std;
int n,m,d;
int a[55][20]={},f[550000]={};
int main()
{
// freopen("stock.in","r",stdin);
// freopen("stock.out","w",stdout);
scanf("%d%d%d",&n,&m,&d);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
for(int i=1;i<m;++i){
memset(f,0,sizeof(f));
for(int j=1;j<=n;++j)
for(int k=a[j][i];k<=d;++k)
f[k]=max(f[k],f[k-a[j][i]]+a[j][i+1]-a[j][i]);
d+=f[d];
}
printf("%d",d);
return 0;
}
没办法,不开优化有一个点被卡常,我也甚是无奈啊。
算了,下一个;
好戏开场
T4&T5
哈哈,好嘚瑟啊,知道为什么T4、T5一起讲么,因为——两道题题目一模一样,就是数据不同,但是dp思维难度巨大,还被我用贪心水过去了,内心的嘚瑟无法比拟。
更重要的是,当时是因为没时间了,为了骗点分才用贪心的,结果感人。
题目描述:
248
(248.pas/c/cpp)
【问题描述】
给定一个1*N(2<=N<=248) 的地图,在里面玩2048,每次可以合并相邻两个(数值范围1-40),问最大能合出多少。注意合并后的数值并非加倍而是+1,例如2与2合并后的数值为3。
【输入】
第一行,一个整数N
接下来N行,每行一个整数,表示第i个数字
【输出】
所求的答案
【输入输出样例1】
248. in | 248. out |
---|---|
4 | 3 |
1 | |
1 | |
1 | |
2 |
先合并第2个和第3个1,得到1 2 2 。再合并两个2,得到3.
T5就是T4数据改n<=262144。
贪心方法十分简单。
正着反着扫两边,把能合的全部合了,把两次最大值取max即可。
那么为什么可以这样贪呢,其实这样是有漏洞的,但是要卡掉他数据很难构到,除非手推。
正常情况下,对于任意连续串:
- 如“11111”;
- 我们发现若把中间的合并,显然不是最优的,如“1211”;这会造成一个断层,无法有效利用。
- 所以我们就可以从两边走。
- 如“2111”“1112”;
于是就发现了这个方法,正着反着扫一遍即可取到最大值。
提一句:什么数据可以卡掉呢?
如:24 2 1 2 9 8 7 6 6 6 7 8 9 9 8 7 5 5 6 7 8 9 2 1 2;
这组数据两边都是陷阱,会造成干扰。
其实可以优化,只需在一边循环中同时将两个栈的值合并一下并计入最大值即可避免此情况,所以这个贪心是正确的吧
这种方式时间复杂度 O ( n ) O(n) O(n),显然效率极高。
贴上代码,有时间我就去优化一下吧:
#include<bits/stdc++.h>
using namespace std;
int n,a[300]={},st1[1010]={},st[1010]={};
long long s=0,maxn=0;
int len=0,len1=0;
int main()
{
// freopen("248.in","r",stdin);
// freopen("248.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);maxn=max(maxn,(long long)a[i]);
}
for(int i=1;i<=n;++i){
st[++len]=a[i];
while(st[len]==st[len-1])len-=2,st[++len]=st[len+1]+1,maxn=max(maxn,(long long)st[len]);
}
for(int i=n;i>=1;--i){
st1[++len1]=a[i];
while(st1[len1]==st1[len1-1])len1-=2,st1[++len1]=st1[len1+1]+1,maxn=max(maxn,(long long)st1[len1]);
}
printf("%d",maxn);
return 0;
}
T5就是这个代码把n改下,同样可以过。
那么正解是什么呢?T4区间动归,T5是一个很神奇的状态,无法描述的dp。
此处不再赘述,主要讲我自己的方法。
我遁。