AtCoder Beginner Contest247(ABCDEF,E两种解法,状压dp+容斥定理)

AtCoder Beginner Contest 247 - AtCoderAtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.https://atcoder.jp/contests/abc247

目录

A - Move Right

B - Unique Nicknames

C - 1 2 1 3 1 2 1

D - Cylinder

E - Max Min

方法1:状压dp

方法2:容斥定理

F - Cards


A - Move Right

读题,将数字向右移动一位,前导为0即可.

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
int main()
{
    string str;
    cin>>str;
    str="0"+str;
    for(int i=0;i<4;i++)
        cout<<str[i];
    return 0;
}

B - Unique Nicknames

该题要求取的绰号和任意下方的人的姓,名都不一样.标记去重判断即可.

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>

using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;

map<string,int>vis;
string a[103],b[103];
int main()
{
    int n,flag=0;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i]>>b[i];
        if(a[i]==b[i])
            vis[a[i]]++;
        else
            vis[a[i]]++,vis[b[i]]++;
    }
    for(int i=0;i<n;i++)
    {
        if(vis[a[i]]>=2&&vis[b[i]]>=2)
        {
            cout<<"No";
            return 0;
        }
    }
    cout<<"Yes";
    return 0;
}

C - 1 2 1 3 1 2 1

这个题意思是可以把x变为字符串x-1  x  x-1的形式,并且一直转化到两段不能转化为止,很容易就联想到大法师(DFS)

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
void dfs(int x)
{
    if(x==1)
    {
        cout<<1;
        return ;
    }
    dfs(x-1);
    cout<<" "<<x<<" ";
    dfs(x-1);
}
int main()
{
    int n;
    cin>>n;
    dfs(n);
    return 0;
}

D - Cylinder

该题题意是给你一定的操作次数,并且含有两种操作,一种是向数组的右侧插入c个值为x的球,另一种操作是在数组左侧取出c个球,并且求出他们的值的和.

可以采用双端队列进行模拟操作.向右插入的操作就插入一个节点,这个节点储存三个值,左右的端点和这个区间的球的值.而第二种操作直接进行模拟,我取出双端队列队首的区间,看能不能取完,不能去玩就继续取,能取完就直接取完计算,因为储存了区间的左右端点和值,那么可以直接用区间长度和值相乘取得和.

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;

const int N = 5e5 + 10, mod = 998244353;
struct node
{
    int l,r,x;
};
deque<node>qu;
signed main()
{
    int q,op,c,x,l=1,sum=0;
    cin>>q;
    while(q--)
    {
        cin>>op;
        if(op==1)
        {
            cin>>x>>c;
            qu.push_back({l,l+c-1,x});
            l=l+c;
        }
        else if(op==2)
        {
            sum=0;
            cin>>c;
            node bef;
            while(c>0)
            {
                bef=qu.front();
                if(c>bef.r-bef.l+1)
                {
                    sum+=(bef.r-bef.l+1)*bef.x;
                    qu.pop_front();
                    c-=bef.r-bef.l+1;
                }
                else if(c<=bef.r-bef.l+1)
                {
                    sum+=c*bef.x;
                    qu.pop_front();
                    bef.l+=c;
                    qu.push_front(bef);
                    c=0;
                }
            }
            cout<<sum<<"\n";
        }
    }
    return 0;
}

E - Max Min

题意:

给你一个长度为n的数组,并且给定最大值x和最小值y.要求求出这个数组中有多少个连续区间满足:包含y和x,并且这个区间内的值都是小于x大于y的.输出满足上面两种条件的连续子区间的数量即可

这两种都难得想,需要一定的思维,我是看了大佬题解才理解的

方法1:状压dp

状压dp的形式为 f [ i ] [ 1/0 ] [ 1/0 ];第一位i的意思为以i结尾的子区间,后面两维分别表示这些区间是否满足包含y和x.这个式子的意思就是看以i结尾的区间内是否满足包含y,x的子区间最大数目.详细的意思可以看代码中的注解.

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
int n,l,r;
int a[200005];
int f[200005][3][3];
signed main()
{
    int ans=0;
    cin>>n>>r>>l;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    f[0][0][0]=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i]<l||a[i]>r)
            continue;
//当前遍历到的数字不符合要求
        int ll=(a[i]==l),rr=(a[i]==r);
//ll,rr意为当前的数字是否为y或者x
        f[i][ll][rr]+=1;
//这个是以i结尾并且只选了当前这个数字a[i]的情况,只选了一个数,所以+1
        for(int j=0;j<2;j++)
            for(int k=0;k<2;k++)
                f[i][ll|j][rr|k]+=f[i-1][j][k];
//遍历所有状态,看把当前遍历到的数字i插入之前的所有情况的子区间中,进行状态转移
//例如f[i-1][1][0]意为以i-1结尾且包含有y但是不包含x的区间个数
//假设当前遍历到了x,那么ll=0,rr=1;
//那么就会转移为f[i][1][1]+=f[i-1][1][0];
//因为在选上当前的a[i]后状态可以如上发生转移
        ans+=f[i][1][1];
//加上符合条件的
    }
    cout<<ans;
    return 0;
}

方法2:容斥定理

首先在了解了基础的容斥的情况下.我们可以计算出区间呢你所有数字的范围都在[ y , x ]之前的子区间个数.比较好算

int get_num(int l,int r)
{
    int ans=0,temp=0;
    for(int i=0;i<n;i++)
    {
        if(a[i]>r||a[i]<l)
        {
            ans+=(temp+1)*temp/2;
            temp=0;
        }
        else
            temp++;
    }
    if(temp)
        ans+=(temp+1)*temp/2;
    return ans;
}

只要遍历一遍,看连续的在范围内的数字有多少之后,分别以第一个数第二个数...为子区间的左端点计算子区间个数即可.满足了在[ y , x ]的范围内之后,只要满足一定包含x,y即可.这里可以画一张图理解:

 三个大圆圈的含义已经写出来了,[y+1,+inf]和[y,x]的相交区域a的意思为[y+1,x],[-inf,x-1]和[y,x]的相交区域b的意思为[y,x-1],那么[y,x]和a不相交的区域的意思就是在[y,x]的范围内包含有y的子区间的集合,而b和[y,x]不相交的区域就是在[y,x]范围内包含有x的子区间的集合,那么可以推出,[y,x]和a,b不相交的区域的即为所求,即在[y,x]范围内但是必定包含x,y的子区间的集合.所以我们就可以用之前的那个方法求出这三块的面积,并且用[y,x]的集合去减去a,b.但是这里会发现有一块c重复减去了两次,所以我们要加上c,c即为在[y,x]的范围内[y+1,x-1]的集合子区间的个数,加上即可.

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
const int N = 5e5 + 10, mod = 998244353;
int n,x,y;
int a[200005];
int get_num(int l,int r)
{
    int ans=0,temp=0;
    for(int i=0;i<n;i++)
    {
        if(a[i]>r||a[i]<l)
        {
            ans+=(temp+1)*temp/2;
            temp=0;
        }
        else
            temp++;
    }
    if(temp)
        ans+=(temp+1)*temp/2;
    return ans;
}
//求区间个数
signed main()
{
    cin>>n>>x>>y;
    for(int i=0;i<n;i++)
        cin>>a[i];
    cout<<get_num(y,x)-get_num(y,x-1)-get_num(y+1,x)+get_num(y+1,x-1);
//容斥核心
    return 0;
}

F - Cards

有n张牌,每个牌有两面,正反两面分别为p,q,且p,q数组都是1-n的全排列.问有多少种选法可以让选出的牌(正反两面都算)包含有1-n的全排列.

这个题涉及到的其实是在数组上面建图的问题

拿例子来说

3

1 2 3

2 1 3

将正反两面的数字练起来可以建立以下的图:

我们将每个集合的方案数相乘即可得到方案数.而每个集合的方案数的计算方法可以由枚举找出规律来,f[1]=1,f[2]=3,f[n]=f[n-1]+f[n-2].(可以枚举试试) .那么用并查集分开集合后计算集合方案相乘即可:

#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
using ll = long long;
const int N = 2e5 + 10, mod = 998244353;
int p[N],q[N],f[N],du[N],fa[N];
int find(int x)
{
    if(x==fa[x])
        return x;
    else
        return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
        fa[fx]=fy;
    return ;
}
signed main()
{
    int n;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&p[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&q[i]);
    f[1]=1,f[2]=3;
    fa[1]=1,fa[2]=2;
    for(int i=3;i<=n;i++)
        f[i]=(f[i-1]+f[i-2])%mod,fa[i]=i;
    for(int i=1;i<=n;i++)
        merge(p[i],q[i]);
    for(int i=1;i<=n;i++)
        du[find(i)]++;
    int ans=1;
    for(int i=1;i<=n;i++)
    {
        if(du[i])
            ans=ans*f[du[i]]%mod;
    }
    printf("%lld",ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值