AtCoder-ABC-179(A-F)

A

输出字符串,以s结尾就加es,否则加s。

B

判断是否有连续三对相同的数。

C

给定N,求多少个正数三元组(A,B,C)满足AB+C=N。预处理因子个数然后暴力枚举C。

D

给定一些区间,从数轴的1开始,每次可以选择任意一个区间的任意一个数,向右走这么多步,问走到N的方案数。

简单的方案数DP,不同的是从前面的某一段区间转移而来,所以枚举区间,然后用前缀和优化即可,再优化也可以先把区间进行合并。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const long long MOD=998244353;
int n,k;
long long dp[N],pre[N];
struct node{
    int l,r;
    bool operator<(const node& rhs)const{
        if(l!=rhs.l){
            return l<rhs.l;
        }else{
            return r<rhs.r;
        }
    }
}a[15];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=k;i++){
        scanf("%d%d",&a[i].l,&a[i].r);
    }
    sort(a+1,a+1+k);
    vector<node> b;
    int lastl=-1;
    int lastr=-1;
    int cur=1;
    while(cur<=k){
        if(a[cur].l<=lastr){
            lastr=a[cur].r;
        }else{
            if(lastr!=-1){
                b.push_back(node{lastl,lastr});
            }
            lastl=a[cur].l;
            lastr=a[cur].r;
        }
        cur++;
    }
    if(lastr!=-1){
        b.push_back(node{lastl,lastr});
    }
    dp[1]=1LL;
    int siz=b.size();
    for(int i=1;i<=n;i++){
        for(int j=0;j<siz;j++){
            int l=max(0,i-b[j].l);
            int r=max(0,i-b[j].r-1);
            dp[i]=(dp[i]+pre[l]-pre[r]+MOD)%MOD;
        }
        pre[i]=pre[i-1]+dp[i];
    }
    printf("%lld\n",dp[n]);
    return 0;
}

E

给定首项x和递推式$a_n=(a_{n-1}^2)%m$,求前n项和。

注意到m的范围是1e5,所以很明显每一项都不会超过1e5,所以肯定会存在循环节,所以暴力找出循环节然后用前缀和计算一下剩下的小部分即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+50;
ll n,x,m;
ll a[N],p[N];
int loc[N];
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%lld%lld%lld",&n,&x,&m);
    a[1]=x;
    p[1]=x;
    ll ans=x;
    memset(loc,-1,sizeof(loc));
    loc[x]=1;
    for(int i=2;i<=n;i++){
        a[i]=(a[i-1]*a[i-1])%m;
        p[i]=p[i-1]+a[i];
        ans+=a[i];
        if(loc[a[i]]!=-1){
            ll cy=p[i]-p[loc[a[i]]];
            ll num=(n-loc[a[i]])/(i-loc[a[i]]);
            ll rem=n-loc[a[i]]-num*(i-loc[a[i]]);
            ans+=cy*(num-1);
            ans+=p[loc[a[i]]+rem]-p[loc[a[i]]];
            break;
        }
        loc[a[i]]=i;
    }
    printf("%lld\n",ans);
    return 0;
}

F

有个n行n列的网格图,初始中间n-2的方阵是黑色,最下边的行和最右边的列是白色,有两种操作,选择一列或者一行变成白色直到第一个原先已经白色的格子,给定一些操作,问最后黑色格子数量。

最重要的一点是要从全局来看这个网格图,每一次操作其实都是对中间n-2行n-2列的黑色方阵的压缩,比如对第x列操作,那么对于x列右边的列,横向行的操作就不会再影响到了。

具体做法如下,行和列分别维护:从上到下第一个被操作(全部变成白色)的行,这是因为上一段提到的,相当于一个收缩隔离的作用,在这个行以下的,状态不会再被改变,即黑色格子数量是一定的,可以通过第二个维护的量来记录;每一行第一个白色格子位置,显然这个白色格子就是列操作产生的,这样子当对这一行进行操作,就可以直接算出消失的黑色格子数量(注意比如[黑 白 黑]的清空,行的操作也只会影响第一个黑,但是这不会影响答案,第二个黑会在列的操作时被影响)。列同理。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+50;
int n,q,o,x;
// a[i]表示第i列第一个白色,b[i]表示第i行第一个白色
int a[N],b[N];
int main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        a[i]=n;
        b[i]=n;
    }
    // r表示从左到右第一个被竖切全白的列
    // d表示从上到下第一个被横切全白的行
    int r=n;
    int d=n;
    long long ans=(long long)(n-2)*(n-2);
    while(q--){
        scanf("%d%d",&o,&x);
        if(o==1){
            if(x<r){
                for(int i=x;i<r;i++){
                    a[i]=d;
                }
                // 更新从左到右第一个全白列
                r=x;
            }
            // a[x]是x列从上到下第一个白色,所以前面a-1个除了第一行(没有颜色),其他(a-2)个都是黑色,将变成白色
            ans-=(long long)(a[x]-2);
        }else{
            if(x<d){
                for(int i=x;i<d;i++){
                    b[i]=r;
                }
                d=x;
            }
            ans-=(long long)(b[x]-2);
        }
    }
    printf("%lld\n",ans);
    return 0;
}
目录