SuntoryProgrammingContest2024(AtCoder Beginner Contest 357)

A - Sanitize Hands

题意:给定一个序列和m,问m按顺序减去这个序列,m >= 0情况下最多能减多少个数

思路:前缀和 + prev(upper_bound())

总结:disinfectan(消毒ji), disinfect(消毒,杀毒), aliens(外星人),

void solve() {
	int n, m;
	cin >> n >> m;

	vector<int> a(n);
	for (int i = 0; i < n; ++i) {
		cin >> a[i];
		if (i) {
			a[i] += a[i - 1];
		}
	}

	cout << (upper_bound(a.begin(), a.end(), m) - a.begin()) << '\n';
}

B - Uppercase and Lowercase

题意:给个奇数长度字符串,如果大写字符数量大于小写字符数量,全小写,否则全大写。

思路:记录数量直接变。

总结:没读透彻题目,还在思考如果两种字符出现次数相等怎么办。才发现是奇数长度的字符串。

void solve() {
	string s;
	cin >> s;

	int cnt0 = 0;
	int cnt1 = 0;

	for (const auto& x : s) {
		if ('a' <= x && x <= 'z') {
			cnt0++;
		}
		else {
			cnt1++;
		}
	}
	for (auto& x : s) {
		if (cnt0 < cnt1) {
			if ('a' <= x && x <= 'z') {
				x += 'A' - 'a';
			}
		}
		else if ('A' <= x && x <= 'Z'){
			x += 'a' - 'A';
		}
	}

	cout << s << '\n';
}

C - Sierpinski carpet

题意:给定一个3的k次幂的矩阵,每个矩阵可以分解为9个3的k-1次幂的小矩阵,其中中间的矩阵需要是白色,其他矩阵作为3的k-1次矩阵存在。输出该矩阵。

思路:深度遍历,每次给定当前矩阵的左上和右下坐标,在矩阵中遍历,中间矩阵染白,其他矩阵递归处理。

总结:在坐标的处理上把握不准确:一共有3行3列,我们只要考虑当前行列左上角的坐标即可,右下角的坐标直接用左上角的坐标加长度就行。左上角的坐标对于行来说,第2行加一个单位长度,第3行加两个单位长度,列也同理。然后其实也可以不用右下角坐标参数,只要传一个边长进去就行,这个边长一定是3的整数次幂。
在输出矩阵的时候wa了两次,习惯了数组输出中间加' '...
carpet(地毯)

void solve() {
	int n;
	cin >> n;

	int m = pow(3, n);

	vector<vector<char>> mat(m + 1, vector<char>(m + 1, '#'));

	function<void(int, int, int, int)> dfs = [&](int x1, int y1, int x2, int y2) {
		if (x1 == x2 && y1 == y2) {
			return;
		}
		int d = (x2 - x1 + 1) / 3;
		for (int i = 1; i <= 3; ++i) {
			for (int j = 1; j <= 3; ++j) {
				int u1 = x1 + (i - 1) * d;
				int v1 = y1 + (j - 1) * d;
				int u2 = u1 + d - 1;
				int v2 = v1 + d - 1;
				if (i == 2 && j == 2) {
					while (u1 <= u2) {
						for (int k = v1; k <= v2; ++k) {
							mat[u1][k] = '.';
						}
						u1++;
					}
				}
				else if (d > 1){
					dfs(u1, v1, u2, v2);
				}
			}
		}
	};

	dfs(1, 1, m, m);

	for (int i = 1; i <= m; ++i) {
		for (int j = 1; j <= m; ++j) {
			cout << mat[i][j];
		}
		cout << '\n';
	}
}

D - 88888888
题意:给定一个数字x(x <= 1e18),问这个数字重复x次对998244353取模是多少。

思路:从最高的前x位考虑,每次取模后的余数m+后x位是下一次要参与到取模计算的位,设t为pow(10, x的长度) % mod,
可以得出公式:(((x % mod * t) + x) % mod * t + x) + x) * t % mod + ...
x % mod是一个定值,设为res,先不考虑mod,公式写为(((res * t) + x) * t + x) * t + ...
第一项:res = x % mod
第二项:res * t + x
第三项:(res * t + x) * t + x = res * t² + x * t + x
第四项: res * pow(t, 3) + x * pow(t, 2) + x * t + x
..
第n项: res * pow(t, n - 1) + x * (pow(t, 1) + pow(t, 2) + .. + pow(t, n - 2))

根据上述公式,第一项快速幂直接得出结果,第二项是一个等比数列,根据等比数列公式 s = t * (1 - pow(t, n - 2)) / 1 - t。由于t是一个模运算的值,所以这里除法要用逆元的形式来求。

基于上述推导,可以使用快速幂+乘法逆元直接计算了,时间复杂度很低,乘法逆元使用费马小定理即可。

总结:赛时公式推导出来了,但是总感觉数字溢出了,最后比赛结束了才发现是除法没有去逆元。 这题目很棒。
快速幂的时候要注意次数一定要>=0,如果输入为1时,不需要按推导公式计算。

constexpr int mod = 998244353;
void solve() {
	long long n;
	cin >> n;
	long long t = 1;
	if (n == 1) {
		cout << 1 << endl;
		return;
	}
	for (auto x = n; x; x /= 10, t = (t * 10) % mod);

	long long res = n % mod;

	res = res * (1 + fastPower(t, n - 1, mod)) % mod + 
		n % mod * (t * (1 - fastPower(t, n - 2, mod)) % mod * fermatInverse(1 - t, mod) % mod) % mod;



	cout << res % mod << endl;

}

E - Reachability in Functional Graph

题意:给定n个点的图,保证每个点的出度为1。sigma(求每个点能到达的其他点的数量)

思路:先用dsu求出所有的环,环中每个点的可达点数量就是环的大小。
然后遍历所有入度为0的点,求出环到点的距离,加上环的大小就是该点可达的点数,最后对每个点求和就行。

总结:看了之后没思路,不知道dsu跟dfs怎么用,其实就是环内的点的数量都能确定,然后环外的点入度一定是0,再依次判断一下即可。dsu类中的友元函数的缺省值要定义在外面,不然C++20会报错。

/*
 * DisjointSet(并查集)
 * 设计思想:保留了基本的合并查询功能,采用了宏定义的方式,可手动指定增加新功能。
 * 基于面向对象的编程思想,本方法尽可能多的隐藏了内部实现的细节,并且将必要的编程接口暴露在外部,并需要对这些接口进行直接的修改。
 * 在该设计中,需要修改的接口有:
 *                             USE_DSU_SET_ELEMENT:是否获取集合中的元素。
 *                                                  如果要保留并查集中每个集合中的具体元素,将该定义设置为true。
 *                             USE_DSU_WEIGHT:是否使用带权并查集。
 *                                             如果使用带权并查集,将该定义设置为true。
 *                                             并且实现两个友元函数mergeWeights和compressWeights。
 *                                             unionWeights:在两个集合合并时调用,需要手动实现初始化权重的细节。
 *                                             compressWeights():在路径压缩时调用,需要手动实现权重更新的细节。
 *                             并查集的主体所有必备的方法都已实现,无需修改。
 *
 * gitHub(仓库地址): https://github.com/100000000000000000000000000000000/Programming-template-for-OJ
 */










#define USE_DSU_SET_ELEMENT 0
#define USE_DSU_WEIGHT 1

class DisjointSet {
	friend void unionWeights(DisjointSet& dsu, int x, int y, int px, int py, long long value);
	friend void compressWeights(DisjointSet& dsu, int x, int y);
public:
	DisjointSet(int sz) :
		sz_(sz),
		num_sets_(sz)
	{
		fa_.resize(sz_);
		std::iota(fa_.begin(), fa_.end(), 0);
		set_size_.assign(sz_, 1);

#if USE_DSU_WEIGHT
		weight_.resize(sz_);
#endif

#if USE_DSU_SET_ELEMENT
		elements_.resize(sz_);
		for (int i = 0; i < sz_; ++i) {
			elements_[i].emplace_back(i);
		}
#endif

	}

	inline int findSet(int x) {
		if (fa_[x] == x) {
			return x;
		}
		int par = fa_[x];
		fa_[x] = findSet(fa_[x]);
#if USE_DSU_WEIGHT
		compressWeights(*this, x, par);
#endif
		return fa_[x];
	}

	inline int getSetSize(int x) {
		return set_size_[findSet(x)];
	}

#if USE_DSU_WEIGHT
	long long getWeight(int x) {
		findSet(x);
		return weight_[x];
	}
#endif

	inline int countSets() {
		return num_sets_;
	}
	inline bool isSameSet(int x, int y) {
		return findSet(x) == findSet(y);
	}

	bool unionSet(int x, int y, long long value = 0) {
		int px = findSet(x);
		int py = findSet(y);
		if (px == py) {
			return false;
		}
		fa_[px] = py;
		num_sets_--;
#if USE_DSU_WEIGHT
		unionWeights(*this, x, y, px, py, value);
#endif
		set_size_[py] += set_size_[px];

#if USE_DSU_SET_ELEMENT
		elements_[y].insert(elements_[y].end(), elements_[x].begin(), elements_[x].end());
		elements_[x].clear();
#endif

		return true;
	}

#if USE_DSU_SET_ELEMENT
	inline std::vector<int> getSetElements(int x) {
		return elements_[findSet(x)];
	}
#endif


private:
	int                             sz_;
	int                             num_sets_;
	std::vector<int>                fa_;
	std::vector<int>                set_size_;
	std::vector<long long>          weight_;


};

/*
  这里x和y是操作时的集合节点,要把x所在集合合并到y。px和py是前两者的集合代表元素。
  value是一个缺省值,代表指定x->y的权值,默认为0。
*/
void unionWeights(DisjointSet& dsu, int x, int y, int px, int py, long long value = 0) {

}

/*
  这里是路径压缩时的更新权重操作,y是x压缩前的直接父亲节点。
*/
void compressWeights(DisjointSet& dsu, int x, int y) {

}

void solve() {
	int n;
	cin >> n;

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

	DisjointSet dsu(n + 1);
	vector<int> sz(n + 1);
	for (int i = 1; i <= n; ++i) {
		if (!dsu.isSameSet(i, a[i])){
			dsu.unionSet(i, a[i]);
		}
		else {
			vector<int> c;
			for (int u = i; ; u = a[u]) {
				c.push_back(u);
				if (a[u] == i) {
					break;
				}
			}
			for (const auto& u : c) {
				sz[u] = (int)c.size();
			}
		}
	}

	function<int(int)> dfs = [&](int u) {
		if (sz[u]) {
			return sz[u];
		}
		int res = dfs(a[u]);
		return sz[u] = res + 1;
		};

	for (int i = 1; i <= n; ++i) {
		if (sz[i] == 0 && indegree[i] == 0) {
			dfs(i);
		}
	}

	long long ans = 0;
	for (int i = 1; i <= n; ++i) {
		ans += sz[i];
	}

	cout << ans << endl;

}

F - Two Sequence Queries
题意:给定2个长度为n的数组a和b,m个操作。对于m个操作,对于区间l和r,每次操作有3种,将a中该区间每个元素+x,或者b中区间每个元素+x,或者求区间中sigma(a[i] * b[i])(l <= i <= r)

思路:长度固定,直接套静态线段树模板。因为还要取模,所以线段树中维护的元素数据类型使用MInt。每个树节点维护:数组a的和,数组b的和,乘积的和。 对于节点合并的操作,直接元素相加即可。对于节点更新的操作,需要考虑三种情况:增加了a,增加了b,a和b的和都增加了,把这三种情况的变动更新到区间乘积和上,再更新a和b的区间和即可。 对于懒人标记的向下更新,直接求和叠加即可,增加数值的操作跟顺序无关。

总结:一开始在合并区间时忘记更新区间和的值了。然后在更新区间值时,没有正确的考虑a和b都增加了数值的情况。都增加数值时,sum有一部分是add_a*sum_b的贡献,还有一部分是add_b * sum_a,还有一部分是add_a * add_b * segnemnt_length。

/*
* StaticSegmentTree(静态线段树)
* 设计思想:静态线段树主要用于先给出一组输入(一般这种输入确定了树中每个节点都会被访问到,所以直接静态开点),并且给出若干个修改和查询的情况。
* 基于面向对象的编程思想,本方法尽可能多的隐藏了内部实现的细节,并且将必要的编程接口暴露在外部,并需要对这些接口进行直接的修改。
* 
* 在该设计中,需要修改的接口有:
*                             UpdateNode:该结构体存储了区间上的操作类型:需要自定义结构体变量成员及构造函数;
*                                         懒人标记的向下传递方法:mergeLazyMarks();
*                                         懒人标记被更新后的方法:clear()。
*                             StaticSegmentTreeNode: 需要在该结构体中写出要维护的区间参数,初始化函数;
*                                                    该类重载了+来实现左右孩子区间合并:在重载函数中实现细节;
*                                                    实现区间上更新懒人标记值的方法:applyUpdate();
*                                                    建树必须指定模板参数True或False,代表是否使用懒人标记。
*                             静态线段树的主体所有必备的方法都已实现,无需修改,有的题目有剪枝操作请自行添加实现。
*
* gitHub(仓库地址): https://github.com/yxc-s/programming-template.git
*/










/*
该节点定义在区间上的操作,可以根据输入的类型重写该结构体。
*/
struct UpdateNode {

    /* 自定义区间要执行的操作变量。*/
    MInt add_a = 0;
    MInt add_b = 0;


    /* 自定义初始化构造函数。*/
    UpdateNode(){}

    UpdateNode(MInt a, MInt b): add_a(a), add_b(b){}


    /*  懒人标记向下传递时调用,涉及区间操作必须实现。*/
    inline void mergeLazyMarks(const UpdateNode& parent_node, int segment_length) {
        add_a += parent_node.add_a;
        add_b += parent_node.add_b;
    }


    /* 清除懒人标记,涉及区间操作必须实现。 */
    inline void clear() {
        add_a = add_b = 0;
    }
};





struct StaticSegmentTreeNode {

    /* 在区间上要维护的变量值,必须实现。*/
    MInt sum_a = 0, sum_b = 0, sum = 0;


    /* 构造函数,必须实现。*/
    explicit StaticSegmentTreeNode(MInt value = 0): sum(value) {}


    /* 重载左右孩子区间合并操作,必须实现。*/
    friend StaticSegmentTreeNode operator + (const StaticSegmentTreeNode& a, const StaticSegmentTreeNode& b) {
        StaticSegmentTreeNode res{a.sum + b.sum};
        res.sum_a = a.sum_a + b.sum_a;
        res.sum_b = a.sum_b + b.sum_b;
        return res;
    }


    /* 区间更新方法,必须实现。*/
    inline void applyUpdate(const UpdateNode& value, int segment_length) {
        sum += sum_b * value.add_a;
        sum += sum_a * value.add_b;
        sum += value.add_a * value.add_b * segment_length;
        sum_a += value.add_a * segment_length;        
        sum_b += value.add_b * segment_length;
    }


};





/*
静态线段树,query返回类型必须是NODE_TYPE类型,更新数值类型必须是UpdateNode。
初始化模板必须指定true或者false。
该树总是约定区间左端点从1开始(避免(0 << 1)的情况)。
可通过树节点类型的数组初始化建树(推荐,建树时间复杂度更低)。
可通过要维护的区间的最大右端点下标来建树。
*/

template<const bool USE_LAZY_FLAG>
class StaticSegmentTree {
    using LAZY_TYPE      =      UpdateNode;
    using NODE_TYPE      =      StaticSegmentTreeNode;


public:
    constexpr explicit StaticSegmentTree(unsigned int n) : n_(n) {
        st_.resize(4 * n_);
        if constexpr (USE_LAZY_FLAG){
            lazy_.resize(4 * n_);
            has_lazy_.resize(4 * n_);
        }
    }


    StaticSegmentTree(const std::vector<NODE_TYPE>& s) : n_(static_cast<int> (s.size()) - 1) {
        st_.resize(4 * n_);
        if constexpr (USE_LAZY_FLAG){
            lazy_.resize(4 * n_);
            has_lazy_.resize(4 * n_);
        }
        build(s, 1, 1, n_);
    }

    /* 单点更新。*/
    inline void update(int i, const LAZY_TYPE& value) {
        update(1, 1, n_, i, i, value);
    }

    /* 更新区间值。*/
    inline void update(int i, int j, const LAZY_TYPE& value) {
        update(1, 1, n_, i, j, value);
    }

    /* 获取区间节点。*/
    inline NODE_TYPE query(int i, int j) {
        return query(1, 1, n_, i, j);
    }


private:
    unsigned int n_;


    std::vector<NODE_TYPE>                st_;
    std::vector<LAZY_TYPE>                lazy_;
    std::vector<bool>                     has_lazy_;

    /* 区间更新。*/
    void update(int p, int l, int r, int i, int j, const LAZY_TYPE& value) {
        if constexpr (USE_LAZY_FLAG) { 
        propagate(p, l, r); 
        }
        if (i > j) { 
        return; 
        }
        if (l >= i && r <= j){
            if (USE_LAZY_FLAG == true){
                lazy_[p] = value;
                has_lazy_[p] = true;
                propagate(p, l, r);
                return;
            }
            else if (l == r){
                st_[p].applyUpdate(value, 1);
                return;
            }
        }
        int mid = (l + r) >> 1;
        update(p << 1, l, mid, i, std::min(mid, j), value);
        update(p << 1 | 1, mid + 1, r, std::max(mid + 1, i), j, value);
        st_[p] = st_[p << 1] + st_[p << 1 | 1];
    };

    /* 区间查询。*/
    NODE_TYPE query(int p, int l, int r, int i, int j) {
        if constexpr (USE_LAZY_FLAG) { 
            propagate(p, l, r); 
        }
        if (l >= i && r <= j) { 
            return st_[p]; 
        }
        int mid = (l + r) >> 1;
        if (j <= mid) {
            return query(p << 1, l, mid, i, j);
        }
        else if (i > mid) {
            return query(p << 1 | 1, mid + 1, r, i, j);
        }
        else {
            return (query(p << 1, l, mid, i, mid) + query(p << 1 | 1, mid + 1, r, mid + 1, j));
        }
    }

    /* 初始化构造。*/
    void build(const std::vector<NODE_TYPE>& s, int p, int l, int r) {
        if (l == r) { st_[p] = s[l]; }
        else {
            int mid = (l + r) >> 1;
            build(s, p << 1, l, mid);
            build(s, p << 1 | 1, mid + 1, r);
            st_[p] = st_[p << 1] + st_[p << 1 | 1];
        }
    }

    /* 懒人标记向下传播。*/
    inline void propagate(int p, int l, int r) {
        if (has_lazy_[p] == true) {
            st_[p].applyUpdate(lazy_[p], r - l + 1);
            if (l != r) {
                lazy_[p << 1].mergeLazyMarks(lazy_[p], r - l + 1);
                lazy_[p << 1 | 1].mergeLazyMarks(lazy_[p], r - l + 1);
                has_lazy_[p << 1] = has_lazy_[p << 1 | 1] = true;
            }
            has_lazy_[p] = false;
            lazy_[p].clear();
        }
    }


};


using StaticSegTree = StaticSegmentTree<true>;
using StaticSegNode = StaticSegmentTreeNode;
/*
ToDoList:

*/


using namespace std;
void preProcess() {

}


void solve(){
    int n, m;
    cin >> n >> m;

    vector<StaticSegNode> a(n + 1);
    for (int i = 1; i <= n; ++i){
        cin >> a[i].sum_a;
    }
    for (int i = 1; i <= n; ++i){
        cin >> a[i].sum_b;
        a[i].sum = a[i].sum_a * a[i].sum_b;
    }

    StaticSegTree st(a);

    while (m --){
        int t, l, r;
        cin >> t >> l >> r;
        if (t == 1){
            int x;
            cin >> x;
            st.update(l, r, UpdateNode{x, 0});
        }
        else if (t == 2){
            int x;
            cin >> x;
            st.update(l, r, UpdateNode{0, x});
        }
        else{
            cout << st.query(l, r).sum << '\n';
        }
    }
}

有模板写题就是好用,只要维护固定的几个函数即可。
模板放在了下面的仓库,如果使用,请点进去点个star。
https://github.com/yxc-s/programming-template/tree/master
该仓库是一个新仓库,旨在打造一个通用的C++算法编程竞赛模板,包含数据结构,数论等各种实用的算法编程模板。如果您使用的语言不是C++,也可以将对应的代码实现翻译成其他语言来使用。

posted @ 2024-06-09 12:25  _Yxc  阅读(17)  评论(0编辑  收藏  举报