一、得分情况
T1 100pts , T2 30pts , T3 20pts , T4 0pts ,总分150pts。赛后AC。
二、比赛概况
20min时过了第一题,想着做对前两道,砸了2h50min去想T2正解,没做出来。最后5min写了T2T3的暴力就结束了。
三、解题报告
T1 牛奶(milk)
得分情况
比赛时AC。
题目大意
有 n 家牛奶供应商 ,第 i 家有 盒牛奶,每盒元。
出题人想要强健(?,他每月要喝 m 盒牛奶,问从供应商处购买 m 盒牛奶的最小价钱。
赛时思路
看到排序想到了结构体排序,利用贪心思想,尽量选择单价较小的奶。
正解思路
结构体排序即可
赛时代码
#include<bits/stdc++.h>
using namespace std;
struct node{
long long num,pri;
}a[114514];
long long n,m,ans,milk;
bool cmp(node x,node y){
if(x.pri!=y.pri)return x.pri<y.pri;//结构体排序
else return x.num>y.num;
}
int main(){
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld %lld",&a[i].num,&a[i].pri);
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n&&milk<m;i++){
if(m-milk>=a[i].num){
milk+=a[i].num;
ans+=a[i].num*a[i].pri;
}
else{
ans+=(m-milk)*a[i].pri;
milk=m;
}
}
cout<<ans;
return 0;
}
正解代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct node {
int a,b;
inline bool operator < (const node& o)const {
return b<o.b;
}
} datas[maxn];
int n,m;
int main() {
cin>>n>>m;
for(int i=1; i<=n; i++) {
cin>>datas[i].a>>datas[i].b;
}
sort(datas+1,datas+1+n);
long long ans=0;
for(int i=1; i<=n&&m>0; i++) {
int t=min(datas[i].a,m);
m-=t;
ans+=1ll*t*datas[i].b;
}
cout<<ans<<endl;
return 0;
}
T2 树组(strray)
得分情况
比赛时40pts。
正解差一点,打的暴力。
题目大意
有n棵树栽在一条直线内,每棵树每天成长1高度,有三种操作
第一种是对一棵树 x 施加魔法,施了魔法的树每天额外成长1高度
第二种是取消某棵树的魔法,第三种是询问树 x 的高度
赛时思路
m天内做暴力,循环给树加高度,若当前有魔法就额外+1.
正解思路
记录第i棵树最近一次施法时间。如果当前要对 i 施加魔法,那么上一次施法时间就是 ,当前时间是枚举到 t 的时间 ,方便判断 t 和 之差是否已经超过 k,超过 k 表示上次施法效果已经没有了,不超过可以继续更新施法时间,更新 。
:记录 之前 i 的魔法加成
赛时代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int a[114514],b[114514];
int main(){
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
int op,x;
cin>>op>>x;
if(op==1){
b[x]=k;
}
if(op==2){
b[x]=0;
}
if(op==3){
cout<<a[x]<<endl;
}
for(int j=1;j<=n;j++){
a[j]++;
if(b[j]>0){
b[j]--;
a[j]++;
}
}
}
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 {//第y棵树的高度就是y-1
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)
得分情况
赛时20pts。
题目大意
01背包,但是价值总和要是7的倍数
赛时思路
二进制模拟01背包,直接判断条件是否成立。
解题思路
在dp二维数组前 i 个物品,空间剩余 j 的情况下再设置一维,即当前价值%7。
注意初始化。正解代码里顺序是正序,为了防止变成完全背包就再设一个数组。
方程:tmp[j][i]=max(f[j-1][i],f[j-1][((i-a[j])%7+7)%7]+a[j]);
赛时代码
#include<bits/stdc++.h>
using namespace std;
int a[11004],w[10005],c[10005];
long long ans,cnt;
int main(){
// freopen("usagi.in","r",stdin);
// freopen("usagi.out","w",stdout);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>c[i];
}for(int i=1;i<=n;i++){
cin>>w[i];
}
while(a[n+1]!=1){
long long cnt=0,cnt1=0;
a[1]++;
for(int i=1;i<=n;i++){
if(a[i]){
cnt+=w[i];
cnt1+=c[i];
}
}
if(cnt<=m&&cnt1%7==0){
ans=max(ans,cnt1);
}
cnt=0;
while(a[++cnt]==2){
a[cnt]=0;
a[cnt+1]++;
}
}
cout<<ans;
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)
得分情况
比赛时0pts。
题目大意
n 个点的无向连通图,无重边和自环,目标是从 1 点到达 n 点。在一个点上,如果有多条最短路径到达终点,则 可能偏离数 加一,求出最短路径的总条数,以及所有最短路中可能偏离数的最大值。
解题思路
最短路计数问题。
第一次广搜
进行宽搜,记录出来 dis 树组, 表示点 i 到 n 的最短路长度
第二次广搜
从 n 为起点,宽搜每个点,可以求出 n 到每个点的最短路,同时记 fa(i) 表示i的父亲(宽搜时的前继点)。最终要求从 1 到 n 的最短路计数,可以离 n 从近到远的考虑,枚举 (x,y),如果,说明x,y在最短路边上,情况累加。
正解代码
#include<bits/stdc++.h>
using namespace std;
#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;
}
四、总结
题目较难,需要在做不出正解的时候快速把保底分拿到,保证成绩。