zoukankan      html  css  js  c++  java
  • Educational Codeforces Round 50(div. 2) 题面 & 题解

    Educational Codeforces Round 50(div. 2)题面 & 题解

    被教练抓去写这个东西了……

    官方题解

    题号 题目名 难度系数 算法标签
    A Function Height 15 贪心,基础数学
    B Diagonal Walking v.2 25 贪心,找规律
    C Classy Numbers 40 基本数位DP
    D Vasya and Arrays 35 贪心,二分查找
    E Covered Points 45 初中计算几何,模拟,暴力
    F Relatively Prime Powers 50 数论,反向思维,卡精度

    A Function Height

    题意

    给定平面直角坐标系 (OXY) ,其中 (x) 轴上有 (2n+1) 个点 (P_0, P_1, dots, P_{2n}) ,初始 (P_i) 坐标为 ((i,0)) 。每次操作,你可以对 (i) 为奇数的点的 (y) 轴坐标 (+1) 。现在你要对这些点做若干次操作,使得线段 (P_iP_{i+1}) 和 x 轴所围成的图形面积恰好为 (k) ,并且使所有点的 (y) 坐标的最大值最小。求这个最小值。 (n, k leq 10^{18})

    题解

    由题,易知一共有 (n) 个可被操作的点,且每次操作会且只会使总面积 (+1) ,所以我们操作最多为 (k) 次。所以我们的移动应尽可能平均(即让每个可被操作的点高度尽可能接近)。容易知道此时答案一定最小,且答案为 (lceil frac{k}{n} ceil)

    代码

    #include <iostream>
    using namespace std;
    long long n, k;//不用 double 和 ceil(),防止精度爆炸。
    int main(){
    	cin >> n >> k;
    	cout << (k+n-1) / n << '
    ';
    	return 0;
    }
    

    B Diagonal Walking v.2

    题意

    你在一个平面直角坐标系上走,初始你在原点。你每次可以从 ((x,y)) 走到:((x+1,y))((x-1,y))((x,y+1))((x,y-1))((x+1,y+1))((x+1,y-1))((x-1,y+1))((x-1,y-1)) 八个点。如果在一步中,你选择走到上面的后四种选择之一,则这一步被称之为“斜步”,而其他的步被称为“直步”。你可以经过一个点任意多次。

    (q (q leq 10^4)) 个询问,每一次询问是否恰好能走 (k) 步走到 ((x,y)) ,如果能,求走路过程中最多能走几个“斜步”;如果不能,输出-1(k,x,y leq 10^{18})

    题解

    假设 (x,y > 0) 。显然所有的 (x,y) 的情况都有等价的 (x,y > 0) 的情况(即 (x = |x|, y = |y|) )。

    易知:对于向上下左右四个方向走 (2) 步的情况都可以通过走两次“斜步”来代替。例如,从 ((x,y)) 走到 ((x+2,y)) 可以由 ((x,y) ightarrow) ((x+1,y+1) ightarrow (x+2,y)) 更优的达到。所以说我们首先可以通过连续的斜步先走到 ((min(x,y),min(x,y))), 然后剩下的要走的路就是一条线段。显然可以发现,走到目的地的最少步数为 (max(x,y))(min(x,y)) 步走完第一部分,(max(x,y)-min(x,y)) 走完剩下的线段)。于是无解的情况就被我们讨论完了。于是我们接着分类讨论:

    • 如果此离目的地还有偶数个直步(已经判断完了无解,下同):

      1. 如果剩偶数步可以走,那么就在最后的步数中一直反复走“斜步”。最终答案为 (k)

      2. 如果剩奇数步可以走, 那么把一开始走到 ((min(x,y),min(x,y))) 的连续斜步中的一个斜步改成两个直步,于是就变成了 上面 1 的情况了。最终答案为 (k-2)

    • 如果距离目的地还剩奇数个直步:

      1. 如果剩偶数步可以走,则先用类似 1 的方法走到一个离终点距离为 (1) 个直步,且离当前位置最近的点,再一个斜步走到目的地另一个离终点距离为 (1) 个直步的点,再一个直步走到目的地。此时变成了 1 的情况。最终答案为 (k-1)

      2. 如果剩奇数步可以走,则一个直步向着目的地走,则变成了 1 的情况了。最终答案为 (k-1)

    (看不懂的话可以自己画图理解。)

    一次询问时间复杂度 (O(1)), 总时间复杂度 (O(q))

    代码

    #include <iostream>
    #include <cstdio>
    using namespace std;
    typedef long long ll;
    template<typename T> inline T read(T& t) {//快读,这道题可以用scanf读入
    	t=0;short f=1;char ch=getchar();
    	while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
    	while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
    	t*=f; return t;
    }
    ll n, m, k, mn, mx;
    
    inline void Main(){
    	read(n); read(m); read(k);
    	if(n < 0) n = -n; //把(n,m)改成正的
    	if(m < 0) m = -m;
    	mn = min(n,m), mx = max(n,m);
    	if(mx > k) {//步数少于最少需要的步数,这是唯一无解的情况。
    		cout << -1 << '
    ';
    		return;
    	}
    	k -= mx;
    	if((mx-mn)%2 == 0){
    		if(k % 2 == 1) cout << k+mx-2 << '
    ';
    		else cout << k+mx << '
    ';
    	}else{
    		cout << k+mx-1 << '
    ';
    	}
    }
    
    signed main(){
    	int q; 	read(q);
    	while(q--) Main();
    	return 0;
    }
    

    C Classy Numbers

    题面

    定义一个数字是“好数”,当且仅当它的十进制表示下有不超过 (3) 个数字(1 sim 9)。举个例子:(4,200000,10203) 是“好数”,然而 (4231,102306,7277420000) 不是。

    (T(1 le T le 10^{4})) 组数据。每组数据给你 (l,r ; (1 le l le r le 10^{18})),问有多少个 (x) ((l le x le r)) ,且 (x) 是“好数”。

    题解

    模板数位 DP 题。

    我们令 (dp_{i,j}) 表示在考虑第 (i) 位,并且非 (0) 的数码有 (j) 个,且第 (i) 位没有选择限制的 后面比他小的位的方案数。我们用记忆化搜索的方式来填 (dp_{i,j}) 。首先我们把原题的求区间改成两个前缀和相减,然后我们分别 (r)(l) 计算前缀和答案。

    在求到 (x) 前缀和答案的过程中,我们首先先把 (x) 拆成他的每一个位,然后从最高位开始进行 dfs。我们发现,如果在某一位选的比 (x) 的那一位要小,那么后面的每一个位不管怎么选,都不会比 (x) 要大,那么这些的答案就将会是通用的,可以把他们记忆化。具体的一些细节在代码里面讲。

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    namespace ztd{
    	using namespace std;
    	typedef long long ll;
    	template<typename T> inline T read(T& t) {//fast read
    		t=0;short f=1;char ch=getchar();double d = 0.1;
    		while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
    		while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
    		if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
    		t*=f;
    		return t;
    	}
    }
    using namespace ztd;
    
    ll dp[20][5];
    ll l, r;
    ll a[20];
    
    ll dfs(int x, int num, bool flag){//分别代表当前搜到的位数、有多少非0数码、是否还在贴着上界进行dfs(贴着就不能进行记忆化)
    	if(!x) return 1;//如果搜完了,搜到了底端,说明当前这个数可以
    	if(!flag && dp[x][num] != -1) return dp[x][num];//搜过了,且现在不贴着上界,就可以用dp记忆化过的东西了。
    	int uplim = flag ? a[x] : 9;//如果现在贴着上界,那么这一位只能不大于上界的这一位,否则随便。
    	ll ret = 0;
    	for(int i = 0; i <= uplim; ++i){
    		if(i == 0) ret += dfs(x-1, num, (flag && i == a[x]));//如果是0就不计入非0数码,
    		else if(num < 3) ret += dfs(x-1, num+1, (flag && i == a[x]));//是的话就计入。
    	}
    	if(!flag) dp[x][num] = ret;//如果当前不贴着上界搜索,当前状态就可以复用,就可以记忆化
    	return ret;
    }
    ll work(ll x){
    	ll t = 0;
    	while(x){//拆位
    		a[++t] = x % 10;
    		x /= 10;
    	}
    	return dfs(t, 0, 1);
    }
    inline void Main(){
    	read(l); read(r);
    	cout << work(r) - work(l-1) << '
    ';
    }
    
    signed main(){
    	memset(dp,-1,sizeof(dp));//由于这道题的状态可以复用,所以在开始初始化一次即可。
    	int T; read(T);
    	while(T--) Main();
     	return 0;
    }
    

    D Vasya and Arrays

    题面

    给你两个数组(a, b),要求把这两个数组各分成 (k) 段(段必须是下标连续的元素),使得 (a,b) 从左到右对应的每一段,里面的元素和相等。求 (k) 的最大值。如果无解,则输出 -1。数组长度 (n, m leq 3 imes 10^5; ;a_i,b_i in[1,10^9])

    题解

    很明显无解当且仅当 (sum a eq sum b) (否则肯定可以把 (a,b) 全部变成一段)。

    可以发现有一个贪心:在分每一个段的时候,找 (a)(b) 的两个前缀,使得这两个前缀的和相等,且这两个前缀尽可能的短,一定是最优解。这是非常显然的,读者自证不难。

    然后我们发现最优解的一个显然的性质:在每一个对应的段的分段点,他们的前缀和相等,且反之亦然。那末我们就可以遍历 (a),对于(a) 里面的每一个元素的前缀和,lower_bound 查找一下 (b) 中有没有前缀和相等的点,有的话,(a,b) 这两个地方就是分段点,没有的话就不是。显然时间复杂度是 (O(n log n)) 的。

    代码

    #include <iostream>
    #include <cstdio>
    namespace ztd{
    	using namespace std;
    	typedef long long ll;
    	template<typename T> inline T read(T& t) {//fast read
    		t=0;short f=1;char ch=getchar();
    		while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
    		while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
    		t*=f; return t;
    	}
    }
    using namespace ztd;
    const int maxn = 3e5+7;
    int n, m, a[maxn], b[maxn];
    ll sa[maxn], sb[maxn];
    signed main(){
    	read(n);
    	for(int i = 1; i <= n; ++i){
    		read(a[i]);
    		sa[i] = sa[i-1] + a[i];
    	}
    	read(m);
    	for(int i = 1; i <= m; ++i){
    		read(b[i]);
    		sb[i] = sb[i-1] + b[i];
    	}
    	if(sa[n] != sb[m]) {
    		cout << -1 << '
    ';
    		return 0;
    	}
    	int ans = 0;
    	for(int i = 1; i <= n; ++i){
    		int tmp = lower_bound(sb+1, sb+m+1, sa[i]) - sb;
    		if(sb[tmp] == sa[i]) ++ans;
    	}
    	cout << ans << '
    ';
    	return 0;
    }
    

    E Covered Points

    题面

    (n;(1 le n le 1000))条线段,问它们覆盖了多少个整点(即 ((x,y)) 都为整数的点)。点的坐标范围是([-10^6,10^6])整数,保证每条线段不会退化成点,保证没有两条线段在同一直线上。

    题解

    暴力。每次加进去一条线段,就算出他所经过的整点的数量(可以通过 (gcd(|A_x-B_x|, |A_y-B_y|)+1) 来计算),然后暴力遍历已经加进去的线,通过形如 (k_1x+b_1=k_2x+b_2) 的一次方程式求出交点的坐标(小技巧:可以把式子化成 ((k_1-k_2)x = b_2-b_1) 后,可以直接通过取模判断 (b_2-b_1) 是否整除于 (k_1-k_2) , 防止精度爆炸)并保证交点确实在线段上而不是只在直线上。时间复杂度 (O(n^2)) ,注意精度。

    代码

    咕了
    

    F Relatively Prime Powers

    题面

    对于一个数 (x),当且仅当将其分解后各个质因数的指数的最大公约数为 (1) 时,我们称这个数是合法的。
    比如:例子:(5(=5^1,gcd(1)=1))(12(=2^2*3^1,gcd(2,1)=1))(72(=2^3*3^2,gcd(3,2)=1)) 是合法的,
    (8(=2^3,gcd(3)=3))(100(=2^2*5^2,gcd(2,2))) 不是合法的。

    (T) 组数据,对于每组数据,询问区间 ([2,n]) 中合法的数的个数。

    题解

    我们容易发现,指数不会超过 (60),因为 (2^{60} > 10^{18})

    很容易知道,所有数都可以表示成 (a ^ b),其中 (a, bin mathbb N^+) ,且 $b = $ 因数个数的 (gcd) 。由前面发现指数上限很小,所以考虑从指数方面入手。我们要求的就是 (b) 最大只能表示成 (1) 的,而这明显难求。所以我们考虑容斥,即求 (b=1, b=2, b=3, dots) 的情况的答案 (ans_b),然后再用容斥系数来容斥,即变成 (ans = p_1ans_1 + p_2ans_2 + dots) 类似的形式。

    从头入手:(b=1) 很明显容斥系数为 (1) (不为 (1) 的话,后面质数什么的你“斥”不掉),(b = 2, b= 3) 容斥系数为 (-1)(b = 4) 的答案由于是 (b=2) 的真子集,所以容斥系数为 (0)(b=5) 时为 (-1)(b = 6) 的时候,由于它的答案恰好是 (b=2,b=3) 的交集,所以容斥系数为 (1)。细细推下去,发现容斥系数是什么?是莫比乌斯函数 (mu) !那么就简单了。考虑每一个指数 (b),考虑它的底数 (a) 最大为多少,排除掉 (1), 然后就乘上一个 (mu) 就完事了。

    那么怎么对于一个 (b)(a) 最大值呢?

    • 我会暴力!

    很明显,TLE。

    • 我会pow加上基本幂运算规则!(a^{frac{1}{x}}) = (sqrt[x]{a})

    但是由于pow的精度感人,你只能达到在第二个点 WA然后无能狂怒半个上午了的好成绩了。

    那么怎么办呢?我们发现问题在于pow的误差是不一定偏大/偏小的,那么我们只要保证他求出来的底数最大值 (a) 永远偏大一些,再用一些常数从误差值开始,用快速幂看有没有超限,来向下找到准确值。这样子就可以杜绝误差了。

    记得快速幂里面用long double,否则你就会和我一样由于爆ll再无能狂怒一个中午了

    时间复杂度 (O(60T))

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    namespace ztd{
    	using namespace std;
    	typedef long long ll;
    	typedef unsigned long long ull;
    	typedef long double db; //一定要用long double! 会爆long long! long double
    	template<typename T> inline T read(T& t) {//fast read
    		t=0;short f=1;char ch=getchar();
    		while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
    		while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
    		t*=f; return t;
    	}
    	inline db ksm(db x, ll y){
            db ret = 1;
            while(y){if(y & 1) ret = ret * x; y >>= 1; x = x*x;}
            return ret;
        }
    }
    using namespace ztd;
    
    int mu[70], prime[70], pcnt;
    bool vis[70];
    inline void get_mu(int uplimit){//预处理莫比乌斯函数
        mu[1] = 1;
        for(register int i = 2; i <= uplimit; ++i){
            if(!vis[i]) prime[++pcnt] = i, mu[i] = -1;
            for(register int j = 1; j <= pcnt && i*prime[j] <= uplimit; ++j){
                vis[i*prime[j]] = 1;
                if(i % prime[j] == 0) break;
                mu[i*prime[j]] = -mu[i];
            }
        }
    }
    ll n;
    inline void Main(){
    	read(n);
    	ll ans = 0;
    	for(register int i = 1; i <= 60; ++i){
    		if((1ll << i) <= n){
    			if(i >= 38){//3^38 > 1e18,而如果能进到循环里面说明2^i <= n,所以这可以卡一些精度
    				ans += mu[i];
    				continue;
    			}
    			//tips : 1.0L 表示 long double
    			ll uplimit = (ll)(pow(n, 1.0L/i)+2);//找到一个近似(必定不比实际小)的上限
    			while(ksm(uplimit,i) > n) --uplimit;//找到实际的上限
    			ans += mu[i] * (uplimit-1);//要排除掉1!
    		}else break;
    	}
    	cout << ans << '
    ';
    }
    
    signed main(){
    	get_mu(65);
    	int T; read(T);
    	while(T--) Main();
    	return 0;
    }
    
  • 相关阅读:
    力扣 227 :基本计算器(II)
    力扣 224 :基本计算器(I)
    力扣 888:公平的糖果棒交换(哈希表法)
    力扣 1047 :删除字符串中的所有相邻重复项
    力扣 1423 :可获得的最大点数
    vue+spreadjs+后台Java实现与服务端交互的导入导出
    webpack 中 require.context() 多个模块的加载
    dwd_fact_coupon_use
    dwd_fact_cart_info
    dwd_fact_order_detail
  • 原文地址:https://www.cnblogs.com/zimindaada/p/CF1036.html
Copyright © 2011-2022 走看看