20230703测试

A

我不打了,但是考场上没想起来排列 stl 怎么写,所以在下面打 10 遍

next_permutation
next_permutation
next_permutation
next_permutation
next_permutation
next_permutation
next_permutation
next_permutation
next_permutation
next_permutation
next_permutation

你可以数一下,一共有 11 遍

B

\([1,1000]\)

典,对于这类数字和的题,直接考虑拆位处理,预处理一下就可以了

const int maxN = 1000 + 10;
int n;
string s[maxN];
int a[maxN][maxN];
int pre[maxN];
void solve(){
    cin >> n;
    fp(i, 1, n) cin >> s[i];
    fp(i, 1, n) fp(j, 1, n) a[i][j] = s[i][j - 1] - '0';
    int num = 1;
    pre[0] = 1;
    fp(i, 1, n) {
        (num *= 10) %= mod;
        pre[i] = (pre[i - 1] + num) % mod;
    }
    int ans = 0;
    fp(i, 1, n) {
        fp(j, 1, n) {
            int x = (a[i][j] * pre[n - i]) % mod;
            (x *= i) % mod;
            (ans += x) %= mod;
            x = (a[i][j] * pre[n - j]) % mod;
            (x *= j) % mod;
            (ans += x) %= mod;
        }
    }
    cout << ans << endl;
}

C

\(n\in [1,20]\)

讲个笑话,老师发的代码过不去第一个样例……
还有个笑话,有个写了 D 的人 C 过不去

考场上想贪心想到头疼,正解是 DP ……
所以告诉我们,贪心没有正确性时,可以考虑 DP

一眼状压,但是我们不能直接枚举最终状态,考虑转移

我们目前已经将一些水杯中的水倒到了其他水杯中,然后考虑继续转移,可以将一杯水倒到另一个非空的杯子里进行更新

所以做完了

我是煞笔

const int maxN = 100, N = 1e7;
int n, m;
int f[N];
int c[maxN][maxN];
int popcount(int now) {
    int sum = 0;
    while (now) sum++, now -= (now & -now);
    return sum;
}
void solve(){
    n = rd(), m = rd();
    fp(i, 1, n) fp(j, 1, n) c[i][j] = rd();
    memset(f, 0x3f, sizeof(f));
    if (m == n) {
        cout << 0 << endl;
        return ;
    }
    f[0] = 0;
    int ans = inf;
    fp(i, 1, (1 << n) - 1){  // black is k
        fp(j, 1, n) 
            if (i & (1 << (j - 1)))   // j to k
                fp(k, 1, n) 
                    if (!(i & (1 << (k - 1)))) 
                        f[i] = min(f[i], f[i ^ (1 << (j - 1))] + c[j][k]);
        if (popcount(i) == n - m)
            ans = min(ans, f[i]);
    }
    cout << ans << endl;
}

D

\(1e5\)

不太难

很明显,有重复的只可能出现在投票的组之中,可以对投票的组算一下每个人会有多少对重复的组,然后减出来就可以了

const int maxN = 1e5 + 10;
int n, m;
pii a[maxN],ts[maxN],mat[maxN]c[maxN];
map<pii, int> vis, mp;

inline int lowbit(int x) { return x & -x; }
inline void add(int wh) {for (; wh <= n; wh += lowbit(wh)) c[wh]++;}
inline int query(int x) {
    if (x <= 0) return 0;
    int sum = 0;
    while (x) sum += c[x], x -= lowbit(x);
    return sum;
}
void solve(){
    cin >> n >> m;
    fp(i, 1, n) {
        cin >> a[i].first >> a[i].second;
        ts[a[i].first]++, ts[a[i].second]++;
        mp[a[i]]++;
        mp[{a[i].second, a[i].first}]++;
    }
    fp(i, 1, n) {
        if (vis[a[i]] || vis[{ a[i].second, a[i].first }]) continue;
        vis[a[i]] = 1;
        int x = a[i].first, y = a[i].second;
        if (ts[x] + ts[y] - mp[a[i]] < m)
            if (ts[x] + ts[y] >= m) 
                mat[x]++, mat[y]++;
    }
    fp(i, 1, n) add(ts[i] + 1);
    int p = 0;
    fp(i, 1, n) {
        int sum = ts[i];
        int k = n - query(m - sum);
        if (ts[i] * 2 >= m) k--;
        p += k - mat[i];
    }
    cout << p / 2 << endl;
}

E

好题

这里着重来讲一下转移的细节

不难想到,阶段的划分在于最后一对匹配上的点,所以可以用 \(f_{i,j}\) 来表示对上面前 \(i\) 个,下面前 \(j\) 个元素进行匹配的最大值

这里有两种转移,一种是不匹配直接摆烂,另一种是匹配,这两种转移相互独立

明显,要是匹配,那一定要找最近的两组

但……直接枚举匹配组好像有些困难,因为另外一组一点信息也没有

枚举一条红边,则蓝边是一定相交的

那就好办了,直接在 vector 上二分就可以了

const int maxN = 4000 + 10;
vector<int> p1[maxN * 2], p2[maxN * 2];
int n, m;
int a[maxN], b[maxN];
int f[maxN][maxN];
int last(vector<int> v, int x) {
    if (v.empty())
        return 0;
    auto i = lower_bound(v.begin(), v.end(), x);
    if (i == v.begin())
        return 0;
    i--;
    return *i;
}
void lis() {
    vector<int> v;
    fp(i, 1, n) v.eb(a[i]);
    fp(j, 1, m) v.eb(b[j]);
    sort(v.begin(), v.end());
    int len = unique(v.begin(), v.end()) - v.begin();
    while (v.size() != len) v.pop_back();
    fp(i, 1, n) a[i] = lower_bound(v.begin(), v.end(), a[i]) - v.begin() + 1;
    fp(j, 1, m) b[j] = lower_bound(v.begin(), v.end(), b[j]) - v.begin() + 1;
}
void solve(){
    n = rd(), m = rd();
    fp(i, 1, n) a[i] = rd();
    fp(i, 1, m) b[i] = rd();
    lis();
    fp(i, 1, n) p1[a[i]].eb(i);
    fp(i, 1, m) p2[b[i]].eb(i);
    int ans = 0;
    vector<int> v;
    auto x = v.begin();
    fp(i, 1, n) {
        fp(j, 1, m) {
            f[i][j] = max(f[i][j - 1], f[i - 1][j]);
            if (a[i] == b[j])
                continue;
            int lj = last(p2[a[i]], j);
            int li = last(p1[b[j]], i);
            if (!lj || !li)
                continue;
            f[i][j] = max(f[i][j], f[li - 1][lj - 1] + 2);
            ans = max(f[i][j], ans);
        }
    }
    cout << ans;
}

F

这题分两个解法

  1. CDQ

加入这个问题是静态的,那该怎么办?
并不难,我们可以开一个树状数组,暴力把所有的蜜饼扫一遍,加入树状数组中

那 CDQ 怎么做?可以对于总时间来进行排序,然后先更新左边,再更新右边就可以了

两只 \(\log\)

没写

  1. 线段树

我的写法和其他的不大一样,比较奇怪

以大小为下标,储存最小的时间

然后对于一次查询,我们将整个区间拆成几个小区间,然后从左到右判断是否子树里面有符合要求的蜜饼,有就直接进去取出来

还挺好写的,不得不说拆分区间真的牛

const int maxN = 2 * 1e6 + 10;
struct node {
    int minx[maxN << 1], son[maxN << 1][2], lx[maxN << 1], rx[maxN << 1], maxx[maxN << 1];
    int idx = 1, root = 1;
    vector<int> v;
    void pa() { met(minx, 0x3f), met(maxx, 0); }
    void insert(int &now, int l, int r, int wh, int x) {
        if (!now)
            now = ++idx;
        lx[now] = l, rx[now] = r;
        if (l == r) {
            minx[now] = min(x, minx[now]), maxx[now] = wh;
            return;
        }
        int mid = (l + r) >> 1;
        if (wh <= mid)
            insert(son[now][0], l, mid, wh, x);
        else
            insert(son[now][1], mid + 1, r, wh, x);
        minx[now] = min(minx[son[now][0]], minx[son[now][1]]);
        maxx[now] = max(maxx[son[now][0]], maxx[son[now][1]]);
    }
    void split(int now, int l, int r, int ql, int qr) {
        lx[now] = l, rx[now] = r;
        if (ql <= l && r <= qr) {
            v.eb(now);
            return;
        }
        int mid = (l + r) >> 1;
        if (mid >= ql)
            split(son[now][0], l, mid, ql, qr);
        if (qr > mid)
            split(son[now][1], mid + 1, r, ql, qr);
    }
    int get_min(int now, int tim) {
        if (lx[now] == rx[now])
            return lx[now];
        if (minx[son[now][0]] <= tim)
            return get_min(son[now][0], tim);
        else
            return get_min(son[now][1], tim);
    }
} seg;
int n, q;
void solve(){
    cin >> n >> q;
    seg.pa();
    while (q--) {
        char c;
        cin >> c;
        int x, a;
        cin >> x >> a;
        if (c == 'M')
            seg.insert(seg.root, 1, n, a, x);
        else {
            seg.v.clear();
            seg.split(1, 1, n, a, n);
            int k = 0;
            for (int y : seg.v)
                if (seg.minx[y] <= x) {k = y; break;}
            if (!k) cout << "-1" << endl;
            else cout << seg.get_min(k, x) << endl;
        }
    }
}

G

\(k^2\) 是一个十分难以直接处理的条件,但是这个条件,却给了一个处理的范围

明显,\(f_i\) 为对于这个前缀,枚举一个前缀进行转移,但是这个枚举区间有一个限制:如果颜色数大于\(\sqrt{n}\),则一定不是最优的

所以我们就有了一个 \(O(n\sqrt{n})\) 的做法

这里使用链表来处理这个问题

这里的链表不省空间

const int maxN = 5 * 1e4 + 10, B = 250;
int n;
int a[maxN], f[maxN];
int last[maxN];
struct node {
    int nex[maxN], pre[maxN];
    void init() {
        pre[0] = -1;
        fp(i, 1, n) pre[i] = i - 1, nex[i] = i + 1;
    }
    void del(int x) {
        nex[pre[x]] = nex[x];
        pre[nex[x]] = pre[x];
    }
} list;

void lis() {
    vector<int> v;
    fp(i, 1, n) v.eb(a[i]);
    sort(v.begin(), v.end());
    int len = unique(v.begin(), v.end()) - v.begin();
    while (v.size() != len) v.pop_back();
    fp(i, 1, n) a[i] = lower_bound(v.begin(), v.end(), a[i]) - v.begin() + 1;
}
void solve(){
    while (scanf("%d", &n) != EOF) {
        fp(i, 1, n) scanf("%d", a + i);
        lis();
        met(f, 0x3f);
        f[0] = 0;
        list.init();
        fp(i, 1, n) {
            if (last[a[i]])
                list.del(last[a[i]]);
            last[a[i]] = i;
            int sum = 0;
            for (int j = list.pre[i]; j != -1; j = list.pre[j]) {
                sum++;
                if (sum >= 250)
                    break;
                f[i] = min(f[j] + (sum * sum), f[i]);
            }
        }
        printf("%d\n", f[n]);
        memset(f, 0, sizeof(f));
        memset(last, 0, sizeof(last));
    }
}
posted @ 2023-07-03 20:25  颈流推进  阅读(21)  评论(0)    收藏  举报