2019杭电多校第一场补题

A:Blank

题意:
求只由{0,1,2,3}组成的满足m个限制条件的长度为n的数组有多少种,答案对998244353取模。
限制条件为:数组区间 [ l i , r i ] [l_i, r_i] [li,ri]中需要有 x i x_i xi个不同数字。
(n,m<=100)
题解:
d p [ l ] [ k ] [ j ] [ i ] dp[l][k][j][i] dp[l][k][j][i]表示把各个数字最后出现的位置从大到小排序之后分别为 l , k , j , i l,k,j,i l,k,j,i的满足条件的方案种数。
那么通过在 l + 1 l+1 l+1这个位置放不同的数字, d p [ l ] [ k ] [ j ] [ i ] dp[l][k][j][i] dp[l][k][j][i]可以更新 d p [ l + 1 ] [ l ] [ k ] [ j ] , d p [ l + 1 ] [ l ] [ k ] [ i ] , d p [ l + 1 ] [ l ] [ j ] [ i ] , d p [ l + 1 ] [ k ] [ j ] [ i ] dp[l+1][l][k][j],dp[l+1][l][k][i],dp[l+1][l][j][i],dp[l+1][k][j][i] dp[l+1][l][k][j],dp[l+1][l][k][i],dp[l+1][l][j][i],dp[l+1][k][j][i]。如果 l = n l=n l=n,那就更新答案。
还需要考虑限制条件,对所有限制条件按右端点分类,然后对于当前的 d p [ l ] [ k ] [ j ] [ i ] dp[l][k][j][i] dp[l][k][j][i]判断一下是否满足所有右端点在 l l l的限制条件,如果不满足就不用它去更新。
用滚动数组优化空间,注意每次使用完当前的dp值之后要把它清零。

#include<bits/stdc++.h>
#define ll long long
#define P pair<int,int>
using namespace std;
const ll mod = 998244353;
const int maxn = 105;
int n, m;
ll dp[2][maxn][maxn][maxn];
vector<P> lim[maxn];
void init()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) lim[i].clear();
    for(int i = 0; i < m; ++i){
        int l, r, x;
        scanf("%d%d%d", &l, &r, &x);
        lim[r].push_back(P(l,x));
    }
}
void sol()
{
    int cur = 0;
    dp[0][0][0][0] = 1;
    ll ans = 0;
    for(int l = 0; l <= n; ++l){
        for(int k = 0; k <= l; ++k){
            for(int j = 0; j <= k; ++j){
                for(int i = 0; i <= j; ++i){
                    int ok = 1;
                    for(int t = 0; t < lim[l].size(); ++t){//检查限制条件
                        int pos = lim[l][t].first;
                        int x = lim[l][t].second;
                        if(pos <= i){
                            if(x != 4) ok = 0;
                        }
                        else if(pos <= j){
                            if(x!=3) ok = 0;
                        }
                        else if(pos <= k){
                            if(x!=2) ok = 0;
                        }
                        else if(pos <= l){
                            if(x!=1) ok = 0;
                        }
                        if(!ok) break;
                    }
                    if(!ok) {
                        dp[cur][k][j][i] = 0;//注意清空当前值
                        continue;
                    }
                    if(l == n){
                        ans = (ans + dp[cur][k][j][i])%mod;
                    }
                    else{
                        dp[cur^1][k][j][i] = (dp[cur^1][k][j][i] + dp[cur][k][j][i])%mod;
                        dp[cur^1][l][j][i] = (dp[cur^1][l][j][i] + dp[cur][k][j][i])%mod;
                        dp[cur^1][l][k][i] = (dp[cur^1][l][k][i] + dp[cur][k][j][i])%mod;
                        dp[cur^1][l][k][j] = (dp[cur^1][l][k][j] + dp[cur][k][j][i])%mod;
                    }
                    dp[cur][k][j][i] = 0;//注意清空当前值
                }
            }
        }
        cur ^= 1;
    }
    printf("%lld\n", ans);
}
int main()
{
    int T;cin>>T;
    while(T--){
        init();
        sol();
    }
}

B: Operation

题意:
两种操作:
0 l r: 从 a l , a l + 1 . . , a r a_l,a_{l+1}..,a_r al,al+1..,ar中选择任意个,求能组成的最大异或和
1 x:在数组的末尾添加一个数x
题解:
维护n个前缀线性基,即对于第i个线性基,它是由 a 1 , a 2 . . a i a_1,a_2..a_i a1,a2..ai这i个数字构成的。同时维护线性基的每一位出现的最右边的位置,每次插入的时候如果当前插入的数字出现位置比线性基上这一位的位置大,就把它置换出来,接着往下。查询最大值的时候只要看线性基的这一位最右出现位置是否比l大,如果可以,它就可以贡献出这一位。
ac代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 50;
int n, m;
int a[maxn];
struct Linebase{
    int p[31], id[31];
    void clear(){
        memset(p, 0, sizeof p);
        memset(id, 0, sizeof id);
    }
    void insert(int x, int pos)
    {
        for(int i = 30; i >= 0; --i){
            if(!(x>>i)) continue;
            if(!p[i]){
                p[i] = x; id[i] = pos;
                break;
            }
            if(pos > id[i]){//ÈÃ×ó¶ËµÄÍùºóÉÔÉÔ
                swap(x, p[i]);
                swap(id[i], pos);
            }
            x ^= p[i];
        }
    }
    int val(int l)
    {
        int x = 0;
        for(int i = 30; i >= 0; --i){
            if(x&(1<<i)) continue;
            if(p[i] && id[i] >= l) x ^= p[i];
        }
        return x;
    }
}base[maxn];
void init()
{
    scanf("%d%d", &n, &m);
    base[0].clear();
    for(int i = 1; i <= n; ++i){
        scanf("%d", &a[i]);
        base[i] = base[i-1];
        base[i].insert(a[i], i);
    }
}
void sol()
{
    int lastans = 0;
    while(m--){
        int op;
        scanf("%d", &op);
        if(!op){
            int l, r;
            scanf("%d%d", &l, &r);
            l = (l^lastans)%n + 1;
            r = (r^lastans)%n + 1;
            if(l > r) swap(l,r);
            lastans = base[r].val(l);
            printf("%d\n", lastans);
        }
        else{
            n++;
            scanf("%d", &a[n]);
            a[n] ^= lastans;
            base[n] = base[n-1];
            base[n].insert(a[n], n);
        }
    }
}
int main()
{
    int a = 1;
    int T;cin>>T;
    while(T--){
        init();
        sol();
    }
}

I:String

题意:
给一个长度为n的字符串,要求找出一个满足条件的长度为k的子串,要求字典序最小。限制条件为:每个字母都有最少出现次数和最多出现次数限制。
题解:
维护每个字母后面出现的各个字母个数,然后每次贪心的加入可以加入的最小字母。细节看代码。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 50;
char s[maxn];
int num[maxn][26];
int l[26], r[26];
int cnt[26];
queue<int> q[26];
int n, k;
void init()
{
    n = strlen(s);
    for(int i = 0; i < 26; ++i){
        scanf("%d%d", &l[i], &r[i]);
        cnt[i] = 0;
        num[n-1][i] = 0;
        while(q[i].size()) q[i].pop();
    }
    for(int i = n-2; i >= 0; --i){
        for(int j = 0; j < 26; ++j) num[i][j] = num[i+1][j];
        int x = s[i+1] - 'a';
        num[i][x]++;
    }
    for(int i = 0; i < n; ++i){
        int x = s[i] - 'a';
        q[x].push(i);
    }
}
vector<int> ans;
int pos = -1;
void sol()
{
    ans.clear();
    pos = -1;//最后一个加入的字母在原字符串的位置
    while(ans.size() < k){
        int fnd = 0;
        for(int i = 0; i < 26; ++i){
            while(q[i].size() && q[i].front() <= pos) q[i].pop();
            if(!q[i].size()) continue;//没有可加的了
            int t = q[i].front();
            if(cnt[i] + 1 > r[i]) continue;//再加i就超过限制了
            int ok = 1;
            int res = 0;
            int sum = 0;
            for(int j = 0; j < 26; ++j){//检查i之外的字母是否可以满足
                if(i == j) continue;
                if(cnt[j] + num[t][j] < l[j]) {
                    ok = 0; break;
                }
                sum += max(l[j] - cnt[j], 0);//还需要加入的字母j个数
                res += r[j] - cnt[j];//还能加入的字母j个数
            }
            if(ans.size() + 1 + res < k) ok = 0;//剩余可加字母全进来也不够
            if(k < sum + ans.size() + 1) ok = 0;//还需要加入的字母比可容纳字母多
            if(ok){//可以在t位置的字母i
                pos = t;
                cnt[i]++;
                q[i].pop();
                ans.push_back(i);
                fnd = 1;
                break;
            }
        }
        if(!fnd) break;
    }
    if(ans.size() < k){
        printf("-1\n");return;
    }
    for(int i = 0; i < ans.size(); ++i) printf("%c",ans[i]+'a');
    printf("\n");
}
int main()
{
    while(scanf("%s%d", s, &k)!=EOF){
        init();sol();
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值