2024牛客寒假算法基础集训营4

文章介绍了两种不同的编程问题解决方案:一种是使用动态规划和预处理策略来解决直线染色问题,通过枚举染色状态和直线上的点;另一种是贪心策略求解数组问题,记录余数并结合前缀和。文章展示了如何运用这些技术来优化计算复杂度。
摘要由CSDN通过智能技术生成

J:画直线

思路:看到数据范围差不多可以想到用来装压dp,一个比较重要的观察是,每次都从已经染色的点上出发画新线,就是最优做法了,每次都挑未染色的点连线,最后再连起来,画线次数不会比第一种方式少。

那么这样每个时刻上色的点的颜色一定相同,所以我们枚举染色状态,染色点以及未染色点确定一条直线然后去跟新 新的状态。这里我们要预处理两点确定的这条直线上存在的点,叉积预处理即可,初始化每一条直线马,每一个点都初始化为1,其余都是无穷大进行转移即可

PII p[N];
int d[N][N];
int f[(1 << 20) + 100];     // f[i]表示在i的二进制状态下,第j为如果为0表示j未染色,为1表示染色了
PII operator-(PII a, PII b) // 重载减号
{
    return {a.xx - b.xx, a.yy - b.yy};
}
int cross(PII a, PII b)
{
    return a.xx * b.yy - a.yy * b.xx;
}
void solve()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> p[i].xx >> p[i].yy;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (i == j)
                continue;
            for (int k = 0; k < n; k++)
            {
                if (cross(p[i] - p[j], p[j] - p[k]) == 0) // 叉积为0说明在以i,j的直线上
                {
                    d[i][j] |= 1 << k;
                }
            }
        }
    }
    memset(f, 0x3f, sizeof f);
    for (int i = 0; i < n; i++)
    {
        f[1 << i] = 1; // 解决可能n=1的情况,n==1的时候,j会被跳过,所以这里先单独初始化一下单独一个点的代价
        for (int j = 0; j < n; j++)
        {
            if (i == j)
                continue;
            f[d[i][j]] = 1; // 一条直线代价就是1
        }
    }
    for (int i = 0; i < (1 << n); i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (i >> j & 1) // 第j个点被染色
            {
                for (int k = 0; k < n; k++)
                {
                    // 第 k 个点没被染色
                    // j和k直线 如果被染色过,再染一遍会使答案更大,所以没判
                    if (k == j)
                        continue;
                    f[i | d[j][k]] = min(f[i | d[j][k]], f[i] + 1);
                }
            }
        }
    }
    cout << f[(1 << n) - 1] << endl;
}

E:漂亮数组

思路:其实贪心的记录一下余数就可以过这题,这里写一下和赛时不一样的dp写法;

f[i] 表示前从前i个数里选的最大答案

其实思路还是一样的也是记录余数,我们记录一下前缀和的余数,然后map记录上一个余数的下标,然后进行转移即可,注意一下0的判断

int n, k;
int a[N], s[N];
int f[N];
map<int, int> mp;
void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        s[i] = (s[i - 1] + a[i]) % k;
    }
    mp[0] = 0;
    for (int i = 1; i <= n; i++)
    {
        f[i] = f[i - 1];
        if (mp.count(s[i]))
            f[i] = max(f[i], f[mp[s[i]]] + 1);
        mp[s[i]] = i;
    }
    cout << f[n] << endl;
}

非dp:

int n, k;
int a[N];
map<int, int> mp;
void solve()
{
    int ans = 0;
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int s = 0;
    for (int i = 1; i <= n; i++)
    {
        s += a[i];
        int t = s % k;
        if (t == 0)
        {
            ans++;
            s = 0;
            mp.clear();
        }
        else
        {
            if (mp[t])
            {
                ans++;
                s = 0;
                mp.clear();
            }
            else
            {
                mp[t]++;
            }
        }
    }
    cout << ans << endl;
}

F:来点每日一题

鸽一下

 H、数三角形(hard)

主要用树状数组来计算贡献,时间复杂度n^2logm级别

#include <bits/stdc++.h>
using namespace std;
#define pi acos(-1)
#define xx first
#define yy second
#define endl "\n"
#define lowbit(x) x & (-x)
#define int long long
#define ull unsigned long long
#define pb push_back
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define LF(x) fixed << setprecision(x)
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define Yshanqian ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 1e6 + 10, M = 3010, inf = 0x3f3f3f3f, mod = 1e9 + 7, P = 13331;
const double eps = 1e-8;
int n, m;
char g[M][M];
int d1[M][M], d2[M][M];
int tr[N];
vector<int> ve[N];
int res;
void add(int x, int c)
{
    for (int i = x; i <= m; i += lowbit(i))
        tr[i] += c;
}
int query(int x)
{
    if (x <= 0)
        return 0;
    int ans = 0;
    for (int i = x; i; i -= lowbit(i))
        ans += tr[i];
    return ans;
}
void work(int sx, int sy, int ey)
{
    int mx = 0;
    int i;
    for (i = sy; i <= ey; i += 2)
    {
        // d1[sx][i]就是 (row,i)的最长左腰
        int L = i, R = L + d1[sx][i] * 2 - 2; // 最远的右腰
        mx = max(mx, R);
        ve[R].pb(L); // 到最远的右腰,计算完就撤销这个+1
        add(i, 1);   // 左腰加1
        R = i;       // 计算他为右腰时的最左腰
        L = R - (d2[sx][i] * 2 - 2);
        res += query(i) - query(L - 1); // 求区间和即可,注意这里一个的也加上了,最后减去*的个数即可
        for (auto j : ve[i])            // 到哪里清空哪里,一个点可能被加了多次,故一边清空即可
        {
            add(j, -1);
        }
        ve[i].clear();
    }
    for (; i <= mx; i += 2) // 可能有最大的R没有清空到继续// 出去了,没清空到
    {
        for (auto j : ve[i])
        {
            add(j, -1);
        }
        ve[i].clear();
    }

    // 因为有间隔
    mx = 0;
    for (i = sy + 1; i <= ey; i += 2)
    {
        int L = i, R = L + d1[sx][i] * 2 - 2;
        mx = max(mx, R);
        ve[R].push_back(L);
        add(i, 1);
        R = i;
        L = R - (d2[sx][i] * 2 - 2);
        res += query(i) - query(L - 1);
        for (auto j : ve[i])
        {
            add(j, -1);
        }
        ve[i].clear();
    }
    for (; i <= mx; i += 2)
    {
        for (auto j : ve[i])
        {
            add(j, -1);
        }
        ve[i].clear();
    }
}
void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> g[i] + 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (g[i][j] == '*')
            {
                d1[i][j] = d1[i - 1][j + 1] + 1;
                d2[i][j] = d2[i - 1][j - 1] + 1;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (g[i][j] == '.')
                continue;
            int k = j;
            while (k + 1 <= m && g[i][k + 1] == '*')
                ++k;
            work(i, j, k);
            j = k;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
            if (g[i][j] == '*')
                res--;
    }
    cout << res << endl;
}
signed main()
{
    Yshanqian;
    int T;
    T = 1;
    // cin >> T;
    for (int cases = 1; cases <= T; ++cases)
    {
        // cout<<"Case #"<<cases<<": ";
        solve();
    }
    return 0;
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值