NOIP提高组试题刷题记录-2021.10

这篇博客回顾了作者在NOIP2001-2018年算法竞赛中的解题经历,包括‘赛道修建’、‘奶酪’、‘逛公园’、‘矩形覆盖’和‘宝藏’等题目。作者分享了每道题目的解题思路、得分情况、用时以及解题后的反思。在‘赛道修建’中,提到了针对不同图形的解法,如二分法、最长边和最短边联合等。在‘奶酪’问题中,通过判断球体集合是否能贯穿奶酪来求解。‘逛公园’和‘矩形覆盖’利用DFS求解,而‘宝藏’问题采用爆搜策略。博客还包含了其他一些题目的解题思路和代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NOIP2001-2017题解
NOIP2018
试题:赛道修建
预计得分:55
实际得分:55
用时:1h
思路:分析数据,可知特殊图像一共分3种:
链式图(b[i] = a[i] + 1):二分
菊花图(a[i] = 1):最长边和最短边联合,次长边和次短边联合……
普通但是只需要一条赛道(m = 1):树的直径
反思:
此题应考虑部分分,而不是最优解
最后附上代码(55分部分分)

#include<bits/stdc++.h>
using namespace std;
int n, m;
const int maxn = 1e5 * 5 + 10;
int a[maxn];
int head[maxn];
struct road{
    int next_edge, to;
    int w;
}edge[maxn * 2];
int tot = 0;
int tot1 = 0;
int ans = 0;
bool check(int total){//TODO check
    int t = 0;
    int summ = 0;
    for(int i = 1;i < n;i++){
        if(summ + a[i]>= total){
            t++;
            summ = 0;
        }
        else summ += a[i];
    }
    return t >= m;
}
void dfs(int now,int fa){//TODO dfs
    for(int k = head[now];k;k = edge[k].next_edge){
        int v = edge[k].to;
        int w = edge[k].w;
        if(v != fa){
            dfs(v,now);
            a[++tot1] = w;
        }
    }
}
int dfs2(int now,int fa){//TODO dfs2
    int max1 = 0,max2 = 0;
    for(int k = head[now];k;k = edge[k].next_edge){
        int v = edge[k].to;
        int w = edge[k].w;
        if(v != fa){
            max2 = max(max2,dfs2(v,now) + w);
            if(max1 < max2)swap(max1,max2);
        }
    }
    ans = max(ans,max1 + max2);
    return max1;
}
void add(int u,int v,int w){//TODO add
    tot++;
    edge[tot].to = v;
    edge[tot].next_edge = head[u];
    edge[tot].w = w;
    head[u] = tot;
}
struct cmp{
    bool operator ()(int a,int b){
        return a > b;
    }
};
int main(){//TODO main
    scanf("%d%d",&n,&m);
    int u, v, w;
    bool book1 = 1;
    int sum = 0;
    for(int i = 1;i < n;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
        if(u != 1 && v != 1)book1 = 0;
        sum += w;
    }
    dfs(1,0);
    if(book1){
        sort(a + 1,a + n,cmp());
        ans = 99999999;
        for(int i = 1;i <= m;i++){
            ans = min(ans, a[i] + a[2 * m - i + 1]);
        }
        printf("%d",ans);
    }
    else if(m == 1){
        dfs2(1,0);
        printf("%d",ans);
    }
    else {
        int l = 1,r = sum,mid;
        while(l < r){
            mid = (l + r + 1) / 2;
            if(check(mid)) l = mid;
            else r = mid - 1;
        }
        printf("%d",l);
    }
    return 0;
}
/*
5 2
1 2 5
2 3 6
3 4 3
4 5 2

5 3
1 3 9
1 2 2
1 4 8
1 5 3
*/

NOIP2017

试题:奶酪
预计得分:???
实际得分:100
用时:0.5-1h
思路:对于这道题,实际上就是相切或相交的球并联成一个集合,然后判断每一个集合是否能贯穿奶酪就完事了
反思:此题需多加思索才能想出正解
代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e3 + 10;
int T;
int n;
ll r,h;
//if u state "r" as "int" instead of "long long",u will "RE"
int f[maxn];
int f1[maxn],f2[maxn];
ll x[maxn], y[maxn], z[maxn];


ll dist(ll x1,ll y1,ll z1,ll x2,ll y2,ll z2){//pay attention:this is dis square
    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2);
}

int getf(int x){
    if(f[x] == x)return x;
    else {
        f[x] = getf(f[x]);
        return f[x];
    }
}

int main(){
    scanf("%d",&T);
    while(T--){
        memset(f1,0,sizeof(f1));
        memset(f2,0,sizeof(f2));
        scanf("%d%lld%lld",&n,&h,&r);
        int top = 0, bottom = 0;
        for(int i = 1;i <= n;i++){
            f[i] = i;
        }
        ll dis = 0;
        for(int i = 1;i <= n;i++){
            scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
            if(z[i] + r >= h){
                top++;
                f1[top] = i;
            }
            if(z[i] - r <= 0){
                bottom++;
                f2[bottom] = i;
            }
            for(int j = 1;j <= i;j++){
                if((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) > 4 * r * r)
                            continue;//it is so large that it maybe over LONG_LONG_MAX
                if(dist(x[i],y[i],z[i],x[j],y[j],z[j]) <= 4 * r * r){
                    int a1 = getf(i);
                    int a2 = getf(j);
                    if(a1 != a2)f[a2] = a1;
                }
            }
        }
        bool book = 0;
        for(int i = 1;i <= top;i++){
            for(int j = 1;j <= bottom;j++){
                if(getf(f1[i]) == getf(f2[j])){
                    book = 1;
                    break;
                }
            }
            if(book)break;
        }
        puts(book ? "Yes" : "No");
    }
    return 0;
}

试题:逛公园
预计得分
实际得分:
用时:
思路:
反思:

试题:矩形覆盖
预计得分:100
实际得分:100
用时:0.5h
思路:先建立一个矩形类型,然后dfs
对于dfs,定义两个状态:当前点和总面积
每一次枚举将当前点放入哪一个矩形
然后就完事了
反思:
代码:

#include<bits/stdc++.h>
using namespace std;
int ans = INT_MAX >> 2;//提前预定大一点防止面积过大导致出错
struct mtrx{
    int left,up,right,down;// up/down:上/下(y) left/right:左/右(x) eg:左下(left,down)
    bool used;
    bool inmtrx(int x,int y){
        return x <= right && x >= left && y <= up && y >= down;
    }
    int S(){
        return (right - left) * (up - down);
    }
    bool intersect(mtrx a){
        if(!a.used || !used)return 0;
        return inmtrx(a.left,a.down)
        || inmtrx(a.right,a.down) || inmtrx(a.left,a.up) || inmtrx(a.right,a.up);
    }
    void add(int x,int y){
        if(!used){
            left = right = x;
            up = down = y;
            used = 1;
        }
        else{
            if(y > up)up = y;
            else if(y < down)down = y;
            if(x > right)right = x;
            else if(x < left)left = x;
        }
    }
    void print(){
        printf("\
left: down:(%d,%d)\
      up  :(%d,%d)\
right:down:(%d,%d)\
      up  :(%d,%d)\n",left,down,left,up,right,down,right,up);
    }
}mtr[5];
int n,k;
int x[100],y[100];
bool check(){
    for(int i = 1;i <= n;i++){//n <= 4请放心食用
        for(int j = i + 1;j <= n;j++){
            if(mtr[i].intersect(mtr[j])) return 0;
        }
    }
    return 1;
}
void dfs(int step,int area){
    if(area >= ans)return;
    if(step == n + 1){
        if(check()){
            if(ans > area){
                ans = area;
            }
        }
    }
    mtrx tmp;
    for(int i = 1;i <= k;i++){
        tmp = mtr[i];
        mtr[i].add(x[step],y[step]);
        dfs(step + 1,area - tmp.S() + mtr[i].S());
        mtr[i] = tmp;
    }
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;i++){
        scanf("%d%d",&x[i],&y[i]);
    }
    dfs(1,0);
    printf("%d",ans);
    return 0;
}

试题:宝藏
预计得分:40
实际得分:70
用时:1h
思路:爆搜没什么好说的
反思:n!< 10 ^ 3所以有重边
代码(70分)

#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m;
int wei[20][20];//wei[i][j] 是i->j的边值为wei[i][j]
int book[20];
int tmp = 0,ans = inf;
void dfs(int now,int num){
    if(num == n){
        if(tmp < ans) ans = tmp;
        return;
    }
    if(tmp >= ans)return;
    for(int i = 1;i <= n;i++){
        if(book[i])continue;
        for(int j = 1;j <= n;j++){
            if(wei[j][i] == inf || !book[j] || j == i)continue;
            book[i] = book[j] + 1;
            tmp += wei[j][i] * book[j];
            dfs(i,num + 1);
            book[i] = 0;
            tmp -= wei[j][i] * book[j];
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    int u,v,w;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= n;j++){
            wei[i][j] = inf;
        }
    }
    for(int i = 1;i <= m;i++){
        scanf("%d%d%d",&u,&v,&w);
        wei[u][v] = wei[v][u] = min(wei[u][v],w);
    }
    for(int i = 1;i <= n;i++){
        book[i] = 1;
        dfs(i,1);
        book[i] = 0;
    }
    printf("%d",ans);
    return 0;
}

题目:P7116
预计得分:40
实际得分:45
用时:1h
思路:对于每一轮移动,都有一些点会因移动后出界而无效,剩余的点的的数量即为这一轮的步数答案
反思:从最小的1维开始思考,即可得到45分答案
代码:(45分)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int k, n;
const LL mod = 1e9 + 7;
const int maxn = 5e5 + 100;
LL c[maxn],d[maxn],e[maxn];//e[i]为当前的偏移量
LL l[20], r[20];//l,r是这一轮第i维的最左端不符合(负数)的和最右端不符合的点(正数)
LL w[20];
LL ans = 1;
int main(){
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= k;i++){
        scanf("%lld",&w[i]);
        ans = ans * w[i] % mod;
    }
    //此时ans = 所有点(每一个点在走出界时也算一步)
    for(int i = 1;i <= n;i++){
        scanf("%lld%lld",&c[i],&d[i]);
    }
    while(1){//枚举每一轮
        for(int i = 1;i <= n;i++){
            //记录偏移量
            e[c[i]] += d[i];
            //更新边界
            l[c[i]] = min(l[c[i]],e[c[i]]);
            r[c[i]] = max(r[c[i]],e[c[i]]);
            LL s = 1;
            for(int j = 1;j <= k;j++){
                if(r[j] - l[j] >= w[j]){
                    printf("%lld",ans);
                    return 0;//所有点已经全部出界
                }
                s = s * (w[j] - r[j] + l[j]) % mod;//加上合格的点
            }
            ans = (ans + s) % mod;
            //更新答案
        }
        bool book = 1;
        for(int i = 1;i <= k;i++){
            if(e[i] != 0){//如果走不出去
                book = 0;
                break;
            }
        }
        if(book){
            puts("-1");
            return 0;
        }
    }
    return 0;
}

详细题解见链接:

https://blog.csdn.net/hi_ker/category_9277438.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值