CSP-S模拟13
又是模拟退役的一天
A. 排序
死因 : 输出没有让前面小于后面
通过找规律发现交换两个数值相邻的一定可以
原因是这样保证每次操作只减少一个逆序对
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c >'9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return x;
}
const int maxn = 1005;
int a[maxn], n, b[maxn];
bool mp[maxn][maxn];
int pos[maxn];
int main(){
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)b[i] = a[i];
int m = 0 ;
for(int i = 1; i <= n; ++i){
for(int j = i + 1; j <= n; ++j)
if(a[i] > a[j]){
++m; mp[i][j] = mp[j][i] = 1;
}
}
for(int i = 1; i <= n; ++i)pos[a[i]] = i;
printf("%d\n",m);
while(m){
for(int i = 1; i < n; ++i){
if(mp[pos[i]][pos[i + 1]]){
mp[pos[i]][pos[i + 1]] = mp[pos[i + 1]][pos[i]] = 0;
printf("%d %d\n", min(pos[i], pos[i + 1]), max(pos[i + 1], pos[i]));
swap(pos[i], pos[i + 1]);
--m;
}
}
}
return 0;
}
目前写法在 \(cf\) 上会 \(TLE\) ,等我褐个正解来
B. Xorum
死因:没有乱搞,啥都不会
目前写出的解法,随机化找互质的数对,然后解 \(ax + by = 1\),搞出 \(ax\) , \(by\) 看看是否需要都加上一个奇数即可
在 \(cf\) 上被卡的死死的,正解稍后再说
upd : cf上需要离散化,然后我写挂了..
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 100005;
ll now[maxn];
set<ll> s;
int top;
bool fl = 0;
struct node{ll opt, x, y;};
vector<node>v;
void add(ll x, ll y){
if(s.count(x + y))return;
now[++top] = x + y; s.insert(x + y);
v.push_back({1, x, y});
}
void xorr(ll x, ll y){
if(s.count(x xor y))return;
now[++top] = x xor y;s.insert(x xor y);
v.push_back({2, x, y});
}
int prime(){
int o = now[top] & 1;
for(int i = top - 1; i >= 0; --i){
if((o xor (now[i] & 1)) && __gcd(now[top], now[i]) == 1){
return i;
}
}
return -1;
}
mt19937 rd((ull)(new char) * (ull)(new char));
int srd(int l, int r){
uniform_int_distribution<>d(l, r);
return d(rd);
}
void exgcd(ll a, ll b, ll &x, ll &y){
if(!b){
x = 1; y = 0;
return;
}
exgcd(b, a % b, y, x);
y = y - a / b * x;
}
void getpow(ll x, ll y){
ll ans = 0; y;
for(; y; y >>= 1, add(x, x), x = x + x)if(y & 1)add(ans, x), ans = ans + x;
}
int main(){
scanf("%lld",&now[0]); s.insert(now[0]);
int pos = -1, cnt = 1;
while((pos = prime()) == -1){
if(srd(1, 5) != 1)add(now[srd(0, top)], now[srd(0, top)]);
else xorr(now[srd(0, top)], now[srd(0, top)]);
}
ll a = now[pos], b = now[top], x = 0, y = 0;
exgcd(a, b, x, y);
ll nx = (x % b + b) % b;
ll k = (nx - x) / b;
ll ny = y - k * a;
xorr(now[0], now[0]);
getpow(a, nx); getpow(b, -ny);
ll ax = a * nx, by = -b * ny;
if((ax xor by) == 1){
xorr(ax, by);
}else {
add(ax, now[0]); add(by, now[0]);
xorr(ax + now[0], by + now[0]);
}
printf("%ld\n",v.size());
for(node p : v){
printf("%lld %lld %lld\n",p.opt, p.x, p.y);
}
return 0;
}
发现可以用左移和异或消去最高位的 \(1\),然后就可以直接搞了
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
struct node{ll opt, x, y;};
vector<node>v;
void add(ll x, ll y){v.push_back({1, x, y});}
void xorr(ll x, ll y){v.push_back({2, x, y});}
int solve(ll x){
ll now = x, y = x >> 1;
while(y){
add(now, now);
now <<= 1;
y >>= 1;
}
ll z = x ^ now; xorr(x, now);
ll a = z + now; add(z, now);
ll b = now + now; add(now, now);
ll c = a ^ b; xorr(a, b);
ll d = c ^ x; xorr(c, x);
while(now != (now & - now)){
if(now & d){
xorr(now, d);
now ^= d;
}
add(d, d);
d <<= 1;
}
xorr(x, now);
return x xor now;
}
int main(){
ll x; scanf("%lld",&x);
while(x != 1)x = solve(x);
printf("%ld\n",v.size());
for(node p : v){
if(p.opt & 1)printf("%lld + %lld\n",p.x, p.y);
else printf("%lld ^ %lld\n",p.x, p.y);
}
return 0;
}
C. 有趣的区间问题
死因 int j = a[i]
分治处理,枚举最大值最小值在区间哪一侧分四种情况讨论,处理时候使用双指针扫
对于最大最小值不在区间一侧的,可以将指针之间的部分加入桶,桶的下标是 \(popcount\),对应查询即可
对于重复元素,最大值最小值在一侧的选择一种带等号,不在同一侧的也选择一个带等号
(好像讲题时候我插的那一嘴还说错了,不过应该没人听吧?)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll x = 0; char c = getchar();
while(c < '0' || c >'9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return x;
}
const int maxn = 1000005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll a[maxn];
int pop[maxn], n;
ll mi[maxn], mx[maxn];
int mip[maxn], mxp[maxn];
int cnp[maxn];
ll solve(int l, int r){
if(l == r)return 1;
if(l > r)return 0;
int mid = (l + r) >> 1;
ll ans = 0;
ll nmi = a[mid], nmx = a[mid];
int nmip = mid, nmxp = mid;
for(int i = mid; i >= l; --i){
if(nmi > a[i]){
nmi = a[i];
nmip = i;
}
if(nmx < a[i]){
nmx = a[i];
nmxp = i;
}
mi[i] = nmi; mx[i] = nmx;
mip[i] = nmip; mxp[i] = nmxp;
}
nmi = nmx = a[mid + 1]; nmip = nmxp = mid + 1;
for(int i = mid + 1; i <= r; ++i){
if(nmi > a[i]){
nmi = a[i];
nmip = i;
}
if(nmx < a[i]){
nmx = a[i];
nmxp = i;
}
mi[i] = nmi; mx[i] = nmx;
mip[i] = nmip; mxp[i] = nmxp;
}
{
int pr = mid;
for(int pl = mid; pl >= l; --pl){
if(pop[mip[pl]] != pop[mxp[pl]])continue;
while(mi[pr + 1] >= mi[pl] && mx[pr + 1] <= mx[pl] && pr < r)++pr;
ans += pr - mid;
}
}
{
int pl = mid + 1;
for(int pr = mid + 1; pr <= r; ++pr){
if(pop[mip[pr]] != pop[mxp[pr]])continue;
while(mi[pl - 1] > mi[pr] && mx[pl - 1] < mx[pr] && pl > l)--pl;
ans += mid + 1 - pl;
}
}
{
int pl = mid, pr = mid;
for(int i = mid; i >= l; --i){
while(mx[pr + 1] <= mx[i] && pr < r)++pr, ++cnp[pop[mip[pr]]];
while(pl < pr && mi[pl + 1] >= mi[i])++pl, --cnp[pop[mip[pl]]];
ans += cnp[pop[mxp[i]]];
}
while(pl < pr)++pl, --cnp[pop[mip[pl]]];
}
{
int pl = mid + 1, pr = mid + 1;
for(int i = mid + 1; i <= r; ++i){
while(mx[pl - 1] < mx[i] && pl > l)--pl, ++cnp[pop[mip[pl]]];
while(pl < pr && mi[pr - 1] > mi[i])--pr, --cnp[pop[mip[pr]]];
ans += cnp[pop[mxp[i]]];
}
while(pl < pr)--pr, --cnp[pop[mip[pr]]];
}
ans += solve(l, mid) + solve(mid + 1, r);
return ans;
}
int main(){
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i){
ll j = a[i];
while(j){
++pop[i];
j -= (j & - j);
}
}
printf("%lld\n",solve(1, n));
return 0;
}
D. 无聊的卡牌问题
死因,没有死
跑个栈就能定下来三元组,然后存在先后的限制关系,所以我们建个图
把三元组左侧元素看做左括号,右侧看做右括号,那么这就是括号匹配,只有包含和不想交的关系
那么我们建出来的其实是一个森林
考虑如何操作
因为最后一次操作是后手,所以我们需要保留一个后手的根
考虑操作过程,我们肯定是选择一个叶子删掉,并且使颜色交替
通过一些证明可以得到删去任何一个叶子都可以
那么我们可以跑拓扑,每次交替取先手后手,可以用两个队列或者双端队列维护一下
记得保留后手的根,可以先预处理出有几个后手的根,拓扑时如果现在出队的是最后一个就跳过他,把他最后输出
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c >'9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return x;
}
const int maxn = 1500;
bool vis[maxn];
struct node{int col, x, y, z;}d[maxn];
int cnt, rd[maxn], fa[maxn];
int sta[maxn], nt[maxn], top;
int n, rem[maxn];
deque<int>q;
void print(int x){
printf("%d %d %d\n", d[x].x, d[x].y, d[x].z);
}
int main(){
n = read();
for(int i = 1; i <= n + n + n; ++i)vis[read()] = 1;
for(int i = 1; i <= n * 6; ++i){
if(top >= 2 && vis[sta[top]] == vis[sta[top - 1]] && vis[i] == vis[sta[top]]){
nt[sta[top - 1]] = ++cnt;
nt[sta[top]] = cnt;
nt[i] = cnt;
d[cnt] = {vis[i], sta[top - 1], sta[top], i};
top -= 2;
}else sta[++top] = i;
}
for(int i = 1; i <= n * 6; ++i){
if(top >= 2 && vis[sta[top]] == vis[sta[top - 1]] && vis[i] == vis[sta[top]])top -= 2;
else{
if(top && nt[i] != nt[sta[top]]){
fa[nt[i]] = nt[sta[top]];
++rd[nt[sta[top]]];
}
sta[++top] = i;
}
}
int crt = 0;
for(int i = 1; i <= cnt; ++i)if(fa[i] == 0 && d[i].col == 0)++crt;
for(int i = 1; i <= cnt; ++i)if(rd[i] == 0){
if(d[i].col)q.push_front(i);
else q.push_back(i);
}
int rt = 0, round = 0;
while(!q.empty()){
++round;
int x = 0;
if(round & 1){
x = q.front();
q.pop_front();
}else{
x = q.back();
q.pop_back();
if(!fa[x] ){
--crt;
if(crt == 0){
rt = x;
if(q.empty())break;
x = q.back();
q.pop_back();
}
}
}
rem[round] = x;
--rd[fa[x]];
if(rd[fa[x]] == 0){
if(d[fa[x]].col)q.push_front(fa[x]);
else q.push_back(fa[x]);
}
}
if(rem[round]) ++round;
rem[round] = rt;
for(int i = 1; i <= round; ++i)print(rem[i]);
return 0;
}