Solution
标程太暴力惹QAQ 相当于是26棵线段树的说QAQ
不过我写了另一种写法,从大到小枚举每一个字母,标记字典序在这个字母之上的位置为1,每次都建一棵线段树,维护1的数量,即区间和。
修改操作就是先查询这个区间1的数量,排序本质上就是把1一起放在这个区间前面或后面,最后查询每个位置,如果为1并且没有被标记过,就标记成当前枚举的字母即可。
将看似复杂的问题转化为了简单的区间修改和查询QAQ
不过需要各种常数优化才能过QAQ
Code
#include<bits/stdc++.h> #define RG register using namespace std; int TR[400005], tag[400005]; char a[100005]; int s[100005]; inline int read() { int x = 0; char ch = getchar(); while(ch > '9' || ch < '0') ch = getchar(); while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x; } inline void update(int nd) { TR[nd] = TR[nd << 1] + TR[nd << 1 | 1]; } inline void push_down(int nd, int l, int r) { if(~tag[nd]) { int mid = (l + r) >> 1; TR[nd << 1] = tag[nd] * (mid - l + 1); TR[nd << 1 | 1] = tag[nd] * (r - mid); tag[nd << 1] = tag[nd]; tag[nd << 1 | 1] = tag[nd]; tag[nd] = -1; } } inline void build(int nd, int l, int r) { tag[nd] = -1; if(l == r) { TR[nd] = s[l]; return ; } int mid = (l + r) >> 1; build(nd << 1, l, mid); build(nd << 1 | 1, mid + 1, r); update(nd); } inline int query(int nd, int l, int r, int L, int R) { if(TR[nd] == 0) return 0; if(L > R) return 0; if(l >= L && r <= R) { int tmp = TR[nd]; TR[nd] = 0; tag[nd] = 0; return tmp; } push_down(nd, l, r); int mid = (l + r) >> 1; int ans = 0; if(L <= mid) ans += query(nd << 1, l, mid, L, R); if(R > mid) ans += query(nd << 1 | 1, mid + 1, r, L, R); update(nd); return ans; } inline void modify(int nd, int l, int r, int L, int R, int d) { if(L > R) return ; if(l >= L && r <= R) { tag[nd] = d; TR[nd] = (r - l + 1) * d; return ; } push_down(nd, l, r); int mid = (l + r) >> 1; if(L <= mid) modify(nd << 1, l, mid, L, R, d); if(R > mid) modify(nd << 1 | 1, mid + 1, r, L, R, d); update(nd); } int d[100005]; inline void get(int nd, int l, int r) { if(l == r) { d[l] = TR[nd]; return ; } push_down(nd, l, r); int mid = (l + r) >> 1; get(nd << 1, l, mid); get(nd << 1 | 1, mid + 1, r); } int n, m, l[100005], r[100005], x[100005], color[100005]; int main() { freopen("string.in", "r", stdin); freopen("string.out", "w", stdout); n = read(), m = read(); scanf("%s", a + 1); for(int i = 1; i <= m; i ++) l[i] = read(), r[i] = read(), x[i] = read(); for(RG int i = 25; i >= 0; i --) { for(RG int j = 1; j <= n; j ++) if(a[j] >= i + 'a') s[j] = 1; else s[j] = 0; build(1, 1, n); for(RG int j = 1; j <= m; j ++) { int delta = query(1, 1, n, l[j], r[j]); if(x[j] == 0) { if(delta > 0) modify(1, 1, n, l[j], l[j] + delta - 1, 1); } else { if(delta > 0) modify(1, 1, n, r[j] - delta + 1, r[j], 1); } } get(1, 1, n); for(RG int j = 1; j <= n; j ++) { if(d[j] && !color[j]) color[j] = i; } } for(int i = 1; i <= n; i ++) printf("%c", color[i] + 'a'); return 0; }
Solution
转移可以说是很难想了QAQ
列上的限制比行上明显要少,所以定义$dp[i][j]$表示当前扫到了第$i$列,当前没有填数的右区间有$j$个时的方案数。这个右区间指$r[k]$在$1~i$范围内的区间,左区间同理。
从前往后有当前放数或不放数两个情况,放数还分左区间放和右区间放。详情看代码吧QAQ
Code
#include<bits/stdc++.h> #define LL long long #define mod 998244353 using namespace std; int dp[3005][3005]; int l[3005], r[3005], n, m, x, y; int main() { freopen("matrix.in", "r", stdin); freopen("matrix.out", "w", stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) scanf("%d%d", &x, &y), l[x] ++, r[y] ++; dp[0][0] = 1; int tot = 0, tmp = 0; for(int i = 1; i <= m; i ++) { tmp ++; tot += r[i]; for(int j = r[i]; j <= tot; j ++) dp[i][j] = dp[i - 1][j - r[i]]; for(int j = 1; j <= tot; j ++) dp[i][j - 1] = (dp[i][j - 1] + 1ll * dp[i][j] * j % mod) % mod; for(int j = 1; j <= l[i]; j ++) { for(int k = 0; k <= tot; k ++) dp[i][k] = 1ll * dp[i][k] * max(0, tmp - (tot - k)) % mod; tmp --; } } printf("%d", dp[m][0]); return 0; }
Solution
看到这种位运算就想到$Trie$树啥的,可是怎么建树还有怎么扫都毫无思路啊QAQ
首先还是从式子入手,对手改变一次的那个式子中,当$x<2^{n-1}$时,原式相当于将$x$左移一位,反之相当于将$x$左移一位再加一,具体写一写就知道了。
这就相当于在断点的位置,把$x$的最高位取下来放到最后。
又因为前面总贡献和后面总贡献都是可以预处理出来的前后缀异或和,再加上按位运算,只会影响一位,与其它位无关,所以可以$O(m)$处理出来每个断点的贡献,将所有贡献加入$Trie$数中。
然后考虑怎么走$x$。因为对手想让结果尽量小,所以他一定会尽量让选择的$x$往和$Trie$数上节点相同的地方走。
所以$dfs$遍历trie树时,如果两边儿子都有,那么这一层不管怎么选都不会有贡献,如果有一边的儿子,那么就可以获得另一边的贡献。但是遍历还是只能顺着这边遍历下去,遍历与计算贡献无关。
Code
#include<bits/stdc++.h> using namespace std; int t[33], tot; int n, m; int son[6000005][2], tail; int pre[100005], las[100005], a[100005]; void add(int x) { memset(t, 0, sizeof(t)); tot = 0; while(x) { t[++tot] = (x & 1); x >>= 1; } int nd = 0; for(int i = n; i >= 1; i --) { if(!son[nd][t[i]]) son[nd][t[i]] = ++ tail; nd = son[nd][t[i]]; } } int ans1, ans2; void dfs(int nd, int val, int depth) { if(depth == -1) { if(val > ans1) ans1 = val, ans2 = 1; else if(val == ans1) ans2 ++; return ; } if(son[nd][0] && son[nd][1]) { dfs(son[nd][0], val, depth - 1); dfs(son[nd][1], val, depth - 1); } else if(son[nd][0]) dfs(son[nd][0], val + (1 << depth), depth - 1); else if(son[nd][1]) dfs(son[nd][1], val + (1 << depth), depth - 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[i]), pre[i] = pre[i - 1] ^ a[i]; for(int i = m; i >= 1; i --) las[i] = las[i + 1] ^ a[i]; for(int i = 0; i <= m; i ++) { int now = pre[i] << 1; if(1 & (now >> n)) now = (now ^ (1 << n)) | 1; now ^= las[i + 1]; add(now); } dfs(0, 0, n - 1); printf("%d %d", ans1, ans2); return 0; }