CCPC网络赛重赛补题

Nun Heh Heh Aaaaaaaaaaa

题面

题目
.

在这里插入图片描述

思路

我们将 n u n h e h h e h nunhehheh nunhehheh a . . . a... a...分离;

n u n h e h h e h nunhehheh nunhehheh看为匹配串,因为 a a a的贡献可以直接用组合数学来求;

f ( i , j ) f(i,j) f(i,j)表示前 i i i个字符中出现了 j j j位的匹配字符

转移方程为 f ( i , j ) = f ( i − 1 , j ) + f ( i − 1 , j − 1 ) f(i,j) = f(i-1,j) + f(i-1,j-1) f(i,j)=f(i1,j)+f(i1,j1)

也就是这一位的匹配字符我们取与不取两种情况;


对于 a a a的个数,假设我们有 k k k a a a,那么我们可以取 1 , 2 , . . . , k 1,2,...,k 1,2,...,k a a a

我们都知道组合数取 0 , 1 , 2 , . . . , k 0,1,2,...,k 0,1,2,...,k个的总和为 2 k 2^k 2k

那么我们只需要取 2 k − C k 0 = 2 k − 1 2^k-C_k^0=2^k-1 2kCk0=2k1即可;


因为我们不能重复计算贡献,因此需要维护一个 l a s t last last

具体看代码…

在这里插入图片描述

Code

二维DP

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

const int MOD = 998244353;

string sub = "0nunhehheh";

char str[N];

ll f[N][15];

int qpow(int x,int y){
    ll base = x,ret = 1;
    while(y){
        if(y&1){
            ret *= base;
            ret %= MOD;
        }
        base *= base;
        base %= MOD;
        y>>=1;
    }
    return ret%MOD;
}
int a[N];
void solve(){
    memset(f,0,sizeof f);
    memset(a,0,sizeof a);
    cin >> (str+1);
    int n = strlen(str+1);
    for(int i=n;i>=1;--i) a[i] = a[i+1] + (str[i] == 'a');
    ll ans = 0;
    ll last = 0;
    for(int i=1;i<=n;++i){
        f[i][1] = f[i-1][1] + ('n' == str[i]);
        f[i][1] %= MOD;
        for(int j=2;j<=9;++j){
            f[i][j] = f[i-1][j] + 
            (sub[j] == str[i])*f[i-1][j-1];
            f[i][j] %= MOD;
        }
        ans += ((f[i][9]-last)%MOD+MOD)%MOD * ((qpow(2,a[i+1])-1+MOD)%MOD);
        last = f[i][9];
        ans %= MOD;
    }
    cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}

滚动数组优化

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

const int MOD = 998244353;

string sub = "0nunhehheh";

char str[N];

ll f[15];

int qpow(int x,int y){
    ll base = x,ret = 1;
    while(y){
        if(y&1){
            ret *= base;
            ret %= MOD;
        }
        base *= base;
        base %= MOD;
        y>>=1;
    }
    return ret%MOD;
}
int a[N];
void solve(){
    memset(f,0,sizeof f);
    memset(a,0,sizeof a);
    cin >> (str+1);
    int n = strlen(str+1);
    for(int i=n;i>=1;--i) a[i] = a[i+1] + (str[i] == 'a');
    ll ans = 0;
    ll last = 0;
    for(int i=1;i<=n;++i){
        f[1] = f[1] + (sub[1] == str[i]);
        f[1] %= MOD;
        for(int j=9;j>=2;--j){
            f[j] = f[j] + 
            (sub[j] == str[i])*f[j-1];
            f[j] %= MOD;
        }
        ans += ((f[9]-last)%MOD+MOD)%MOD * ((qpow(2,a[i+1])-1+MOD)%MOD);
        last = f[9];
        ans %= MOD;
    }
    cout << ans << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}

Jumping Monkey

题面

题目

在这里插入图片描述
在这里插入图片描述

思路

首先题目给我们的是一棵树,也就是一整个连通块

显然任何一个点都能到连通块中最大的点;

那么连通块中最大的点对于其他的点贡献为 1 1 1


现在我们将最大的点删去,分裂成若干个连通块;

可以发现,每个连通块都是上面问题的一个子问题,可以递归的解决;

那么对于每个连通块,过程如下

  1. 找到最大的点
  2. 将这个点所在的连通块中除了最大点之外所有的点贡献+1
  3. 删去这个最大的点

我们发现这个过程并不容易实现;

因为次小的点可以给最小的点一个贡献,第三小的点可以给次小和最小的点一个贡献,依次类推;

那我们按照权值升序排序,反向建树;

那么每个点的答案就是其在新树中的深度了;

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

vector<int> tr[N],G[N];
int n,dep[N],p[N];

struct Node{
    int val,x;
}a[N];

int _find(int x){
    if(x == p[x]) return x;
    return p[x] = _find(p[x]);
}

void clear(){
    for(int i=1;i<=n;++i){
        tr[i].clear();
        G[i].clear();
        //这里之所以p[i]写0 是因为p[i]同时兼顾了点i是否访问过的作用
        dep[i] = p[i] = 0; 
    }
}
void dfs(int u,int fa){
    dep[u] = dep[fa] + 1;
    for(auto v : G[u]){
        if(v != fa){
            dfs(v,u);
        }
    }
}
void solve(){
    cin >> n;
    clear();
    for(int i=1,u,v;i<=n-1;++i){
        cin >> u >> v;
        tr[u].push_back(v);
        tr[v].push_back(u);
    }
    for(int i=1;i<=n;++i){
        cin >> a[i].val;
        a[i].x = i;
    }
    sort(a+1,a+1+n,[](Node p,Node q)->bool{
        return p.val < q.val;
    });
    for(int i=1;i<=n;++i){
        int u = a[i].x;
        p[u] = u;//表示访问过了 同时初始化
        for(auto v : tr[u]){
            if(p[v]){
                int fv = _find(v);
                p[fv] = u;
                //v已经遍历过了
                G[u].push_back(fv);
            }
        }
    }
    dfs(a[n].x,0);
    for(int i=1;i<=n;++i) cout << dep[i] << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        solve();
    }
    return 0;
}

Monopoly

题目

在这里插入图片描述
在这里插入图片描述

思路

很容易想到,我们应该以 n n n个数为一个周期来考虑;

s i + n = s i + S s_{i+n}=s_i+S si+n=si+S

S = 0 S=0 S=0,我们只能从前缀和中找一个最近的点;


S > 0 S>0 S>0,如下图

在这里插入图片描述
其中有一点需要特别注意,因为前缀和中可能有多个相同的余数;

显然 s j s_j sj越大,循环的轮次 k k k就越小;

如果都一样大,那么应该取最靠前的;


在这里插入图片描述

Code解释

代码中的unordered_map<ll,set<pil,CMP> >um;是哈希表;

对于每个余数来说,都对应一个set,用来找位置最靠前,值最大的 s j s_j sj

时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn)


因为lower_bound只能找≥x的值,因此我们需要给 x x x以及 s j s_j sj取反

Code

#include <iostream>
#include <unordered_map>
#include <set>
#include <utility>
#include <cstdio>
#include <map>
#include <algorithm>

using namespace std;

typedef long long ll;

typedef pair<ll,int> pil;

const int N = 1e5 + 10;

ll s[N];

struct CMP{
    bool operator()(pil a,pil b){
        if(a.first == b.first) return a.second < b.second;
        return a.first < b.first;
    }
};

unordered_map<ll,set<pil,CMP> >um;

map<ll,int> ma;
int n,m;
ll x,r;
void work_0(){
    for(int i=1;i<=m;++i){
        while(m--){
            cin >> x;
            if(x == 0) cout << 0 << '\n';
            else if(ma.count(x)){
                cout << ma[x] << '\n';
            }else cout << -1 << '\n';
        }
    }
}
ll ans;
void solve(){
    ma.clear();
    um.clear();
    cin >> n >> m;
    for(int i=1,u;i<=n;++i){
        cin >> u;
        s[i] = s[i-1] + u;
        //取最近的,否则WA
        if(!ma.count(s[i])) ma[s[i]] = i;
    }
    if(s[n] == 0){
        work_0();
        return;
    }
    bool negative = false;
    if(s[n] < 0){
        negative = true;
        for(int i=1;i<=n;++i) s[i] = -s[i];
    }
    for(int i=1;i<=n;++i){
        r = ((s[i]%s[n])+s[n])%s[n];
        //值大的排前面 如果值相同,那么位置越靠前越好
        um[r].insert({-s[i],i});
    }
    while(m--){
        cin >> x;
        if(x == 0){
            cout << 0 << '\n';
            continue;
        }
        if(negative) x = -x;
        r = ((x%s[n])+s[n])%s[n];
        if(um.count(r)){
            auto ptr = um[r].lower_bound({-x,0});
            if(ptr != um[r].end()){
                ans = (*ptr).second;
                        //注意 这里(*ptr).first 是-s(i)
                        //因此是加
                        //轮次 = (x-S_j)/s[n]
                ans += n*(x + (*ptr).first)/s[n];
                cout << ans << '\n';
            }else{
                cout << -1 << '\n';
            }
        }else{
            cout << -1 << '\n';
        }
    }
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值