AtCoder - ABC 178 - C~F

C - Ubiquity(容斥原理/DP)

题意:

求满足以下条件的长为 n 的不同序列的个数:
1.0 ≤ a_{i} ≤9
2.序列中至少有一个a_{i} =0
3.序列中至少有一个 a_{i}=9

答案对 10^{9}+7 取模。

数据范围:

1 ≤ N ≤ 10^{6}

思路1(容斥原理):

有0有9的 = 总的 - 无0的 - 无9的 + 无0无9的

Code1:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;

int qmi(int a,int k,int p)
{
    int res = 1;
    while(k)
    {
        if(k & 1)res = res * a % p;
        k >>= 1;
        a = a*a%p;
    }
    return res;
}

void solve()
{
    cin >> n;
    if(n == 1)cout << 0 << endl;
    else cout << ((qmi(10,n,mod) - 2*qmi(9,n,mod) + qmi(8,n,mod))% mod+mod)% mod << endl;
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

思路2(DP):

状态表示:f[i,j,k]表示总共 i 个数的序列。j = 1 表示出现0,j = 0 表示没有出现 0;k = 1 表示出现9,k = 0 表示没有出现9。最终求的答案是 f[n,1,1]

集合划分:前 i 个数的有0有9的序列可以由前 i - 1 个数的序列状态转移得到——
如果前 i - 1 个数的序列有0有9,则第 i 个数可取0~9;
如果前 i - 1 个数的序列有0无9,则第 i 个数一定取9;
如果前 i - 1 个数的序列有0无9,则第 i 个数一定取9;
如果前 i - 1 个数无0无9,则无法由该状态转移得到。

所以 f[i][1][1] = f[i-1][1][0] + f[i-1][0][1] + f[i-1][1][1]*10

可以看出,我们需要算出 f[i-1,1,0] 和 f[i-1,0,1] 如何转移得到的,同理:

对于前 i 个数有0无9的 f[i,1,0]——
如果前 i - 1 个数的序列无0无9,则第i个数一定是0;
如果前 i - 1 个数的序列有0无9,则第i个数可取0~8;
如果前 i - 1 个数有0有9或者前 i - 1 个数无0有9,不能转移得到。

所以 f[i][1][0] = f[i-1][0][0] + f[i-1][1][0]*9

对于前i个数无0有9的 f[i,1,0]——
如果前i-1个数的序列无0无9,则第i个数一定是9;
如果前i-1个数的序列无0有9,则第i个数可取1~9;
如果前i-1个数有0有9或者前i-1个数有0无9,不能转移得到。

所以 f[i][0][1] = f[i-1][0][0] + f[i-1][0][1]*9

再考虑前i个数无0无9,只能由前 i - 1 个数的无0无9的序列转移得到,第 i 个数可取1~8

所以 f[i][0][0] = f[i-1][0][0]*8 

所以我们需要将f[i,0,0],f[i,1,0],f[i,0,1],f[i,1,1]的转移方程都列出最终答案为 f[n,1,1]。

注意取模

Code2:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;
int a[N];
int f[N][2][2];                     //f[i,j,k]表示前i个数;j=1表示出现0,j=0表示没有出现0;k=1表示出现9,k=0表示没有出现9

void solve()
{
    cin >> n;
    f[0][0][0] = 1;                 //初始时没有数,满足没有0没有9.相当于空序列这一种情况,初始化为1
    for(int i = 1; i <= n; i++)
    {
        f[i][0][0] = f[i-1][0][0]*8 % mod;                                          //前i个数既没有0也没有9时,第i个数可取1~8,前i-1个数也满足无0无9,f[i-1][0][0]*8
        f[i][0][1] = (f[i-1][0][0] + f[i-1][0][1]*9)%mod;                           //前i个数无0有9,以前i-1个数是否出现9为划分依据:如果前i-1个数无0无9,那么第i个数必须为9,f[i-1,0,0];前i-1个数出现9,第i个数可取1~9,f[i-1,0,1]*9
        f[i][1][0] = (f[i-1][0][0] + f[i-1][1][0]*9)%mod;                           //同上
        f[i][1][1] = ((f[i-1][1][0] + f[i-1][0][1])%mod + f[i-1][1][1]*10)%mod;     //前i个数有0有9:前i-1个数无0有9,第i个数是0,f[i-1,0,1];前i-1个数有0无9,第i个数是9,f[i-1,1,0];前i-1个数有0有9,第i个数可取0~9,f[i-1][1][1]*10
    }
    cout << f[n][1][1] % mod << endl;  //前n个数有0有9
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

D - Redistribution(找规律/DP/组合数学)

题意:

给出一个正整数 S,问有多少序列满足 a_{i} ≥ 3,且序列和为 s,答案对 10^{9}+7 取模。

数据范围:

1 ≤ S ≤ 2000

思路1(找规律):

找规律:n = 1,2  1
                 3         1
                 4         1
                 5         1
                 6        1+1=a[5]+a[3]=2(6,3 3)
                 7        1+2=a[6]+a[4]=3(7,3 4,4 3)
                 8        1+3=a[7]+a[5]=4(8,3 5,5 3,4 4)
                 9        1+4+1=a[8]+a[6]=6(9,3 6,4 5,5 4,6 3,3 3 3)
                …………
                 n         a[n] = a[n-1]+a[n-3]

Code1:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;
int a[N];

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        if(i < 3)a[i] = 0;
        else if(i == 3 || i == 4 || i == 5)a[i] = 1;
        else a[i] = (a[i-1] + a[i-3]+mod)%mod;
    }
    cout << a[n] % mod << endl;
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

思路2(DP):

状态表示:f[i] 表示序列和为 i 时能划分得到的不同序列的个数。最终答案为 f[s]。

集合划分:将 i 划分成成不同序列时,以序列最后一个数为依据进行划分,最后一个数的取值范围为 3~i 。同时考虑当 i >= 3 时,i 本身可以当做一个长度为 1 的序列,所以先初始化 f[i] = 1,再判断如果 i < 3,则 f[i] = 0;否则 f[i] += f[i-k](k = 3,4,……,i)。

注意取模。

Code2:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;
int f[N];         //f[i]表示序列和为i时能划分得到的序列个数

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)f[i] = 1;          //i本身可以当做一个长度为1的序列,初始化f[i]=1
    for(int i = 1; i <= n; i++)
    {
        if(i < 3)f[i] = 0;
        else{
            for(int j = 3; j <= i; j++)
                f[i] = (f[i] + f[i-j]+mod)%mod;    //和为i时,以划分的最后一块的值为依据进行划分,该值为3~i
        }
    }
    cout << f[n] << endl;
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

思路3(组合数学):

题意相当于:有 n 个球,把它们放在 m 个盒子里,可以有空盒,问有多少种划分方法?

先考虑最多能划分为 k = n/3 组,我们只要对长度为 [1,k] 的组进行计算,最后把每组的答案数累加即可。

由于每组最小为 3,所以 k 组最低需要 3*k ,将剩下 n − 3*k 分给 k 个组,组可以为空,这就是个经典的组合数学题——相当于有 n 个球,把它们放在 m 个盒子里,可以有空盒,问问有多少种划分方法?

如果没有空盒,我们考虑隔板法:n 个球总共 n - 1 个隔间,在这里面选 m - 1 个隔间放挡板,将 n个球分成 m 组,即C_{n-1}^{m-1}
如果有空盒呢,我们先多加上 m - 1 个球,总共 n + m - 1 个球,在这 n + m - 1 个球中选 m - 1 个球作为隔板,将 n 个球分成 m 组,即 C_{n+m-1}^{m-1}

所以答案为C_{n-3k+k-1}^{k-1} = C_{n-2k-1}^{k-1} 

实现:预处理阶乘和阶乘的逆元即可。C_{n-2k-1}^{k-1}枚举k从1~n/3。

Code3:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;
int fac[N],infac[N];

//快速幂
int qmi(int a,int k,int p)
{
    int res = 1;
    while(k)
    {
        if(k & 1) res = res*a%p;
        k >>= 1;
        a = a*a%p;
    }
    return res;
}

//预处理阶乘和阶乘的逆元
void init()
{
    fac[0] = infac[0] = 1;
    for(int i = 1; i <= N; i++)
    {
        fac[i] = fac[i-1]*i%mod;
        infac[i] = qmi(fac[i],mod-2,mod)%mod;
    }
}

//求组合数C(a,b)=a!/b!*(a-b)!
int C(int a,int b)
{
    return fac[a]*infac[b]%mod*infac[a-b]%mod;
}

void solve()
{
    init();
    cin >> n;
    int k = n/3;                      //最多分为k组
    int res = 0;
    for(int i = 1; i <= k; i++)
    {
        res = (res + C(n-2*i-1,i-1)+mod)%mod; //C(n-2i-1,i-1)枚举能分成的组数i从1~k
    }
    cout << res << endl;
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

E - Dist Max(结论题)

题意:

给出 n 个二维平面中的点,问两点间的最远曼哈顿距离(点(xi,yi)与点(xj,yj)的曼哈顿距离 |xi-xj| + |yi-yj|)。

数据范围:

2 ≤ N ≤ 2 × 10^{5}

1 ≤ xi​,yi ​≤ 10^{9}

思路:

最远曼哈顿距离

简单的说,曼哈顿距离的情况很多,但都可以归类为|(xi+yi)−(xj+yj)|,|(xi−yi)−(xj−yj)|。
预处理所有坐标的 x + y 和 x - y 。用最大值-最小值,最后再取最大值即可。

Code:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;
vector<int>a,b;

void solve()
{
    cin >> n;
    a.resize(n);
    b.resize(n);
    for(int i = 0; i < n; i++)
    {
        int x,y;
        cin >> x >> y;
        a[i] = x+y;
        b[i] = x-y;
    }    
    sort(a.begin(),a.end());
    sort(b.begin(),b.end());
    cout << max(*a.rbegin() - *a.begin(),*b.rbegin() - *b.begin()) << endl;
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

F - Contrast(思维)

题意:

给出两个非递减序列 a 和 b,问能否重排 b 使得 ai≠bi 。

数据范围:

1 ≤ N ≤ 2 × 10^{5}

1 ≤ Ai​,Bi ​≤ N

思路:

参考博客

将序列 b 反转,因为 a 为非递减序列,b 为非递增序列,所以序列中至多存在一段区间 [l,r] 满足 a[l~r] = b[l~r] = c。

如果区间外的某个 bj 与区间内的 bi 调换后满足题意,那么 aj≠bi, bj≠ai,即 aj≠c 且 bj≠c 。

如果这样的 bj 个数小于区间长度则无解,否则输出调换后的序列 b 即可。

实现:将b数组反转后,遍历a,b数组,找出c;再找出区间[l,r]后,遍历b数组找可以替换的值。如果最终有值找不到可以替换的值,无解。

Code:

#include<bits/stdc++.h>
using namespace std;

#define fi first
#define se second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 1e6+10, M = 3010, INF = 0x3f3f3f3f, mod = 1e9+7;

typedef pair<int, int>PII;

int n;
vector<int>a,b;

void solve()
{
    cin >> n;
    a.resize(n),b.resize(n);
    
    for(int i = 0; i < n; i++)cin>>a[i];
    for(int i = 0; i < n; i++)cin >> b[i];
    reverse(b.begin(),b.end());                 //将b数组逆序
    
    int c = -1;
    for(int i = 0; i < n; i++)
    {
        if(a[i] == b[i])
        {
            c = a[i];                           //找到两序列重合的那段区间的数c
            break; 
        }
    }
    int l = n,r = -1;
    for(int i = 0; i <n; i++)
    {
        if(a[i] == c && b[i] == c)
        {
            l = min(l,i);                      //l为该区间的左端点
            r = max(r,i);                      //r为该区间的右端点
        }
    }
    for(int i = 0;i < n; i++)
    {
        if(a[i]!=c && b[i]!=c && l <= r)
        {
            swap(b[i],b[l]);                    //遍历b数组,找能与区间替换的数
            l++;
        }
    }
    if(l <= r)cout << "No" << endl;            //l<=r说明遍历完了b数组,找不到可以替换的了
    else
    {
        cout << "Yes" << endl;
        for(int i = 0; i < n; i++) cout << b[i] <<' ';
        cout << endl;
    }
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:这次的题基本上都是思维题。

题目注意点:
1.C题:的容斥原理运用不太熟练,当时写时迷糊了一会才搞对(太菜了)。下次再遇见可以考虑画图。

2.D题给我的收获满大的,首先实在没思路就找规律的思维要有;这题DP也是很基础的DP,正好给好久没写dp的我回忆回忆dp;然后是组合数学的方法,有没有空盒这块犹记得高中几何课学过,但是忘记了,也要捡起来捡起来。

3.E题是个结论题,记一下记一下。

4.F题是个纯思维题,一看题解恍然大悟╰(*°▽°*)╯。

### AtCoder ABC229_A 题目解析与代码实现 AtCoder Beginner Contest (ABC) 是一项面向初学者的编程竞赛,通常每道题目都具有清晰的问题描述以及适中的难度。对于 ABC229_A 的解决方案或代码实现,以下是详细的分析和解答。 #### 问题概述 假设 ABC229_A 的问题是关于字符串处理或者简单的算术运算(这是许多 A 类问题的特点)。以下是一个通用的例子:给定两个整数 `A` 和 `B`,计算它们的总和并输出结果。 #### 解决方案说明 解决此类问题的关键在于理解输入格式、数据范围以及如何高效地完成基本操作。Python 提供了简洁而强大的工具来快速解决问题[^1]。 ```python import sys def solve(): input_data = sys.stdin.read() lines = input_data.splitlines() # 假设第一行为 A B 格式的输入 A, B = map(int, lines[0].split()) result = A + B print(result) if __name__ == "__main__": solve() ``` 上述代码展示了如何读取标准输入并将结果打印到标准输出。通过使用 `sys.stdin.read()` 方法可以一次性获取所有输入内容,并利用 Python 的内置函数进行必要的转换和计算。 #### 关键点解释 - **输入/输出管理**:在竞赛环境中,高效的 I/O 处理至关重要。上例中采用了 `sys.stdin.read()` 来替代传统的 `input()` 函数,从而提升性能。 - **错误预防**:确保变量类型正确无误,尤其是在涉及多种数据类型的混合运算时。 如果具体问题涉及到 URL 编码解码,则可参考如下方法: ```python import urllib.parse encoded_url = 'https://atcoder.jp/contests/abc229/tasks/abc229_a' decoded_url = urllib.parse.unquote(encoded_url) print(decoded_url) # 输出原始URL地址 ``` 此部分适用于需要对特殊字符编码的情况,例如提交表单参数或其他网络请求场景下的预处理工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值