18.10.29 考试总结

今天考试我没有一道题想出正解了...。 听了idy的话今天先把暴力搞了狗了标准100暴力分竟然没有垫底..

 

这道题其实要多暴力有多暴力...。 正解是开26棵线段树 每一个字母对应一棵线段树 

每次搞一个区间首先把他所有的字母取出来 然后填回去 当然不是暴力填 仍然是区间填 每个的填法都是从小到大填

所以每次填完要打上标记 若下次递归到这里下放标记即可

代码

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

const int N = 1e5 + 5;
char s[N];
int tag[4 * N], n, m;
struct node {
    int sum[26];
    node operator + (const node & b) const {
        node a;
        for(int i = 0;i < 26;i ++) a.sum[i] = b.sum[i] + sum[i]; return a;
    }
}zero, K, f[4 * N];

int read( ) {
    
    int t = 1, ans = 0;
    char x; x = getchar( );
    while(x < '0' || x > '9') {
        if(x == '-') t = -1;
        x = getchar( );
    }
    while(x >= '0' && x <= '9') {
        ans = ans * 10 + x - '0';
        x = getchar( );
    }
    return ans * t;
}

void update(int o) {
    f[o] = f[2 * o] + f[2 * o + 1];
}

void build(int o, int l, int r) {
    
    if(l == r) {
        f[o].sum[s[l] - 'a'] ++; return ;
    }
    int mid = l + r >> 1;
    build(o << 1, l, mid); build(o << 1 | 1, mid + 1, r);
    update(o);
}

node giv(node & x, int size) {
    
    node tmp = zero;
    for(int i = 0;i < 26;i ++) {
        if(size < x.sum[i]) {x.sum[i] -= size; tmp.sum[i] += size; return tmp;}
        tmp.sum[i] += x.sum[i]; size -= x.sum[i]; x.sum[i] = 0;
    }
}

void push_down(int o, int l, int r) {
    
    int mid = l + r >> 1;
    if(tag[o]) {
        tag[o << 1] = tag[o << 1 | 1] = tag[o];
        node tmp = f[o];
        if(tag[o] == -1) {
            f[o << 1 | 1] = giv(tmp, r - mid);
            f[o << 1] = tmp;
        }
        else {
            f[o << 1] = giv(tmp , mid - l + 1);
            f[o << 1 | 1] = tmp;
        }
        tag[o] = 0;
    }
}

node query(int o, int l, int r, int L, int R) {
    
    if(l >= L && r <= R)  return f[o];
    int mid = l + r >> 1;
    push_down(o, l, r);
    node ans = zero;
    if(L <= mid) ans = ans + query(o << 1, l, mid, L, R);
    if(mid < R) ans = ans + query(o << 1 | 1, mid + 1, r, L, R);
    update(o);
    return ans;
}

void get_tag(int o, int l, int r, int L, int R, int opt) {
    
    if(l >= L && r <= R) {
        tag[o] = opt;
        f[o] = giv(K, r - l + 1); return ;
    }
    push_down(o, l, r);
    int mid = l + r >> 1;
    if(opt == 1) {
        if(L <= mid) get_tag(o << 1, l, mid, L, R, opt);
        if(mid < R) get_tag(o << 1 | 1, mid + 1, r, L, R, opt);
    }
    else {
        if(mid < R) get_tag(o << 1 | 1, mid + 1, r, L, R, opt);
        if(L <= mid) get_tag(o << 1, l, mid, L, R, opt);
    }
    update(o);
}

void dfs(int o, int l, int r) {
    
    if(l == r) {
        for(int i = 0;i < 26;i ++)
            if(f[o].sum[i]) {printf("%c",i + 'a');}
        return ;
    }
    push_down(o, l, r);
    int mid = l + r >> 1;
    dfs(o << 1, l, mid); dfs(o << 1 | 1, mid + 1, r);
}

int main( ) {
    
    freopen("string.in", "r", stdin);
    freopen("string.out", "w", stdout);
    scanf("%d%d",& n,& m);
    scanf("%s",s + 1);
    build(1, 1, n);
    while(m --) {
        int l, r, opt;
        l = read( ), r = read( ), opt = read( );
        if(! opt) opt = -1;
        K = query(1, 1, n, l, r);
        get_tag(1, 1, n, l, r, opt);
    }
    dfs(1, 1, n);
}

这道题是一道很日怪的$dp$

$dp[i][j]$表示到了第$i$列有$j$列没有填的合法方案数

那么$i$越过一个左侧区间的右端点时,从之前剩下空列中选一在这个左侧区间放1。转移时分在右侧区间放1或不放1。

代码

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

const int N = 3003;
const long long MOD = 998244353;
int F[N], G[N], n, m, lef, tot;
long long dp[N][N];

int main( ) {
    
    freopen("matrix.in", "r", stdin);
    freopen("matrix.out", "w", stdout);
    scanf("%d%d",& n,& m);
    for(int i = 1;i <= n;i ++) {
        int l, r; scanf("%d%d",& l,& r);
        F[l] ++; G[r] ++;
    }
    dp[0][0] = 1;
    for(int i = 1;i <= m;i ++) {
        lef ++; tot += G[i];
        for(int j = G[i];j <= tot;j ++) dp[i][j] = dp[i - 1][j - G[i]];
        for(int j = 1;j <= tot;j ++) dp[i][j - 1] = (dp[i][j - 1] + dp[i][j] * j % MOD) % MOD;
        for(int j = 1;j <= F[i];j ++) {
            for(int k = 0;k <= tot;k ++)
                dp[i][k] = dp[i][k] * max(lef - (tot - k), 0) % MOD;
            lef --;
        }
    }
    printf("%lld\n", dp[m][0]);
}

  

这道题首先对对手操作进行化简 原式子的$x$可以分为两种情况

1.$x$的第$n$位为$1$ 也就是说它乘以二之后模上$2^{n}$为$1$ 后面的$2 * x$相当于$x$左移一位 所以这个操作相当于把$x$最高位取出来挤到最后一位

2.$x$的第$n$位为$0$ 那么这个时候他乘以二模上$2 ^ {n}$不变 他除以$2 ^ {n}$则为0 同样相当于把他的最高位取出来挤到最后

先不考虑异或上给定的数

因为异或是相互独立的 所以在进行这个操作的时候相当于先把前面位置的$a[i]$分别进行上述操作再异或起来 所以这个时候会产生$m + 1$个值与给定数进行异或 因为有$m + 1$个断点

那么这个时候就可以对这$m + 1$个数建一棵深度为$n$的$trie$ 然后在这个上面$dfs$

若到达某一个深度时他有两个儿子 那么他对答案不会做出贡献 因为不论我这一位选择什么我的对手都可以选择另外一边将我吃掉

那么若这一位只有一个儿子 就可以产生$(1 << (dep - 1))$的贡献 因为对手只有一种选择 我选择与他相反的即可

一直走到叶子节点 统计对于每个叶子节点的答案的最大值 因为最小值是在$dfs$过程中已经保证了 

代码

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

const int N = 3e6 + 5;
int ans, num, cnt, son[N][2], n, m, rsum[N], sum[N], a;
bool vis[N];

void insert(int x) {
    
    int now = 0;
    for(int i = n;i >= 1;i --) {
        int nd = x & (1 << (i - 1)) ? 1 : 0;
        if(! son[now][nd]) son[now][nd] = ++ cnt;
        now = son[now][nd];
    }
    vis[now] = true;
}

void dfs(int nd, int tmp, int dep) {
    
    if(vis[nd]) {
        if(tmp > ans) ans = tmp, num = 1;
        else if(tmp == ans) num ++; return ;
    }
    if(son[nd][1] && son[nd][0]) {
        dfs(son[nd][1], tmp, dep - 1); dfs(son[nd][0], tmp, dep - 1);
    }
    else if(son[nd][1]) dfs(son[nd][1], tmp + (1 << (dep - 1)), dep - 1);
    else dfs(son[nd][0], tmp + (1 << (dep - 1)), dep - 1);
}

int main( ) {
    
    freopen("big.in", "r", stdin);
    freopen("big.out", "w", stdout);
    scanf("%d%d",& n,& m);
    for(int i = 1;i <= m;i ++) {
        scanf("%d",& a); int h = (a & (1 << (n - 1))) ? 1 : 0;
        int r = ((a ^ (h << (n - 1))) << 1) | h;
        sum[i] = sum[i - 1] ^ a; rsum[i] = rsum[i - 1] ^ r;
    }
    for(int i = 0;i <= m;i ++) {
        int p = rsum[i] ^ sum[m] ^ sum[i];
        insert(p);
    }
    dfs(0, 0, n); printf("%d\n%d\n", ans, num);
}
posted @ 2018-10-29 19:04  阿澈说他也想好好学习  阅读(148)  评论(0编辑  收藏  举报