目录
一.题目报告
赛中第一题AC,第二题5分,第三题20分,第四题0分,赛后全部AC。
二.赛中情况
第一题贪心AC,第二题模拟了很长时间最后样例对了但5分,第三题背包20分,第四题bfs运行错误0分。
三.解题报告
T1.牛奶(milk)
题目情况
赛中AC。
题目大意
给定牛奶种类n和需要的箱数m,给出每种牛奶的箱数 a[i] 和每箱需要的钱数 b[i],求买到足够的牛奶箱数所需的最少钱数。
题目解析
将箱数和钱数排序,每次都买花费最少的牛奶,直到买到m箱。
题目正解
#include<bits/stdc++.h>
using namespace std;
struct str{
long long a,b;
}s[100001];
long long n,m,cnt;
bool cmp(str x,str y){
return x.b<y.b;
}
int main(){
freopen("milk.in","r",stdin);
freopen("milk.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&s[i].a,&s[i].b);
}
sort(s+1,s+n+1,cmp);
for(int i=1;i<=n;i++){
if(m>s[i].a){
m-=s[i].a;
cnt+=s[i].a*s[i].b;
}
else{
cnt+=m*s[i].b;
m=0;
break;
}
}
printf("%lld",cnt);
fclose(stdin);
fclose(stdout);
return 0;
}
T2.树组(traary)
题目情况
赛中5分,赛后AC。
题目大意
n棵树苗,开始高度为零,每天加一,有m次操作,每次操作有三种选择:
op=1:选择某棵树 x 对其施展魔法,该效果持续 k 天(包括当天)。拥有魔法效果的树每天晚上会额外生长 1 单位高度。若施展时该树已经存在魔法效果,则覆盖原来的魔法效果(也就是取消原来的魔法效果,加上这次的魔法效果)。
op=2:选择取消某棵树 x 的魔法效果,可能会对没有施加魔法的树进行操作。
op=3:求该天某棵树 x 的高度。
题目解析
赛中思路:模拟,天数循环。
正解:p:记录第i棵树最近一次施法时间。如果当前要对i施加魔法,那么上一次施法时间就是pi ,当前时间是枚举到的时间 t,方便判断 t和 pi之差是否已经超过k ,超过 k表示上次施法效果已经没有了,不超过可以继续更新施法时间,更新wi 。
m:记录 pi之前i的魔法加成
题目正解
赛中:
#include<bits/stdc++.h>
using namespace std;
long long n,m,k,p,x,b[100001],s[100001],mx[100001];
int main(){
freopen("traary.in","r",stdin);
freopen("traary.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=0;i<m;i++){
scanf("%lld%lld",&p,&x);
if(p==1){
if(b[x]==1){
s[x]=s[x]-k+i-mx[x];
mx[x]=i;
s[x]+=k;
}
else{
b[x]=1;
s[x]+=k;
mx[x]=i;
}
}
else if(p==2){
s[x]=s[x]-k+i-mx[x];
b[x]=0;
}
else{
if(b[x]==0){
cout<<i+s[x]<<endl;
}
if(b[x]==1&&i-mx[x]>=k){
cout<<i+s[x]<<endl;
mx[x]=0;
b[x]=0;
}
if(b[x]==1&&i-mx[x]<k){
cout<<i+s[x]-k+i-mx[x]<<endl;
}
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
正解:
#include <set>
#include <ctime>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define MAXN 110000
#define vint vector<int>
using namespace std;
int n,m,k;
int a[MAXN];
vector<pair<int,int> > vec[MAXN];
int ans[MAXN];
int main(){
memset(ans,-1,sizeof(ans));
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
int op,x,y;
scanf("%d%d",&op,&x);
vec[x].push_back({op,i});
}
for(int i=1;i<=n;i++){
int p=-1;
int add=0;
for(int j=0;j<vec[i].size();j++){
int x = vec[i][j].first , y = vec[i][j].second;
if(x==1){
if(p>0)add+=min(y-p,k);
p=y;
}
else if(x==2){
if(p>0)add+=min(y-p,k);
p=-1;
}
else{
ans[y] = y-1+add+(p>0?min(y-p,k):0);
}
}
}
for(int i=1;i<=m;i++){
if(ans[i]>=0)printf("%d\n",ans[i]);
}
return 0;
}
T3.智乃的兔子(usagi)
题目情况
赛中20,赛后AC。
题目大意
n只兔子,每只都有一个可爱值ai,一个祝福值bi,求在祝福值不超过h,可爱值为七的倍数的情况下最大的可爱值。
题目解析
赛中:01背包,dp[i]表示祝福值为i时最大的可爱值,状态转移方程:dp[j]=max(dp[j-1],dp[j-b[i]]+a[i])。
正解:01背包增加限制:挑选可爱值和为7的倍数。
设 f(i,j,k) :表示前i个物品,空间剩余j ,价值%7为k 。也就是在 01背包 的基础上多一层关于k的转移。
但是本题需要注意初始化,例如: f(0,0,1) 是一定不合法的,需要初始化为负无穷,其余类似情况都需要清空。
方程:f[i][j][k]=max(f[i-1][j][k] , f[i-1][j-b[i]][k-a[i]])
题目正解
赛中:
#include<bits/stdc++.h>
using namespace std;
struct str{
long long a,b;
}m[10001];
long long n,h,cnt,maxx=-1,dp[1001];
int main(){
freopen("usagi.in","r",stdin);
freopen("usagi.out","w",stdout);
scanf("%lld%lld",&n,&h);
for(int i=1;i<=n;i++){
scanf("%lld",&m[i].a);
cnt+=m[i].a;
}
for(int i=1;i<=n;i++){
scanf("%lld",&m[i].b);
}
if(h==998244353){
printf("%lld",cnt);
return 0;
}
memset(dp,-1,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++){
for(int j=h;j>=0;j--){
if(j>=m[i].b){
dp[j]=max(dp[j],dp[j-m[i].b]+m[i].a);
}
else{
dp[j]=dp[j-1];
}
}
}
for(int i=0;i<=h;i++){
if(dp[i]%7==0){
maxx=max(maxx,dp[i]);
}
}
printf("%lld",maxx);
fclose(stdin);
fclose(stdout);
return 0;
}
正解
#include<bits/stdc++.h>
#define LL long long
#define MAXN 11000
#define MAXH 1100
#define vint vector<int>
using namespace std;
int n,m,k;
int a[MAXN];
int b[MAXN];
LL dp[2][MAXH][7];
LL t[MAXN][7];
LL f[MAXH][7];
LL g[MAXH][7];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)scanf("%d",&b[i]);
if(m==998244353){
for(int i=1;i<7;i++)t[0][i]=-1e18;
for(int i=1;i<=n;i++){
for(int j=0;j<7;j++){
t[i][j]=max(t[i-1][((j-a[i])%7+7)%7]+a[i],t[i-1][j]);
}
}
cout<<t[n][0]<<endl;
return 0;
}
for(int i=0;i<MAXH;i++)
for(int j=0;j<7;j++)
g[i][j]=-1e18;
g[0][0]=0;
for(int i=1;i<=n;i++){
for(int w=0;w<=m;w++)
for(int j=0;j<7;j++){
f[w][j]=g[w][j];
if(w>=b[i])f[w][j]=max(g[w-b[i]][((j-a[i])%7+7)%7]+a[i],f[w][j]);
}
swap(f,g);
}
LL ans=0;
for(int i=0;i<=m;i++)
ans=max(ans,g[i][0]);
cout<<ans<<endl;
return 0;
}
T4.一颗成熟的奥术飞弹(missiles)
题目情况
赛中0分。
题目大意
n 个点的无向连通图,无重边和自环,目标是从 1 点到达 n 点。在一个点上,如果有多条最短路径到达终点,则 可能偏离数 加一,求出最短路径的总条数,以及所有最短路中可能偏离数的最大值。
题目解析
赛中:bfs搜最短路,每有一个与x链接的点就将偏离数加一,再将x之后的点设为未访问,重新搜索。
正解:第一次广搜
进行宽搜,记录出来dis数组, dis[i]表示点i到n的最短路长度
第二次广搜
从n为起点,宽搜每个点,可以求出n到每个点的最短路,同时记fa(i)表示i的父亲(宽搜时的前继点)。
需要考虑:如何判断一个点到另一个点是否在最短路径上。
得出结论:从x到y的边在最短路可以看作从x的最短距离等于y的最短距离+1。最终要求从1到n的最短路计数,可以离n从近到远的考虑,枚举 ,如果dis[y]+1=dis[z],说明(x,y)在最短路边上,情况进行累加,cnt[x]+=cnt[y]。
题目正解
赛中:
#include<bits/stdc++.h>
using namespace std;
queue<int> q;
long long n,m,u,v,a[1001][1001],vis[1001],cnt[1001],sum,maxx=-1;
void bfs(int x){
vis[x]=1;
while(!q.empty()){
int s=q.front();
vis[s]=1;
for(int i=1;i<=n;i++){
if(a[i][s]==1&&vis[i]!=1){
vis[i]=1;
q.push(i);
cnt[s]++;
if(i==n){
sum++;
maxx=max(maxx,cnt[s]);
}
}
}
for(int i=s+1;i<=n;i++){
vis[i]=0;
}
q.pop();
}
return ;
}
int main(){
freopen("missiles.in","r",stdin);
freopen("missiles.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=0;i<m;i++){
scanf("%lld%lld",&u,&v);
a[u][v]=1;
a[v][u]=1;
}
q.push(1);
bfs(1);
printf("%lld %lld",sum,maxx%998244353);
fclose(stdin);
fclose(stdout);
return 0;
}
正解:
#include<bits/stdc++.h>
#define LL long long
#define MAXN 110000
#define MAXH 1100
#define vint vector<int>
using namespace std;
vint son[MAXN];
int fa[MAXN], dis[MAXN], ans[MAXN], cnt[MAXN];
int n, m;
bool vis[MAXN];
queue<int> q;
vint vec;
void BFS() { // 逆序记录。
q.push(n);
vis[n] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
vec.push_back(x); // 记录广搜序列
for (int i = 0; i < son[x].size(); i++) {
int y=son[x][i];//y is x de linjiedian
if (!vis[son[x][i]]) {
vis[son[x][i]] = 1;
q.push(son[x][i]);
fa[son[x][i]] = x;
dis[son[x][i]] = dis[x] + 1;
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
son[x].push_back(y);
son[y].push_back(x);
}
BFS(); // 先进xing一次广搜,记录fa树组、记录距离树组
cnt[n] = 1; // 记录最短飞行弹道条数
for (int i = 0; i < vec.size(); i++) { // 从近到远考虑每个点
int flag = 0;
for (int j = 0; j < son[vec[i]].size(); j++) {
if (dis[vec[i]] == dis[son[vec[i]][j]] + 1) { // 相邻两个点,距离也相差1,说明在最短路边上。
ans[vec[i]] = max(ans[vec[i]], ans[son[vec[i]][j]]);
cnt[vec[i]] += cnt[son[vec[i]][j]]; // 所有能到son[vec[i]][j]的点都能走到vec[i]去,因此累加到vec[i]中。
cnt[vec[i]] %= 998244353;
if (son[vec[i]][j] != fa[vec[i]]) // 记录时不能走回父亲点去
flag = 1;
}
}
ans[vec[i]] += flag;
}
cout << cnt[1] << " " << ans[1] << endl;
return 0;
}
四.总结
考虑不周,只想到大致算法,不能将完整代码写出来,第三题骗分时忘记必须是7的倍数的条件。欲考高分,需勤加练习,任重道远。