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题是个纯思维题,一看题解恍然大悟╰(*°▽°*)╯。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值