2024 (ICPC) Jiangxi Provincial 省赛

2024 (ICPC) Jiangxi Provincial 省赛

前言

和队友 vp 7t,赛后补了几题。

A. Maliang Learning Painting

思路

输出 a + b + c

代码

cin>>n>>m>>k;
cout<<n+m+k<<endl;

C. Liar

思路

队友写得

代码

void solve() {
    int n,k;
    cin>>n>>k;
    int ans=0;
    vector<int>a(n);
    for (int i = 0; i <n ; ++i) {
            cin>>a[i];
    }
    sort(a.begin(), a.end());
    for (int i = 0; i <n ; ++i) {
        ans+=a[i];
    }
    if(ans==k)
    cout<<n<<endl;
    else{
        cout<<n-1<<endl;
    }
}

D. Magic LCM

思路

数学、质因数分解

考虑 \(a,b\) 互质,则其中一方会变为 \(1\),另一方变为 \(ab\);若 \(a=kx,b=ky\) ,则有一方变为 \(k\),另一方变为 \(kxy\) ,因为 \(xy>x+y\),所以我们应该把质因子尽量地合在一起,且要独立考虑每个质因子的幂次,即将各质因子按幂次从大到小地尽量合,最后就是要么不互质,要么就是 1,所以可以考虑用筛法将质数筛出来,为了降低复杂度,只筛 1e3 以内的,一个小于 1e6 的数不可能产生幂次大于 1 且大于 1e3 的质因子,因此碰到大质数我们直接乘到对应的答案中,小质数需要按照幂次排序再相乘。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

constexpr i64 mod = 998244353;

i64 ksm(i64 x, i64 y) {
    i64 res = 1;
    while (y) {
        if (y & 1) res = res * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return res;
}

//欧拉函数,质数
vector<int> euler_range(int n) {
    vector<int> phi(n + 1), prime;
    vector<bool> is_prime(n + 1, true);
    is_prime[1] = 0, phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (is_prime[i]) prime.push_back(i), phi[i] = i - 1;
        for (int j = 0; j < (int)prime.size() && i * prime[j] <= n; j++) {
            is_prime[i * prime[j]] = 0;
            if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            else {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
        }
    }
    return prime;
}

constexpr int N = 1e3;
auto pr = euler_range(N);

void solve() {

    int n;
    cin >> n;

    vector<vector<int>> mp(pr.back() + 1);
    vector<i64> res(n + 1, 1);
    map<int, int> mp2;

    int noone = 0, a, cnt = 0;
    for (int i = 1; i <= n; i ++) {
        cin >> a;
        for (auto &j : pr) {
            if (a % j == 0) {
                cnt = 0;
                while (a % j == 0) {
                    a /= j;
                    cnt ++;
                }
                mp[j].emplace_back(cnt);
                noone = max(noone, (int)mp[j].size());
            }
        }
        if (a > 1) {
            (res[++mp2[a]] *= a) %= mod;
            noone = max(noone, mp2[a]);
        }
    }

    i64 ans = n - noone;

    for (auto v : pr) {
        if (mp[v].size()) {
            sort(mp[v].begin(), mp[v].end());
        }
    }

    for (int i = 1; i <= noone; i ++) {
        for (auto &v : pr) {
            if (mp[v].size()) {
                auto mi = mp[v].back();
                mp[v].pop_back();
                res[i] = res[i] * ksm(v, mi) % mod;
            }
        }

        ans = (ans + res[i]) % mod;
    }

    cout << ans << '\n';

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        solve();
    }

    return 0;
}

E. Magic Subsequence

思路

双向 dfs,三进制枚举(?

注意到一个长度为 n 的正整数序列的子序列的和最多只有 \(2^n\) 种,那么取 \(n = 30\),有 \(2^n > M × n\) 存在,因此本题只需要通过任意 30 个数进行判断。(实际上存在更紧凑的界

对于子集的选取,若有解,一定存在一个子集间互相不交的解,那么 30 个数可以分为三类,属于集合 A 的数,属于集合 B 的数,不在集合中的数。所以可以采用双向 dfs 搜索,不过我这里采取的是三进制枚举,带了个 10 左右的常数,跑满 \(3^{15}\) 会超时,所以只跑了 \(3^{13}\)\(3^{14}\)就像上面说的跑不满,选取28会TLE,26会WA,很神奇(,实际写的话还是更推荐 dfs,最后合并的时候可以用归并,也可以哈希一下前半段的差,后半段搜到符合条件的解直接输出即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    int n;
    cin >> n;

    vector<int> a(n + 1);
    for (int i = 1; i <= n; i ++)
        cin >> a[i];

    n = min(n, 27);
    int mid = n >> 1;

    int end1 = 1;
    for (int i = 1; i <= mid ; i ++)
        end1 *= 3;
    vector<pair<int, int>> pre, suf;

    for (int i = 1; i < end1; i ++) {
        int sum1 = 0, sum2 = 0;
        int x = i;
        for (int j = 1; j <= mid; j ++) {
            if (x % 3 == 1) sum1 += a[j];
            if (x % 3 == 2) sum2 += a[j];
            x /= 3;
        }
        pre.emplace_back(sum1 - sum2, i);
    }

    int end2 = 1;
    for (int i = 1; i <= n - mid; i++)
        end2 *= 3;
    for (int i = 1; i < end2; i ++) {
        int sum1 = 0, sum2 = 0;
        int x = i;
        for (int j = 1 + mid; j <= n; j ++) {
            if (x % 3 == 1) sum2 += a[j];
            if (x % 3 == 2) sum1 += a[j];
            x /= 3;
        }
        suf.emplace_back(sum2 - sum1, i);
    }

    auto print = [ &mid, &n](int pre, int suf)->void{
        vector<int> A, B;
        for (int i = 1; i <= mid; i ++) {
            if (pre % 3 == 1) A.emplace_back(i);
            if (pre % 3 == 2) B.emplace_back(i);
            pre /= 3;
        }
        for (int i = 1 + mid; i <= n; i ++) {
            if (suf % 3 == 1) A.emplace_back(i);
            if (suf % 3 == 2) B.emplace_back(i);
            suf /= 3;
        }
        cout << A.size() << ' ';
        for (auto i : A) {
            cout << i << " \n"[i == A.back()];
        }
        cout << B.size() << ' ';
        for (auto i : B) {
            cout << i << " \n"[i == B.back()];
        }
    };

    sort(pre.begin(), pre.end());
    sort(suf.begin(), suf.end());

    int l = 0, r = suf.size() - 1;
    while (l < pre.size() && r >= 0) {
        auto [pv, pst] = pre[l];
        auto [sv, sst] = suf[r];
        if (pv + sv == 0) {
            print(pst, sst);
            return;
        }
        if (pv < -sv) l ++;
        else r --;
    }

    cout << "-1\n";

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        solve();
    }

    return 0;
}

G. Multiples of 5

思路

队友写得,放个官方题解吧

考虑到对每个位的数独立考虑对 5 取模的余数,假设第 i 位的数为 x,它们的贡献为 \((11^i−1 × x) \bmod 5\)
对连续长度的同一个数的贡献可以等比数列求和,根据实现细节的不同可能需要使用逆元。
注意到 \(11^i\) 的末位均为 1,因此其对 5 取模的结果均为 1,那么所有数位的贡献都是相等的,直接考虑有多少个当前数计算即可。

代码

void solve() {
	cin>>n;
	char x;
	int s=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		cin>>x;
		int t;
		if(x=='A'){
			t=x-'A'+10;
		}else{
			t=x-'0';
		}
		s+=(t*a[i]%5);
	}
	if(s%5){
		cout<<"No\n";
	}else{
		cout<<"Yes\n";
	}
}

H.Convolution

思路

队友写得,放个官解吧

考虑卷积核 \(K\) 中的每个元素产生贡献。\(K\) 中的每个元素都对应矩阵 \(I\) 中的一个子矩阵。
例如 \(K_{i,j}\) 产生的贡献是 \(K_{i,j}×\) 矩阵 $I_{i,j∼n−k+i,m−l+j} $的子矩阵之和,其中的 \(k, l\) 是卷积核 \(K\) 的长宽。
所以当子矩阵和为负数时,\(K_{i,j}\) 取 −1;子矩阵和为正数时,\(K_{i,j}\) 取 1。快速求出某个子矩阵和可以用二维前缀和来实现。

代码

void solve() {
	cin>>n>>m>>k>>l;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			b[i][j]=b[i-1][j]+a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(i<=n-k+1)c[i][j]=c[i][j-1]+b[i][j];
			else{
				c[i][j]=c[i][j-1]+b[i][j]-b[i-(n-k+1)][j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(j<=m-l+1)d[i][j]=c[i][j];
			else{
				d[i][j]=c[i][j]-c[i][j-(m-l+1)];
			}
		}
	}
	int ans=0;
	for(int i=0;i<k;i++){
		for(int j=0;j<l;j++){
			ans+=abs(d[i+n-k+1][j+m-l+1]);
		}
	}
	cout<<ans<<endl;
}

I. Neuvillette Circling

思路

计算几何

实际上所有的最小覆盖圆,都是由这 n 个点中的两个点(作为直径时)或者三个点确定。所以只需要 \(n^2 + n^3\) 分别枚举所有圆,并且再分别计算每个圆覆盖了多少点和它的半径。这样就可以维护所有 k 点覆盖圆中最小的半径。总复杂度为 \(O(n^4)\)

\(n^2\) 两点距离作为直径,中心作为圆心,\(n^3\) 三点求外切圆。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

constexpr double eps = 1e-8;   // 根据题目精度要求进行修改
constexpr double PI = acos(-1.0); // pai, 3.1415916....

int sgn(double x) {  // 进行判断, 提高精度
   if (fabs(x) < eps) return 0; // x == 0, 精度范围内的近似相等
   return x > 0 ? 1 : -1;       // 返回正负
}

// Need: sgn()

typedef struct Point {
   double x, y;
   Point(double x = 0, double y = 0) : x(x), y(y) {}  // 构造函数, 初始值为 0

   // 重载运算符
   // 点 - 点 = 向量(向量AB = B - A)
   Point operator- (const Point &B) const { return Point(x - B.x, y - B.y); }

   // 点 + 点 = 点, 点 + 向量 = 向量, 向量 + 向量 = 向量
   Point operator+ (const Point &B) const { return Point(x + B.x, y + B.y); }

   // 向量 × 向量 (叉积)
   double operator^ (const Point &B) const { return x * B.y - y * B.x; }

   // 向量 · 向量 (点积)
   double operator* (const Point &B) const { return x * B.x + y * B.y; }

   // 点 * 数 = 点, 向量 * 数 = 向量
   Point operator* (const double &B) const { return Point(x * B, y * B); }

   // 点 / 数 = 点, 向量 / 数 = 向量
   Point operator/ (const double &B) const { return Point(x / B, y / B); }

   // 判断大小, 一般用于排序
   bool operator< (const Point &B) const { return x < B.x || (x == B.x && y < B.y); }

   // 判断相等, 点 == 点, 向量 == 向量, 一般用于判断和去重
   bool operator== (const Point &B) const { return sgn(x - B.x) == 0 && sgn(y - B.y) == 0; }

   // 判断不相等, 点 != 点, 向量 != 向量
   bool operator!= (const Point &B) const { return sgn(x - B.x) || sgn(y - B.y); }
} Vector;

// Need: (-, *)

double dist(Point a, Point b) { return sqrt((a - b) * (a - b)); }

// Need: Point()

struct Circle {
   Point o;
   double r;
   Circle(Point _o = Point(), double _r = 0) : o(_o), r(_r) {}

   // 圆的面积
   double Circle_S() { return PI * r * r; }
   // 圆的周长
   double circle_C() { return 2 * PI * r; }
};

// Need: sgn(), dist()

// 点在圆上, 返回 0
// 点在圆外, 返回 -1
// 点在圆内, 返回 1

int Point_with_circle(Point p, Circle c) {
   double d = dist(p, c.o);
   if (sgn(d - c.r) == 0) return 0;
   if (sgn(d - c.r) > 0) return -1;
   return 1;
}

// Need: dist()

Circle get_circumcircle(Point A, Point B, Point C) {
   double Bx = B.x - A.x, By = B.y - A.y;
   double Cx = C.x - A.x, Cy = C.y - A.y;
   double D = 2 * (Bx * Cy - By * Cx);

   double x = (Cy * (Bx * Bx + By * By) - By * (Cx * Cx + Cy * Cy)) / D + A.x;
   double y = (Bx * (Cx * Cx + Cy * Cy) - Cx * (Bx * Bx + By * By)) / D + A.y;
   Point P(x, y);
   return Circle(P, dist(A, P));
}


int main() {
   // ios::sync_with_stdio(false);
   // cin.tie(nullptr);

   int n;
   scanf("%d", &n);

   vector<Vector> p;
   for (int i = 1; i <= n; i ++) {
      double x, y;
      scanf("%lf%lf", &x, &y);
      p.emplace_back(x, y);
   }

   vector<double> ans(n * (n - 1) / 2 + 1, 1e15);
   for (int i = 0; i < n; i ++) {
      for (int j = i + 1; j < n; j ++) {
         Point mid((p[i].x + p[j].x) * 0.5, (p[i].y + p[j].y) * 0.5);
         double R = dist(mid, p[i]);
         Circle O(mid, R);

         int num = 0;
         for (int k = 0; k < n; k ++) {
            if (Point_with_circle(p[k], O) != -1)num++;
         }
         int op = num * (num - 1) / 2;
         for (int idx = 1; idx <= op; idx ++)
            ans[idx] = min(ans[idx], R);
      }
   }

   for (int i = 0; i < n; i ++) {
      for (int j = i + 1; j < n; j ++) {
         for (int k = j + 1; k < n; k ++) {
            Circle O = get_circumcircle(p[i], p[j], p[k]);
            int num = 0;
            for (int l = 0; l < n; l ++) {
               if (Point_with_circle(p[l], O) != -1)
                  num++;
            }
            int op = num * (num - 1) / 2;
            for (int idx = 1; idx <= op; idx ++)
               ans[idx] = min(ans[idx], O.r);
         }
      }
   }

   for (int i = 1; i <= n * (n - 1) / 2; i ++) {
      printf("%.10lf\n", ans[i]);
   }

   return 0;
}

J.Magic Mahjong

思路

字符串模拟。
需要注意国士无双是 13 种幺九牌各一张再加上其中任意一张,且牌之间是无序的。
需要注意七对子不能有相同的对子。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

set<string> Thirteen, seven;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string t = "1p9p1s9s1m9m1z2z3z4z5z6z7z";

    for (int i = 0; i < t.size(); i += 2) {
        Thirteen.insert(t.substr(i, 2));
    }

    int n;
    cin >> n;

    while (n --) {

        string s;
        cin >> s;

        bool f = 1;
        set<string> k;
        for (int i = 0; i < s.size(); i += 2) {
            string p = s.substr(i, 2);
            k.insert(p);
            if (!Thirteen.count(p)) f = 0;
        }

        if (f && k.size() == 13) {
            cout << "Thirteen Orphans\n";
        } else if (k.size() == 7) {
            cout << "7 Pairs\n";
        } else {
            cout << "Otherwise\n";
        }

    }

    return 0;
}

K. Magic Tree

思路

诈骗题。

注意到换行后树实际上截断了,原来一侧的是一条没有选择的链。可以基于这个进行简单的动态规划。
继续观察可以发现,当上次 dfs 时进行了换行后,下一次操作只有一种情况(另一侧延伸是死路),而如果当前没有换行,下一次操作可以选择继续延伸或者不换行。
因为列增加即会导致两种可能,因此方案数为 \(2^{n−1}\)

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

constexpr i64 mod = 998244353;

i64 ksm(i64 x, i64 y) {
   i64 res = 1;
   while (y) {
      if (y & 1) res = res * x % mod;
      x = x * x % mod;
      y >>= 1;
   }
   return res;
}

int main() {
   ios::sync_with_stdio(false);
   cin.tie(nullptr);

   i64 m;
   cin >> m;

   cout << ksm(2, m - 1) % mod << '\n';

   return 0;
}

L. Campus

思路

最短路

时间段最多只有 \(2k+1\) 个,也就是 15 个互不相交的区间和没有大门开的区间,所以只要对这 k 个大门都做一遍 dijkstra ,然后判断哪些时间段有哪些大门开着即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

struct DIJ {
    using i64 = long long;
    using PII = pair<i64, i64>;
    vector<i64> dis;
    vector<vector<PII>> G;

    DIJ() {}
    DIJ(int n) {
        dis.assign(n + 1, 1e18);
        G.resize(n + 1);
    }

    void add(int u, int v, int w) {
        G[u].emplace_back(v, w);
    }

    void dijkstra(int s) {
        priority_queue<PII> que;
        dis[s] = 0;
        que.push({0, s});
        while (!que.empty()) {
            auto p = que.top();
            que.pop();
            int u = p.second;
            if (dis[u] < p.first) continue;
            for (auto [v, w] : G[u]) {
                if (dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    que.push({ -dis[v], v});
                }
            }
        }
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k, T;
    cin >> n >> m >> k >> T;

    vector<int> a(n + 1);
    for (int i = 1; i <= n; i ++)
        cin >> a[i];

    vector<DIJ> dij(k, n);

    vector<array<int, 3>> door(k);
    for (auto &[p, l, r] : door) {
        cin >> p >> l >> r;
    }

    for (int i = 0; i < m; i ++) {
        int u, v, w;
        cin >> u >> v >> w;
        for (int j = 0; j < k; j ++) {
            dij[j].add(u, v, w);
            dij[j].add(v, u, w);
        }
    }

    for (int i = 0; i < k; i ++) {
        dij[i].dijkstra(door[i][0]);
    }

    vector<i64> dis;
    auto res = [&](int x)->i64{
        if (!x) return -1;
        vector<i64>(n + 1, 1e18).swap(dis);
        for (int i = 0; i < k; i ++) {
            if (x >> i & 1) {
                for (int j = 1; j <= n; j ++) {
                    dis[j] = min(dis[j], dij[i].dis[j]);
                }
            }
        }

        i64 res = 0;
        for (int i = 1; i <= n; i ++)
            res += a[i] * dis[i];
        return res;
    };

    unordered_map<int, i64> ans;

    for (int t = 1; t <= T; t ++) {
        int mask = 0;
        for (int i = 0; i < k; i ++) {
            auto &[p, l, r] = door[i];
            if (l <= t && t <= r)
                mask |= 1 << i;
        }

        if (!ans.count(mask)) {
            ans[mask] = res(mask);
        }

        cout << ans[mask] << '\n';
    }

    return 0;
}
posted @ 2024-07-31 21:46  Ke_scholar  阅读(326)  评论(0编辑  收藏  举报