2023第十四届河南省ICPC省赛部分题解

7a684047-65b5-481c-8550-b8712baed8d0

        

去年大一缺乏体验,今年比赛前心血来潮又V了一把,彻底成为竞赛败犬。。。。

K.连通最小乘积

        签到,贪心搞一下,根据乘法结合分配律优化一下,正数负数相互结合就行。


vector<ll> fu,zheng;
void solve()
{
    int n;
    cin >> n;
    for(int i=1;i<=n;i++){
        int t;cin>>t;
        if(t<=0) fu.push_back(t);
        else zheng.push_back(t);
    }
    sort(zheng.begin(),zheng.end());
    sort(fu.begin(),fu.end());
    ll ans=0;
    int ff=fu.size();
    int zz=zheng.size();
    if(fu.size()==0){
        for(int i=1;i<zheng.size();i++){
            ans+=zheng[0]*zheng[i];
        }
    }
    else if(zheng.size()==0){
        for(int i=fu.size()-2;i>=0;i--){
            ans+=fu[ff-1]*fu[i];
        }
    }
    else{
        ll tt=0;
        ans=0;
        for(int i=0;i<zz;i++){
            tt+=zheng[i];
        }
        for(int i=0;i<ff;i++){
            ans+=tt*fu[i];
        }
    }
    cout<<ans;
}
}

L.行星探索

        也算个签到吧,很裸的二维前缀和,队友很快就A了。膜%%%%

#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 2e6 + 7;
const int mod = 998244353;
int s[1010][1010][4];
char p[1010][1010];
void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            char x;
            cin >> x;
            if (x == 'C')
                s[i][j][1] = 1;
            if (x == 'M')
                s[i][j][2] = 1;
            if (x == 'F')
                s[i][j][3] = 1;
        }

    for (int p = 1; p <= 3; p++)
    {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                s[i][j][p] += s[i - 1][j][p] + s[i][j - 1][p] - s[i - 1][j - 1][p];
    }
    while (k--)
    {
        int x, y, xx, yy;
        cin >> x >> y >> xx >> yy;
        for (int p = 1; p <= 3; p++)
            cout << s[xx][yy][p] - s[x - 1][yy][p] - s[xx][y - 1][p] + s[x - 1][y - 1][p] << " ";

        cout << "\n";
    }
}

M.二手物品回收

        很一眼的全裸多重背包题,据说因为数据水很多人贪心过(,这里把每个商家看成一组,每组有自己的物品,可以选一些带走,背包容量为k,每次花费体积为1。需要特别注意的是需要严格选择k个,即使亏钱(crazy). 

        复杂度分析的话应该是O(m(k + n))。因为物品总数只有n个。

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 1e3 + 10;
const int mod = 998244353;
ll dp[N][N];
int n, m, k;
vector<int> e[N];
ll c[N];
void solve()
{
    cin >> n >> m >> k;
    for (int i = 1; i <= m; i++)
    {
        cin >> c[i];
    }
    for (int i = 1; i <= n; i++)
    {
        int a, b;
        cin >> a >> b;
        e[b].push_back(a);//统计每组数量
    }
    for(int i=0;i<=m;i++){
        for(int j=0;j<=k;j++){
            dp[i][j] = -inf;//初始化为负数
        }
    }
    for (int i = 1; i <= m; i++)
    {
        sort(e[i].rbegin(), e[i].rend());
    }
    dp[0][0] = 0;
    for (int i = 1; i <= m; i++)
    {

        for (int j = k; j >= 0; j--)
        {
            ll sum = 0;
            dp[i][j] = dp[i - 1][j];
            for (int p = 0; p < e[i].size(); p++)
            {
                sum += e[i][p];
                if (p + 1 <= j)
                {
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - p - 1] + sum - c[i]);
                }
            }
        }
    }
    cout << dp[m][k] << '\n';
}

C.结对编程 

        也算个签到吧,没模数提示有点明显,找到规律就好了,队友又速A了%%%%

#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 1e3 + 10;
const int mod = 998244353;
void solve()
{
    int n;
    cin >> n;
    map<int, int> mp;
    for (int i = 2; i <= n; i++)
    {
        int x;
        cin >> x;
        mp[x]++;
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        ans += x;
        ans -= 1ll * x * mp[i];
    }
    cout << ans << endl;
}

以上就是23年省赛最签到的几个题了,仍记得全场坐牢的场景(惨。

G.异或解密

        比较诈骗的一个题,玄学复杂度。

        考虑原题式子可以左移可化为 Xi ^ a = Yi 。拆解成二进制可以理解为对于60位中的每一位,都可以使这一位置为 0 或 1 ,贡献与原来数字对应位数为1个数有关。

        比如第j位有p个数包含1,如果a第j位为1,则贡献为 (1<<j) * (n - p),为0时则 *p。那么我们可以考虑爆搜,对于每一位分别置0/1时观察答案,时间复杂度为 O(2的60次方) (雾。

        考虑剪枝,一个比较明显的剪枝是,当前位置0/1后产生的贡献大于可以到达的最大值,则不可以。另一个比较难观察的是,如果当前位后面所有位置为1时是能得到的最大值,如果这个值比要达到的值还要小,就证明不可能。

        到此即可通过本题。

额外注意的一点是过程中可能会爆 int64.


#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
#define ll __int128_t
const int N = 1e3 + 10;
const int mod = 998244353;
ll d[60];
long long n, S;
long long ans = 1e18;
bool dfs(ll now, ll p, ll res)
{
    if (p == -1)
    {
        if (now == 0)
        {
            ans = res;
            return true;
        }
        return false;
    }
    ll k =  d[p]*(1ll << p);

    if (k <= now&&(now - k) / n<=__int128_t((1ll << p) - 1))
    {
        if (dfs(now - k, p - 1, res))
        {
            return true;
        }
    }
    k = (1ll << p) * (n - d[p]);
    if (k <= now&&(now - k) / n<=__int128_t((1ll << p) - 1))
    {
        return dfs(now - k, p - 1, res + (1ll << p));
    }
    return false;
}
void solve()
{

    cin >> n >> S;
    for (int i = 0; i < n; i++)
    {
        long long x;
        cin >> x;
        for (int j = 0; j < 60; j++)
        {
            if (x >> j & 1)
            {
                d[j]++;
            }
        }
    }

    dfs(S, 59, 0);
    if (ans == 1e18)
    {
        cout << -1 << '\n';
    }
    else
    {
        cout << ans << '\n';
    }
}

A.列车售货员难题

        很好的一道题,使我汗流浃背。。。。

        比较朴素的想法是,枚举所有区间计算答案,但是显然会超时。此时我们注意到m的大小只有100,我们对于m进行优化。考虑枚举区间左端点,对于这个左端点会产生的贡献,考虑从最右端减到最左端,每当丢失一种物品时会产生贡献,而丢失物品的次数不超过m次。我们可以预处理出dp[i][j]表示以i为左端点,j最后一次出现的位置(靠左)是哪里,之后我们可以在O(nm)的枚举中处理出答案。

        到这这题已经差不多了,但是还有一个折磨我许久的问题是如何去重,因为这些区间产生的集合可能是重复的,一个比较经典的写法是使用__int128表示集合,每一位表示对应第i个有没有,最后使用map或set去重即可。我使用了一种哈希异或映射的方法通过本题。

#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using ull = unsigned long long;
const int N = 2e5 + 10;
const int mod = 998244353;
const ll os = 1331;
int f[N][110];
mt19937_64 rnd(98275314);

long long gen()
{
	long long x = 0;
	while(x == 0)
		x = rnd();
	return x;
}
void solve()
{
    int n, m;
    cin >> n >> m;
    vector<vector<int>> a(n + 1);
    vector<ll>mp(m + 1);
    for(int i=1;i<=m;i++){
        mp[i] = gen();//每一个都映射一种值
    }
    for (int i = 1; i <= n; i++)
    {
        int k;
        cin >> k;
        a[i].resize(k);
        for (auto &x : a[i])
        {
            cin >> x;
        }
    }
    for (int i = n; i >= 1; i--)
    {
        for (int j = 1; j <= m; j++)
        {
            f[i][j] = f[i + 1][j];
        }
        for (auto x : a[i])
        {
            f[i][x] = i;
        }
    }
    set<ll> s;
    for (int i = 1; i <= n; i++)
    {
        vector<pii> id;
        ll ps = 0;
        for (int j = 1; j <= m; j++)
        {
            if (f[i][j] == 0)
                continue;
            id.push_back({f[i][j], j});
        }
        sort(id.begin(), id.end());
        for (int j = 0; j < id.size(); j++)
        {
            ps ^= mp[id[j].second];
        }
        if(ps)s.insert(ps);//非空
        for (int j = id.size() - 1; j >= 0; j--)
        {
            ps ^= mp[id[j].second];
            if (j > 0 && id[j].first == id[j - 1].first)
                continue;
            if (ps)
                s.insert(ps);
        }
    }
    cout << s.size() << '\n';
}

I.calc

        很nice的一道题,使我思维旋转。

        求方案数,考虑计数dp。但是一看数据范围1e7和7。很合理的想到状压dp+矩阵快速幂优化。

        考虑如何设计状态,k的范围是[1,7],考虑连续的七个数,用二进制状态表示,根据输入处理出不合法的状态,可以得到1-7的答案,考虑向后递推。每向左移动一位,产生的新的状态判断是否合法,如果合法即可转移。再看对于左移加一,如果合法且上一个丢失的位置与这一位没有冲突,也可以转移,由此可设计出(1<<k) * (1<<k)的转移矩阵,直接转移即可。

#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
const int mod = 998244353;
struct Mat
{
    int v[130][130] = {0};
    int x, y;
    Mat() {}
    Mat(int _x, int _y) { x = _x, y = _y; }
    // 清空
    void zero()
    {
        memset(v, 0, sizeof(v));
        x = y = 0;
    }
    // 单位矩阵化
    void ones()
    {
        memset(v, 0, sizeof(v));
        for (int i = 0; i <= x; i++)
        {
            v[i][i] = 1;
        }
    }
    // 乘法
    void Mul(Mat a, Mat b)
    {
        zero();
        x = a.x, y = b.y;
        int c = a.y;
        for (int i = 0; i <= x; ++i)
        {
            for (int j = 0; j <= y; ++j)
            {
                for (int k = 0; k <= c; ++k)
                {
                    v[i][j] += (long long)a.v[i][k] * b.v[k][j] % mod;
                    v[i][j] %= mod;
                }
            }
        }
        return;
    }
    // 打印
    void show()
    {
        for (int i = 1; i <= x; i++)
        {
            for (int j = 1; j <= y; j++)
            {
                cout << v[i][j] << " ";
            }
            cout << '\n';
        }
    }
};
Mat operator*(const Mat &x, const Mat &y)
{
    Mat res;
    res.Mul(x, y);
    return res;
}
Mat ksm(Mat a, long long b)
{
    Mat x = a;
    x.ones();
    while (b)
    {
        if (b & 1)
            x = x * a;
        b >>= 1;
        a = a * a;
    }
    return x;
}
void solve()
{
    int k, n;
    cin >> k >> n;
    vector<int> s(8);
    for (int i = 0; i < k; i++)
    {
        int x;
        cin >> x;
        s[x] = 1;
    }
    vector<int> f(1 << 7);
    for (int i = 0; i < (1 << 7); i++)
    {
        vector<int> a;
        for (int j = 0; j < 7; j++)
        {
            if (i >> j & 1)
            {
                a.push_back(j + 1);
            }
        }
        int ok = 1;
        for (int p = 0; p < a.size(); p++)
        {
            for (int q = p + 1; q < a.size(); q++)
            {
                if (s[a[q] - a[p]])
                    ok = 0;
            }
        }
        f[i] = ok;
    }//预处理合法位置
    if (n <= 7)
    {
        int ans = 0;
        for (int i = 0; i < (1 << n); i++)
        {
            ans += f[i];
        }
        cout << ans - 1<< '\n';
    }
    else
    {
        Mat a(1, (1 << 7) - 1);
        for (int i = 0; i < (1 << 7); i++)
        {
            a.v[1][i] = f[i];
        }
        Mat op(127, 127);
        for (int i = 0; i < (1 << 7); i++)//设计矩阵
        {
            int sp = i;
            int ok = 0;
            if (sp & (1 << 6))
                ok = 1;
            if (ok)
                sp -= (1 << 6);
            sp <<= 1;
            op.v[i][sp] = 1;
            op.v[i][sp | 1] = f[sp | 1] && !(s[7] && ok);//新旧位是否冲突
        }
        
        Mat res = a * ksm(op, n - 7);
        ll ans = 0;
        for(int i=0;i<(1<<7);i++){
            ans += res.v[1][i];
            ans %= mod;
        }
        cout << ans - 1<< '\n';//减去全0方案数
    }
}

到这差不多结束了,但还是有点遗憾,再口胡两道已知的题目。

F.实验器材采购

        23年赛时过了这个题,当时是学长写的,据说是一个带修莫队(雾

H.部落冲突

        很裸的半平面交,因为23年赛时没学过用其它麻烦的方法没调出来而感到遗憾(败犬

。。。。不会LateX使我的题解难看。

7a684047-65b5-481c-8550-b8712baed8d0
  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小爬菜、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值