刷题记录(NC16857 [NOI1999]生日蛋糕,NC16752 [NOIP2000]单词接龙,NC204418 新集合,NC20566 [SCOI2010]游戏)

本文探讨了在编程竞赛和实际问题解决中如何通过剪枝、动态规划和搜索算法来优化解决方案。针对NOI1999的生日蛋糕问题,讲解了如何利用体积和表面积限制进行剪枝,降低时间复杂度。同时,介绍了单词接龙问题的解决方案,强调了字符串匹配和双重循环的使用。此外,文章还提到了新集合问题的解决策略,以及SCOI2010游戏中如何寻找无环路径以减少攻击节点。文章深入浅出地展示了在不同场景下优化算法的重要性。
摘要由CSDN通过智能技术生成

NC16857 [NOI1999]生日蛋糕

题目链接

关键点:

1、如何剪枝:

n为总体积,m为总层数

(1)先考虑每一层的r和h的上下界

r:因为下层的圆柱体会大于上层圆柱体,因此当前层的r至少要为当前层数,此为下界

上界:设当前已有的体积为v,那么pi*(n-v)>=pi*r*r*h;

得r<=(int)sqrt((n-v)/h) 转换成 r<=(int)sqrt(n-v);

r还有一个上界为底下一层的R,r<=R-1;

因此r<=min((int)sqrt(n-v), r[deep+1]-1);

同理的算法算h

h[deep] = min((int)(double)(n-v)/r[deep]/r[deep], h[deep+1]-1)+1

(2)当前的表面积+最小的可能表面积<=ans

(3)当前的体积+最小可能的体积<=n;

(4)当前的体积算出的表面积+当前已有的表面积<ans

(2*(目标体积-已有体积)/r + 已有表面积)>=ans)

完整代码:

# include <bits/stdc++.h>
using namespace std;
const int inf = 10000000;
int n, m, ans=inf;
int mins[30];
int minv[30];
int r[30], h[30];
void dfs(int deep, int s, int v)
{
    if (deep==0)
    {
        if (v==n)
        ans = min(ans, s);
        return ;
    }
    r[deep] = min((int)sqrt(n-v), r[deep+1]-1);
    while (r[deep]>=deep)
    {
        h[deep] = min((int)(double)(n-v)/r[deep]/r[deep], h[deep+1]-1)+1;
        while (h[deep]>deep)
        {
            --h[deep];
            if (mins[deep]+s>ans) continue;
            if (minv[deep]+v>n) continue;
            if (2.0*(n-v)/r[deep]+s > ans) continue;
            if (deep == m) s = r[deep]*r[deep];
            dfs(deep-1, 2*r[deep]*h[deep]+s, v+r[deep]*r[deep]*h[deep]);
            if (deep == m) s=0;
        }
        --r[deep];
    }
}
int main()
{
    cin>>n>>m;
    for (int i=1; i<=m; i++)
    {
        mins[i] = mins[i-1]+2*i*i;
        minv[i] = minv[i-1]+i*i*i;
    }
    h[m+1] = r[m+1] = inf;
    dfs(m, 0, 0);
    if (ans == inf) cout<<"0"<<endl;
    else
        cout<<ans<<endl;
    
    return 0;
}

NC16752 [NOIP2000]单词接龙

题目链接

关键点:

1、如何判断两个字符串是否重合,重合部分为多少

采用从a串尾,对着b串头对应判断的方法,从a串末尾枚举a,b串从哪个位置开始重合

要特判a串长度为1,不然重合长度会为0

2、易错点:每个字符串可以使用两次

完整代码

# include <bits/stdc++.h>
using namespace std;
int n, ans;
string s[30];
string fir;
int vis[30];
int check(string a, string b)
{
    int len = min(a.length(), b.length());
    for (int i=1; a.length()==1? i<=len: i<len; i++)
    {
        bool flag = true;
        for (int j=0; j<i; j++)
        {
            if (a[a.size()-i+j] != b[j])
            {
                flag = false;
                break;
            }
        }
        if (flag == true)
            return i;
    }
    return 0;
}
void dfs(string st, int now)
{
    ans = max(ans, now);
    for (int i=1; i<=n; i++)
    {
        if (vis[i]>1) continue;
        int add = check(st, s[i]);
        if (add == 0) continue;
        vis[i]++;
        dfs(s[i], now+s[i].length()-add);
        vis[i]--;
    }
}
int main()
{
    cin>>n;
    for (int i=1; i<=n; i++)
        cin>>s[i];
    cin>>fir;
    dfs(fir, 1);
    cout<<ans<<endl;
    
    
    return 0;
}

NC204418 新集合

题目链接

关键点:

1、对于每个数字有选或者不选,最后再判断有没有在给出的限制集合中出现的两个数

完整代码:

/**
 * struct Point {
 *	int x;
 *	int y;
 *	Point(int xx, int yy) : x(xx), y(yy) {}
 * };
 */

class Solution {
public:
    int ans=0;
    int use[30];
    int solve(int n, int m, vector<Point>& limit) {
        memset(use, 0, sizeof(use));
        dfs(1, n, m, limit);
        return ans;
    }
    void dfs(int now, int n, int m, vector<Point>& limit)
    {
        if (now > n)
        {
            for (int i=0; i<m; i++)
            {
                int x = limit[i].x, y = limit[i].y;
                if (use[x] && use[y]) 
                    return ;
            }
            ans++;
            return ;
        }
        use[now] = 1;
        dfs(now+1, n, m, limit);
        use[now] = 0;
        dfs(now+1, n, m, limit);
    }
};

NC20566 [SCOI2010]游戏

题目链接

关键点:

dfs:

1、对于装备的两个属性,我们将其连成双向边,对于连成的树去判断该树是否有来回路,有就说明可以使用该树的所有结点,为一颗树说明该树只能用n-1个结点

2、如何dfs,先标记当前结点访问过了,然后遍历所有与当前结点连接的结点,看是否和上一个访问过的结点相同,相同说明重复就continue,再看是否访问过,访问过说明有重边。该步要一定要在上一个判断的后面,因为和上一个访问过的结点相同就是访问过,而此时不能说明有重边,该dfs还有对是否有重边返回一个判断

2、先将答案ans设为总结点数+1, 遍历所有结点,找未访问过的,且该结点所在的为一颗树,那么就更新ans,在dfs里计算该树的最大结点maxn,ans = min(ans, maxn)

最后输出ans-1,因为算出的最大结点,而结点所在为一棵树,说明只能攻击结点数-1

完整代码:

# include <bits/stdc++.h>
using namespace std;
int n, maxn;
vector<int> ve[1000000+10];
int vis[1000000+10];
int dfs(int x, int fa)
{
    maxn = max(maxn, x);
    int flag = 0;
    vis[x] = 1;
    for (int i=0; i<ve[x].size(); i++)
    {
        int y = ve[x][i];
        if (y == fa) continue;;
        if (vis[y]) {flag = 1; continue;}
        if (dfs(y, x)) flag = 1;
    }
    return flag;
}
int main()
{
    cin>>n;
    for (int i=1; i<=n; i++)
    {
        int x, y;
        cin>>x>>y;
        ve[x].push_back(y);
        ve[y].push_back(x);
    }
    int ans = n+1;
    for (int i=1; i<=n; i++)
    {
        maxn = 0;
        if (!vis[i] && !dfs(i, 0))
            ans = min(ans, maxn);
    }
    cout<<ans-1<<endl;
    
    return 0;
}

并查集:

fa数组存父亲结点,初始化为本身值

maxn结点存该树的最大结点,初始化为本身值

b数组存该结点所在的树是否有回路,初始化为无回路0;

1、装备的属性看作为两点连边,然后在父亲所在的maxn中更新为,两个结点的最大值,父亲所在的b更新为,如果两人结点有一个的b为1,那么就为1,两个结点中有一个结点所在的树有回路,那么两个树连接就为有回路的树

2、ans =10000+1, 最后遍历所有属性值,遍历到fa[i]=i,父亲等于本身,说明信息都在i(父亲)中,那么看b[i]是否为0,为0,说明要更新ans值为, ans = min(maxn[i], ans);

3、最后输出ans-1,原因和dfs一致

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值