2019杭电多校第一场

2019杭电多校第一场

有点自闭,本来应该昨天写的,拖到了今天

1001. Blank

upsolved

题意是在\(n\)个位置上填数,只能填\(0,1,2,3\)这四种,然后有\(m\)个限制条件,限制的是区间不同数的个数,求填数方案数\(1<=n,m<=100\)

看着官方题解一下就明白了

\(dp[i][j][k][t]\)代表填完前\(t\)个数之后四种数的出现位置从小大大排序分别是\(i,j,k,t\)的方案数,限制条件加到右端点里判断就好了,\(t\)那一维可以滚动掉,时间复杂度\(O(Tn^4)\)(因为是\(i,j,k,t\)有序的所以常数比一小得多),空间复杂度\(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;

const int N = 101, mod = 998244353;

int dp[N][N][N][2], T, n, m, l, r, x, ans;

vector<pair<int, int>> cons[N];

void addmod(int &x, int y) {
    x += y;
    if(x >= mod)
        x -= mod;
}

int main() {
    scanf("%d", &T);
    while(T--) {
        ans = 0;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; ++i) {
            scanf("%d%d%d", &l, &r, &x);
            cons[r].push_back({l, x});
        }
        for(int i = 1; i <= n; ++i)
            cons[i].push_back({i, 1});
        memset(dp, 0, sizeof(dp));
        dp[0][0][0][0] = 1;
        for(int cur = 1; cur <= n; ++cur) {
            int o = cur & 1;
            for(int i = 0; i <= cur; ++i) 
                for(int j = i; j <= cur; ++j)
                    for(int k = j; k <= cur; ++k)
                        dp[i][j][k][o] = 0;
            for(int i = 0; i <= cur; ++i) 
                for(int j = i; j <= cur; ++j)
                    for(int k = j; k <= cur; ++k) {
                        addmod(dp[j][k][cur - 1][o], dp[i][j][k][o ^ 1]);
                        addmod(dp[i][k][cur - 1][o], dp[i][j][k][o ^ 1]);
                        addmod(dp[i][j][cur - 1][o], dp[i][j][k][o ^ 1]);
                        addmod(dp[i][j][k][o], dp[i][j][k][o ^ 1]);
                    }
            for(int i = 0; i <= cur; ++i) {
                for(int j = i; j <= cur; ++j)
                    for(int k = j; k <= cur; ++k) 
                        for(auto c : cons[cur]) {
                            l = c.first, x = c.second;
                            if((i >= l) + (j >= l) + (k >= l) + (cur >= l) != x)
                                dp[i][j][k][o] = 0;
                        }
            }
        }
        for(int i = 0; i <= n; ++i)
            for(int j = i; j <= n; ++j) 
                for(int k = j; k <= n; ++k)
                    addmod(ans, dp[i][j][k][n & 1]);
        printf("%d\n", ans);
        for(int i = 1; i <= n; ++i)
            cons[i].clear();
    }
    return 0;
}

1002. Operation

upsloved

强制在线,每次往数列末加数或询问区间子集异或最大值

究极自闭,cf1100F原题(有在线做法),这题我寒假的时候还做过。。。然后看了五个小时没看出来。。。

就是记录\(n\)个前缀线性基,线性基额外维护当前位置的数的插入位置,插入的时候贪心地取最大值,具体可以搜cf1100F的题解

1004. Vacation

solved at 02:40

\(n+1\)辆车,每辆车有长度最大速度以及离终点的距离,不能超车,可以贴着前面车走,问离终点最远的车到达终点的时间

队友想出了二分答案的做法

有线性做法:离终点最远的车肯定最后是和其他若干辆车(可能只有它自己)连在一起过终点的,\(O(n)\)枚举取最大值就好了

1005. Path

solved at 02:06

有一个有向图,拆除一条边的代价是这条边的长度,要求使用最小的代价使得从\(1\)\(n\)的最短路变长

把最短路上的边拿出来建一张新图跑最小割就行了

队友建新图的时候点没标记疯狂\({\rm MLE}\)...

1006. Typewriter

upsolved

用最小的代价构建目标字符串,你一开始有一个空串,有两种操作

\(1\):花费\(p\)的代价往你的串最后加一个字符

\(2\):花费\(q\)的代价往你的串最后加一个你的串的任意子串

比赛上来读了个假题。。。

维护两个位置\(i, j\)

\(s[1:j-1]\)插入\({\rm SAM}\),然后看\(s[j:i]\)是不是\(s[1:j-1]\)的子串

不是就插入\(s[j], j++\)

是就\(dp[i] = min(dp[i - 1] + p, dp[j-1]+q])\)

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

int nxt[N << 1][26], len[N << 1], par[N << 1];
int sz, last;
char s[N];
int n, p, q;
long long dp[N];

int newnode(int l) {
    len[sz] = l;
    memset(nxt[sz], 0, sizeof(nxt[sz]));
    return sz++;
}

void init() {
    sz = last = 0;
    par[sz] = -1;
    newnode(0);
}

void add(int x) {
    int p = last, np = newnode(len[last] + 1);
    for(; ~p && !nxt[p][x]; p = par[p]) nxt[p][x] = np;
    if(p == -1) {
        par[np] = 0;
    }
    else {
        int q = nxt[p][x];
        if(len[q] == len[p] + 1) {
            par[np] = q;
        }
        else {
            int nq = newnode(len[p] + 1);
            memcpy(nxt[nq], nxt[q], sizeof(nxt[nq]));
            par[nq] = par[q];
            par[q] = par[np] = nq;
            for(; ~p && nxt[p][x] == q; p = par[p])
                nxt[p][x] = nq;
        }
    }
    last = np;
}


int main() {
    while(~scanf("%s%d%d", s + 1, &p, &q)) {
        init();
        n = strlen(s + 1);
        int cur = 1, now = 1, pos = 0;
        while(cur <= n) {
            int x = s[cur] - 'a';
            if(nxt[pos][x]) {
                pos = nxt[pos][x];
                dp[cur] = min(dp[cur - 1] + p, dp[now - 1] + q);
                cur++;
            }
            else if(cur == now) {
                add(x);
                dp[cur] = dp[cur - 1] + p;
                cur++;
                now++;
            }
            else {
                add(s[now++] - 'a');
                int tlen = cur - now;
                while(~pos && ~par[pos] && len[par[pos]] >= tlen)
                    pos = par[pos];
            }
        }
        printf("%lld\n", dp[n]);
    }
    return 0;
}

1009. String

upsolved

赛中队友说可做,只是我一直在1002...我真菜

选择一个给定字符串的一个长度为\(k\)的子序列构成目标串,要求字典序最小,同时给出了\(26\)种字母在新串中出现次数的上界和下界

直接贪心就完事了。。。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10, knd = 26;

char s[N];
int L[knd], R[knd], cnt[N][knd], c[knd], n, k, nxt[N][knd], pos, tmp, tot;
char ans[N];

bool check(int p, int now) {
    if(p < 0) return false;
    int x = s[p] - 'a', res = 0, ret = 1;
    if(c[x] == R[x]) return false;
    c[x]++;
    for(int i = 0; i < knd; ++i) {
        if(L[i] > c[i]) res += L[i] - c[i];
        if(cnt[p][i] - (x == i) < L[i] - c[i]) {
            ret = 0;
            break;
        }
    }
    if(res > k - now) 
        ret = 0;
    c[x]--;
    return ret;
}

int main() {
    while(~scanf("%s%d", s, &k)) {
        tot = 0;
        n = strlen(s);
        for(int i = 0; i < knd; ++i) {
            scanf("%d%d", &L[i], &R[i]);
            tot += R[i];
        }
        memset(nxt[n], -1, sizeof(nxt[n]));
        memset(cnt[n], 0, sizeof(cnt[n]));
        memset(c, 0, sizeof(c));
        for(int i = n - 1; ~i; --i) {
            memcpy(nxt[i], nxt[i + 1], sizeof(nxt[i + 1]));
            memcpy(cnt[i], cnt[i + 1], sizeof(cnt[i + 1]));
            nxt[i][s[i] - 'a'] = i;
            cnt[i][s[i] - 'a']++;
        }
        pos = -1;
        for(int i = 0; i < knd; ++i) {
            if(check(nxt[0][i], 1)) {
                c[i]++;
                pos = nxt[0][i] + 1;
                ans[0] = 'a' + i;
                break;
            }
        }
        if(pos == -1 || tot < k) {
            puts("-1");
            continue;
        }
        for(int i = 1; i < k; ++i) {
            for(int j = 0; j < knd; ++j) {
                if(c[j] == R[j]) continue;
                if(check(nxt[pos][j], i + 1)) {
                    c[j]++;
                    pos = nxt[pos][j] + 1;
                    ans[i] = 'a' + j;
                    break;
                }
            }
        }
        ans[k] = '\0';
        printf("%s\n", ans);
    }
    return 0;
}

1012. Sequence

NTT

参考博客

1011,1013都是队友负责的方向。。

这场打的真是菜,全场没什么贡献,以后学了什么新东西还是应该记录下来啊。。。

转载于:https://www.cnblogs.com/tusikalanse/p/11234313.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值