CSP-S模拟13 [排序,Xorum,有趣的区间问题,无聊的卡牌问题](好久没更过考试总结了呢)
CSP-S模拟13
今天的破防除了考试保龄以外,还有自己的涩图题解传到了hsez的同学那里,属实是完完全全的社死了。
丢大人了啊。TST将会臭名昭著
A.排序
CF1375E Inversion SwapSort
给定长度为 \(n\) 的序列 \(a_i\),求一种将每个逆序对下标 \((u,v)\) 的排序,使依次交换每个 \((a_u,a_v)\) 后,\(a_i\) 不减。
其实考试题是弱化版,原题是一个序列,考试题是一个排列。序列会出现重复的数,会更难处理一些,我们先假定 \(a\) 是一个排列。
因为最后要 \(a_i\) 不减,所以最后每个数放在哪里是固定的。要求交换每个逆序对,我们可以 \(n^2\) 扫一遍求出所有的逆序对,记录下每组逆序对在 \(a\) 的位置。
之后去考虑每组逆序对交换的顺序。由于要交换逆序对的个数次,所以在每次交换后逆序对数只能减 \(1\),即每一次交换只能影响当前交换的逆序对,不能影响其他的逆序对。比如:\(5,4,2,3,1\),\(5\) 不能直接和 \(1\) 交换,这样 \(4,2,3\) 和 \(1\) 构成的逆序对都会被影响,交换次数就假掉了。
所以,我们要优先交换 \(a\) 值大的逆序对。详细一点,对于逆序对 \((a_i,a_j)\),在 \(a_i\) 构成的所有逆序对中,我们要先交换 \(a_j\) 小的。对于 \(a_i\) 不同,优先要交换 \(i\) 小的。
对于序列其实同理,只是会有相等的情况。同样,对于逆序对 \((a_i,a_j)\),如果 \(a_j\) 相同,先交换 \(j\) 小的。
Code
#include<cstdio>
#include<vector>
#include<algorithm>
#define Pair pair< int, int >
#define Make(x, y) make_pair(x, y)
using namespace std;
const int MAXN = 1010;
int n, cnt;
int num[MAXN], val[MAXN];
vector< Pair > out;
bool cmp(const Pair &x, const Pair &y){
if(x.first == y.first){
if(num[x.second] != num[y.second]) return num[x.second] > num[y.second];
return x.second > y.second;
}
return x.first < y.first;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
n = read();
for(register int i = 1; i <= n; i++){
num[i] = read();
val[i] = num[i];
}
sort(val + 1, val + 1 + n);
cnt = unique(val + 1, val + 1 + n) - val - 1;
for(register int i = 1; i <= n; i++){
int pos = lower_bound(val + 1, val + 1 + cnt, num[i]) - val;
num[i] = pos;
}
for(register int i = 1; i <= n; i++)
for(register int j = i + 1; j <= n; j++)
if(num[i] > num[j]) out.push_back(Make(i, j));
sort(out.begin(), out.end(), cmp);
printf("%ld\n", out.size());
for(register int i = 0; i < out.size(); i++)
printf("%d %d\n", out[i].first, out[i].second);
return 0;
}
B.Xorsum
CF1427E Xum
给出的 \(x\) 一定为奇数,则它二进制表示下的最低位一定是 \(1\),所以我们考虑如何把 \(x\) 其他位置的 \(1\) 消去。
依次消除 \(x\) 当前的最高位。注意对同一个数进行相加操作相当于左移一位,想要让 \(x\) 的最低位和最高位对齐,一共需要左移 \((x)_{2}\) 的位数 \(-1\) 位,设这个数为 \(y\)。当 \(x\) 异或 \(y\) 后,\(x\) 的最高位就被消掉了,设这个数为 \(z\)。
记 \(r = y + z\),\(r\) 的低位和 \(z\) 相同,高位相当于 \(y\) 的高位左移一位,中间不同的一位是因为 \(y\) 和 \(z\) 相加最高位的进位产生。之后我们将 \(y\) 左移一位,记作 \(s\)。当我们将 \(s\) 与 \(r\) 异或后,得到 \(t\),\(t\) 的最高位和 \(y\) 的最高位相同,其余位和 \(x\) 相同,\(t\) 异或 \(x\) 就可以得到 \(t\) 的 \(highbit\),记为 \(u\)。我们用 \(u\) 每次消去 \(y\) 的一个 \(1\),直到 \(y\) 只剩下最低位的 \(1\),这样就得到了 \(x\) 的 \(highbit\),就可以消掉 \(x\) 的最高位。一直消下去,直到 \(x\) 为 \(1\) 即可。
Code
#include<cstdio>
#include<vector>
#include<algorithm>
#define LL long long
using namespace std;
LL x;
struct Answer{
LL opt, x, y;
};
vector<Answer> out;
LL lowbit(LL x){
return x & (-x);
}
LL Change(LL x){
LL y = x, res = x >> 1/*每次需要左移 x 的二进制表示的位数 - 1位*/;
while(res){
out.push_back((Answer){1, y, y});
y <<= 1;
res >>= 1;
}
//这时 y 的最低次位已经和 x 的最高次位对齐了
LL z = x ^ y; //消去最高次位上的 1
out.push_back((Answer){2, x, y});
//printf("x = %lld y = %lld z = %lld\n", x, y, z);
//然后去找 x 的 highbit,也就是把 y 消到只剩下一个 1
LL r = y + z;
out.push_back((Answer){1, y, z});
LL s = y + y;
out.push_back((Answer){1, y, y});
LL t = r ^ s;
out.push_back((Answer){2, r, s});
LL u = t ^ x;
out.push_back((Answer){2, t, x});
while(y != lowbit(y)){
if(y & u){
out.push_back((Answer){2, y, u});
y ^= u;
}
out.push_back((Answer){1, u, u});
u += u;
//printf("y = %lld\n", y);
}
//printf("first x = %lld\n", x);
out.push_back((Answer){2, x, y});
x ^= y;
return x;
}
int main(){
scanf("%lld", &x);
while(x != 1) x = Change(x);
printf("%ld\n", out.size());
for(register int i = 0; i < out.size(); i++){
if(out[i].opt == 1) printf("%lld + %lld\n", out[i].x, out[i].y);
else if(out[i].opt == 2) printf("%lld ^ %lld\n", out[i].x, out[i].y);
}
return 0;
}
C.有趣的区间问题
CF1609F Interesting Sections
考虑枚举最大值和最小值的 popcount
,那么问题等价于,给定一个序列 \(a_i\),其中有一些位置是关键位置,要求有多少个区间,满足其最大值和最小值都是关键位置。
枚举区间问题无非两个套路,扫描线和CDQ,这里用扫描线。枚举右端点 \(r\),扫描到 \(r\) 时,我们记 \(f_l\) 表示 \([l,r]\) 中最大值是否关键位置,以及 \([l,r]\) 中最小值是否是关键位置,那么显然 \(f_l\) 的变化可以通过单调栈求出,那么对于一个右端点 \(r\) 和一个 popcount
\(v\),其对答案的贡献就是 \(f_l=2\) 的 \(l\) 的个数。注意到 \(f_l\) 的上界就是 \(2\),因此这个可以通过区间最大值及其出现次数的线段树维护。
以及,这题卡常,用一下 fread
之类的差不多就能卡过去了。
Code
#include<cstdio>
#include<vector>
#include<algorithm>
#define LL long long
using namespace std;
const int MAXN = 1e6 + 10, SIZE = 65;
int n;
LL ans;
LL num[MAXN];
int cnt[MAXN], temp[SIZE];
int stk_max[MAXN], top_max, stk_min[MAXN], top_min;
struct Change{
int pos, l, r, v;
};
vector<Change> q[SIZE];
inline char gc(){
static char buf[MAXN], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXN, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL read(){
LL x = 0, f = 1;
char c = gc();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = gc();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = gc();
}
return x * f;
}
inline LL lowbit(LL x){
return x & (-x);
}
inline int popcount(LL x){
int ans = 0;
while(x){
++ans;
x -= lowbit(x);
}
return ans;
}
struct Segment_Tree{
struct Tree{
int l, r;
int sum;
int max;
int lazy;
}tr[MAXN << 2];
inline int lson(int rt){
return rt << 1;
}
inline int rson(int rt){
return rt << 1 | 1;
}
inline void Pushup(int rt){
tr[rt].sum = 0;
tr[rt].max = max(tr[lson(rt)].max, tr[rson(rt)].max);
if(tr[rt].max == tr[lson(rt)].max) tr[rt].sum += tr[lson(rt)].sum;
if(tr[rt].max == tr[rson(rt)].max) tr[rt].sum += tr[rson(rt)].sum;
}
void Build(int rt, int l, int r){
tr[rt].l = l;
tr[rt].r = r;
tr[rt].max = 0;
tr[rt].lazy = 0;
tr[rt].sum = r - l + 1;
if(l == r) return;
int mid = (l + r) >> 1;
Build(lson(rt), l, mid);
Build(rson(rt), mid + 1, r);
}
inline void Pushdown(int rt){
if(tr[rt].lazy){
tr[lson(rt)].max += tr[rt].lazy;
tr[rson(rt)].max += tr[rt].lazy;
tr[lson(rt)].lazy += tr[rt].lazy;
tr[rson(rt)].lazy += tr[rt].lazy;
tr[rt].lazy = 0;
}
}
void Update(int rt, int l, int r, int data){
if(tr[rt].l >= l && tr[rt].r <= r){
tr[rt].max += data;
tr[rt].lazy += data;
return;
}
Pushdown(rt);
int mid = (tr[rt].l + tr[rt].r) >> 1;
if(l <= mid) Update(lson(rt), l, r, data);
if(r > mid) Update(rson(rt), l, r, data);
Pushup(rt);
}
}S;
int main(){
n = read();
for(register int i = 1; i <= n; ++i){
num[i] = read();
cnt[i] = popcount(num[i]);
++temp[cnt[i]];
}
//for(register int i = 1; i <= n; i++)
// printf("cnt[%d] = %d\n", i, cnt[i]);
for(register int i = 1; i <= n; ++i){
while(top_min && num[stk_min[top_min]] > num[i]){
int p = stk_min[top_min--];
q[cnt[p]].push_back((Change){i, stk_min[top_min] + 1, p, -1});
}
q[cnt[i]].push_back((Change){i, stk_min[top_min] + 1, i, 1});
stk_min[++top_min] = i;
while(top_max && num[stk_max[top_max]] < num[i]){
int p = stk_max[top_max--];
q[cnt[p]].push_back((Change){i, stk_max[top_max] + 1, p, -1});
}
q[cnt[i]].push_back((Change){i, stk_max[top_max] + 1, i, 1});
stk_max[++top_max] = i;
}
for(register int i = 0; i <= 64; ++i){
if(!temp[i]) continue;
S.Build(1, 1, n);
for(register int j = 1, sit = 0; j <= n; ++j){
while(sit < q[i].size() && q[i][sit].pos <= j){
S.Update(1, q[i][sit].l, q[i][sit].r, q[i][sit].v);
++sit;
}
if(S.tr[1].max == 2) ans += S.tr[1].sum;
}
}
printf("%lld\n", ans);
return 0;
}
D.无聊的卡牌问题
CF1427F Boring Card Game
没搞懂呢,咕。
搞懂了,就是细节有点恶心,搞了一个上午。
Code
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1210;
int n, tot/*联通块的数量*/, num/*不存在依赖关系的为 0 的联通快的数量*/;
int card[MAXN], belong[MAXN]/*这张牌归谁取*/, type[MAXN]/*联通快的类型*/, size[MAXN]/*联通快的大小*/;
int deg[MAXN]/*联通块的入度*/, son[MAXN]/*联通块的儿子*/;
int block[MAXN][4]; //每个联通块的成员
int stk[MAXN], top;
bool vis[MAXN];
queue<int> q1, q2;
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
n = read() * 3;
for(register int i = 1; i <= n; i++){
card[i] = read();
belong[card[i]] = 1; //是先手
}
n <<= 1;
for(register int i = 1; i <= n; i++){
if(!top || type[stk[top]] != belong[i]){
stk[++top] = ++tot;
type[tot] = belong[i];
++size[tot];
block[tot][size[tot]] = i;
}
else{
int p = stk[top];
++size[p];
block[p][size[p]] = i;
if(size[p] == 3){ //弹栈
int q = stk[top - 1];
son[p] = q;
++deg[q];
--top;
}
}
}
for(register int i = 1; i <= tot; i++) //去找不存在依赖关系的为 0 的联通快
if(!deg[i] && !son[i] && !type[i]) ++num;
for(register int i = 1; i <= tot; i++){
if(!deg[i] && type[i]) q1.push(i);
if(!deg[i] && !type[i]) q2.push(i);
}
for(register int tim = 1; tim <= tot; tim++){ //第几次取
if(tim & 1){ //到先手取
while(!q1.empty()){
int t = q1.front();
q1.pop();
vis[t] = true;
if(son[t]){
--deg[son[t]];
if(!deg[son[t]] && !vis[son[t]]){
q2.push(son[t]);
if(!son[son[t]]) ++num;
}
}
printf("%d %d %d\n", block[t][1], block[t][2], block[t][3]);
break;
}
}
else{
while(!q2.empty()){
int t = q2.front();
q2.pop();
if(tim < tot && !son[t] && num == 1 && !q2.empty()){
q2.push(t);
continue;
}
vis[t] = true;
if(son[t]){
--deg[son[t]];
if(!deg[son[t]] && !vis[son[t]]) q1.push(son[t]);
}
if(!son[t]) --num;
printf("%d %d %d\n", block[t][1], block[t][2], block[t][3]);
break;
}
}
}
return 0;
}
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16736153.html