CSP-S模拟13 [排序,Xorum,有趣的区间问题,无聊的卡牌问题](好久没更过考试总结了呢)

CSP-S模拟13

今天的破防除了考试保龄以外,还有自己的涩图题解传到了hsez的同学那里,属实是完完全全的社死了。

image
image

丢大人了啊。TST将会臭名昭著

A.排序

CF1375E Inversion SwapSort

给定长度为 n 的序列 ai,求一种将每个逆序对下标 (u,v) 的排序,使依次交换每个 (au,av) 后,ai 不减。

其实考试题是弱化版,原题是一个序列,考试题是一个排列。序列会出现重复的数,会更难处理一些,我们先假定 a 是一个排列。

因为最后要 ai 不减,所以最后每个数放在哪里是固定的。要求交换每个逆序对,我们可以 n2 扫一遍求出所有的逆序对,记录下每组逆序对在 a位置

之后去考虑每组逆序对交换的顺序。由于要交换逆序对的个数次,所以在每次交换后逆序对数只能减 1,即每一次交换只能影响当前交换的逆序对,不能影响其他的逆序对。比如:5,4,2,3,15 不能直接和 1 交换,这样 4,2,31 构成的逆序对都会被影响,交换次数就假掉了。

所以,我们要优先交换 a 值大的逆序对。详细一点,对于逆序对 (ai,aj),在 ai 构成的所有逆序对中,我们要先交换 aj 小的。对于 ai 不同,优先要交换 i 小的。

对于序列其实同理,只是会有相等的情况。同样,对于逆序对 (ai,aj),如果 aj 相同,先交换 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+zr 的低位和 z 相同,高位相当于 y 的高位左移一位,中间不同的一位是因为 yz 相加最高位的进位产生。之后我们将 y 左移一位,记作 s。当我们将 sr 异或后,得到 tt 的最高位和 y 的最高位相同,其余位和 x 相同,t 异或 x 就可以得到 thighbit,记为 u。我们用 u 每次消去 y 的一个 1,直到 y 只剩下最低位的 1,这样就得到了 xhighbit,就可以消掉 x 的最高位。一直消下去,直到 x1 即可。

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,那么问题等价于,给定一个序列 ai,其中有一些位置是关键位置,要求有多少个区间,满足其最大值和最小值都是关键位置。

枚举区间问题无非两个套路,扫描线和CDQ,这里用扫描线。枚举右端点 r,扫描到 r 时,我们记 fl 表示 [l,r] 中最大值是否关键位置,以及 [l,r] 中最小值是否是关键位置,那么显然 fl 的变化可以通过单调栈求出,那么对于一个右端点 r 和一个 popcount v,其对答案的贡献就是 fl=2l 的个数。注意到 fl 的上界就是 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;
}
posted @   TSTYFST  阅读(83)  评论(7编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示