CSP-S模拟13
A. 排序
赛时想到玄学做法,正确性不会证……来源是没审题上来写了个冒泡排序,发现a是原数组,就开始魔改冒泡,发现倒序输出就对了!?
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1009;
int n, a[maxn], fnd[maxn];
vector<pair<int, int> > ans;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read();
for(int i=1; i<=n; i++)
{
a[i] = read(); fnd[a[i]] = i;
}
for(int i=n-1; i>=1; i--)
{
bool tot = 0;
for(int j=1; j<=i; j++)
{
if(a[j] > a[j+1])
{
ans.push_back(make_pair(fnd[a[j]], fnd[a[j+1]]));
swap(a[j], a[j+1]);
tot = 1;
}
}
if(!tot) break;
}
int sz = ans.size();
printf("%d\n", sz);
for(int i=sz-1; i>=0; i--)
{
printf("%d %d\n", ans[i].first, ans[i].second);
}
return 0;
}
B. Xorum
随机化很好用,可惜我不会***
鹤:
考虑每一次将最高位去掉转换成一个子问题,目标就是构造出最高位的1 <
1.将x在二进制意义下向左平移使得最小的非0位与high相等,令这个数为y
2.1的结果y和原数异或,将high处置为0,令这个数为z,末尾是原数,缺了high前面也是原数
3.将z+y记为p,末尾是原数,high是1,high之前为原数左移留空位(high前面就是加法)
4.p^x得到q,尾巴被消了(包括high),high之前同上
5.y+y得到h(向左平移使得最小的非0位与high+1相等),除了high为0之外前面与q完全相同
6.q^h得到2^(high+1)接下来就可以用它消掉y中多余的1了
连具体的例子也鹤了吧:
Sample
x=000111
000111 + 000111 = 001110
001110 + 001110 = 011100
011100 ^ 000111 = 011011
011011 + 001110 = 110111
110111 ^ 000111 = 110000
011100 + 011100 = 111000
110000 ^ 111000 = 001000
011100 ^ 001000 = 010100
001000 + 001000 = 010000
010100 ^ 010000 = 000100
000111 ^ 000100 = 000011
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 2;
int x, len;
bool vis[60];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
struct Answer
{
int op; ll a, b;
}ans[maxn];
void work(int x)
{
if(x == 1) return;
int high;
for(int i=19; i>=0; i--)
{
if((x>>i)&1) {high = i; break;}
}
ll tmp = x;
for(int i=0; i<high; i++)
{
ans[++len].op = 0;
ans[len].a = tmp; ans[len].b = tmp; tmp <<= 1;
}
if(!vis[high+1])
{
ans[++len].op = 1; ans[len].a = tmp; ans[len].b = x;
ll now = tmp^x;
ans[++len].op = 0; ans[len].a = now; ans[len].b = tmp;
ll sum = now + tmp;
ans[++len].op = 1; ans[len].a = sum; ans[len].b = x;
sum ^= x;
ans[++len].op = 0; ans[len].a = tmp; ans[len].b = tmp;
now = (tmp << 1);
ans[++len].op = 1; ans[len].a = now; ans[len].b = sum;
vis[high+1] = 1;
}
for(int i=high+1; (1ll<<i)<=tmp; i++)
{
if(!vis[i])
{
ans[++len].op = 0; ans[len].a = (1ll<<(i-1));
ans[len].b = (1ll<<(i-1)); vis[i] = 1;
}
if((tmp>>i)&1)
{
ans[++len].op = 1; ans[len].a = tmp;
ans[len].b = (1ll<<i); tmp ^= (1ll<<i);
}
}
vis[high] = 1;
ans[++len].op = 1; ans[len].a = x; ans[len].b = (1<<high);
x ^= (1ll<<high);
work(x);
}
int main()
{
x = read();
work(x);
printf("%d\n", len);
for(int i=1; i<=len; i++)
{
if(!ans[i].op)
{
printf("1 %lld %lld\n", ans[i].a, ans[i].b);
}
else
{
printf("2 %lld %lld\n", ans[i].a, ans[i].b);
}
}
return 0;
}
C. 有趣的区间问题
我明知道它是个套路,本来打算建一个线段树,区间拓展时维护Max-Min的绝对值的最小值,写了一半发现不会更新!?然后换cdq分治,发现需要查询区间定值出现的次数,难道是可持久化权值线段树??我又看到了内存,我还发现了我主席树的板子还没打熟***
"果断"放弃,偷懒用了个__builtin_popcount来数1,然而我并不知道这个函数还有开不开long long的区别,应该是__builtin_popcountll才对……唉我的15分……
code
//鹤的
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const int logV = 65;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, cnt1[logV], cnt2[logV], cnt3[logV], cnt4[logV], eq[maxn];
ll a[maxn], ans;
#define Clear(a) memset(a, 0, sizeof(a))
#define popc __builtin_popcountll
//我知道我为什么没分了!因为我用的是__builtin_popcount没开ll!!!
inline ll read()
{
ll x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
void solve(int l, int r)
{
if(l > r) return;
if(l == r) {ans++; return;}
int mid = (l + r) >> 1;
Clear(cnt1), Clear(cnt2), Clear(cnt3), Clear(cnt4);
eq[mid] = 0;
//预处理中点必选,右区间的合法个数
for(ll i=mid+1,mn=a[mid+1],mx=a[mid+1]; i<=r; i++,mn=min(mn,a[i]),mx=max(mx,a[i]))
{
eq[i] = eq[i-1] + (popc(mn) == popc(mx));
}
ll mn = inf, mx = -inf, mn1 = inf, mx1 = -inf, mn2 = inf, mx2 = -inf;
//从大到小枚举左端点i,对于一个固定的i,在[m+1,r]之间一定会存在两个分界点p1,p2
//把右区间分为3部分,分类讨论max和min都在那里取到
//随着i递减,max和min都在左端/右端取到都会变难,p1,p2都是递增的
//第1种和第3种的贡献:右端点用预处理的,左端点直接距离*合法性
for(int i=mid,p1=mid,p2=mid; i>=l; i--)
{
mn = min(mn, a[i]); mx = max(mx, a[i]);
while(p1<r && mn<=min(mn1,a[p1+1]) && mx>=max(mx1,a[p1+1]))
{
p1++; mn1 = min(mn1, a[p1]); mx1 = max(mx1, a[p1]);
cnt1[popc(mn1)]++; cnt2[popc(mx1)]++;
}
while(p2<r && (mn<=min(mn2,a[p2+1])||mx>=max(mx2,a[p2+1])))//包含了第一种情况
{
p2++; mn2 = min(mn2, a[p2]); mx2 = max(mx2, a[p2]);
cnt3[popc(mn2)]++; cnt4[popc(mx2)]++;
}
ans += (popc(mn)==popc(mx))*(p1-mid)+eq[r]-eq[p2];
//讨论是max还是min在左区间取到
if(mn<=mn2) ans += cnt4[popc(mn)]-cnt2[popc(mn)];//左端最小值小于右端最小值
//最小值在左边取,最大值在右边取,用全部减掉最大值也在左边取的个数,就是最大值和最小值pop相等的数目
else ans += cnt3[popc(mx)]-cnt1[popc(mx)];
//关于我本来打算开一棵可持久化权值线段树来找个数这件事……
}
solve(l, mid); solve(mid+1, r);
}
int main()
{
n = read();
for(int i=1; i<=n; i++) a[i] = read();
solve(1, n);
printf("%lld\n", ans);
return 0;
}
D. 无聊的卡牌问题
开了一堆set找了一堆指针结果RE了10分,用find来跳过已经删掉的数WA了个6,气急败坏地写了个无脑大暴力WA 43,然而错误原因并不是我的暴力写错了而是——(通过鹤正解的代码发现了一些东西)
后手为0,必须留下一个为0的根最后取,但是为什么要人为留下,难道不是一定会有吗?
除了最后一个为0的根以外其他的都随便取
如果有解最后一个根是给后手的,所以保证有解就说明我不取它也存在?
问题跳过并不是同一次,当x=13时无解了,当x=12时应该跳过后手的最后一个根
Answer:
后手的最后一个根注定是最后取的,如果提前把它取出来就会导致后面应该取1的无解
因为这个1能取出来的条件就是他下面的叶子被取走了,而我取走了根并没有起到开路的作用
这也说明我一开始那种暴力是不可能改对的,因为它不能判断谁是根也就无法跳过
保证有解也就保证了跳过这个根一定还能取到
关于像括号序列一样用栈建树之类的其他题解大概非常详细了,于是Cat又被打了lazy标记
dfs也可以切,我正打算去研究!
code
//鹤题解
//问题又来了:留第一个为什么不对?按说留每一个都是一样的??
//留topsort最后一个一定是对的,最后一个留不下其他的都留不下
//不知道怎么证明留前面的不对
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1206;
int n, k, a[maxn], bz[maxn], du[maxn], used[maxn], res;
int d[maxn][3], id[maxn], p[maxn][3], tot, w, fa[maxn], c[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read();
for(int i=1; i<=3*n; i++)
{
int x = read(); a[x] = 1;
}
for(int i=1; i<=6*n; i++)
{
if(!w || a[d[w][0]]!=a[i])
{
w++; d[w][0] = i; c[w] = 1; id[w] = ++tot;
//w就是栈的top
}
else
{
d[w][c[w]++] = i;
if(c[w] == 3)
{
k = id[w]; fa[k] = id[w-1]; bz[k] = a[d[w][0]];
memcpy(p[k], d[w], sizeof(p[k]));
w--;
}
}
}
//我试图找到错因
//p数组里并没有非法内容!?
/*for(int i=1; i<=tot; i++)
{
printf("---%d %d %d\n", p[i][0], p[i][1], p[i][2]);
}*/
for(int i=1; i<=tot; i++) du[fa[i]]++;
for(int i=1; i<=tot; i++) if(fa[i]==0 && bz[i]==0) res++;
for(int i=1; i<=2*n; i++)
{
int c = i&1, x = 0;
//printf("i = %d\n", i);
for(int j=1; j<=tot; j++)
{
//printf("j = %d\n", j);
if(du[j]==0 && !used[j] && bz[j]==c)
{
//i<2*n最后一次把根取出是合理的
//res==1说明他是最后一个根
//为什么取其它的根不会出现挡路?
//在保证有解的情况下,前面的根本来就会在前面被取出,这时还有叶子符合条件
//任何时刻去选这些根叶子都存在,先后无关,所以只需要判断最后一个
if(i<2*n && c==0 && fa[j]==0 && res==1)
{
//printf("???\n");
continue;
}
//上面这个continue是在干嘛?所以我把它删了
//于是我发现得分结果和我的43分大暴力是一样的
//错误原因一个是没找到答案,一个是多输出了一行0!?
//其实都是没找到答案
//正好少了一组,直到最后都没输出来
//printf("%d is true\n", j);
x = j; break;
}
}
/*
后手为0,必须留下一个为0的根最后取,但是为什么要人为留下,难道不是一定会有吗?
除了最后一个为0的根以外其他的都随便取
如果有解最后一个根是给后手的,所以保证有解就说明我不取它也存在?
问题跳过并不是同一次,当x=13时无解了,当x=12时应该跳过后手的最后一个根
Answer:
后手的最后一个根注定是最后取的,如果提前把它取出来就会导致后面应该取1的无解
因为这个1能取出来的条件就是他下面的叶子被取走了,而我取走了根并没有起到开路的作用
这也说明我一开始那种暴力是不可能改对的,因为它不能判断谁是根也就无法跳过
保证有解也就保证了跳过这个根一定还能取到
*/
//printf("x = %d\n", x);//x居然会等于0?
//加上continue语句x==8,这说明答案就是一定存在??那为什么没有它的话就找不到答案呢
printf("%d %d %d\n", p[x][0], p[x][1], p[x][2]);
du[fa[x]]--; used[x] = 1;
if(c==0 && fa[x]==0) res--;
}
return 0;
}
以下是dfs的版本,能想到这个版本的前提是先想到有些选法可能会导致另一个人无解。%%%caorong
code
//dfs
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
int n, has[maxn], vis[maxn];
struct Op
{
int x, y, z;
}e[maxn];
set<int> s;
set<int>::iterator it;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
void dfs(int op, int gan)
{
if(gan == n*2)
{
for(int i=1; i<=gan; i++)
{
printf("%d %d %d\n", e[i].x, e[i].y, e[i].z);
}
exit(0);
}
int ans[4], tot;
if(op == 1) //该A选了
{
int siz = (6*n-gan*3)/3;//剩下的牌
for(int ty=1; ty<=siz; ty++)
{
int now = 0, yes = 0; tot = 0;
int ok = 0;
for(it=s.begin(); it!=s.end(); it++)
{
int id = *it;
if(has[id] && !vis[id])
{
now++; ans[++tot] = id;
if(now == 3)
{
ok++;
//为了找到不同的情况,因为回溯的时候把ans又加回去了
//因为回溯写在了for循环的外层,所以每次都只能找到前3个
//但是回溯在ty也就是3个的选择组数的内层,它是不会重复的
//用它来判断掉和上一次一样的选择
if(ok == ty) {yes = 1; break;}
else {now = 0; tot = 0;}
}
}
else {now = 0; tot = 0;}
}
if(yes)
{
s.erase(ans[1]); s.erase(ans[2]);
s.erase(ans[3]);
vis[ans[1]] = vis[ans[2]] = vis[ans[3]] = 1;
e[gan+1] = (Op){ans[1], ans[2], ans[3]};
dfs(!op, gan+1);
//正是dfs给了它返悔的机会,如果找不到方案那就回溯,
//时间很正确。因为只需要回溯一次!也就是后手的树根
s.insert(ans[1]);
s.insert(ans[2]);
s.insert(ans[3]);
vis[ans[1]] = vis[ans[2]] = vis[ans[3]] = 0;
}
}
}
else
{
int siz = (6*n-gan*3)/3;
for(int ty=siz; ty>=1; ty--)
{
int now = 0, yes = 0; tot = 0;
int ok = 0;
for(it=s.begin(); it!=s.end(); it++)
{
int id = *it;
if(!has[id] && !vis[id])
{
now++; ans[++tot] = id;
if(now == 3)
{
ok++;
if(ok == ty) {yes = 1; break;}
else {now = 0; tot = 0;}
}
}
else {now = 0; tot = 0;}
}
if(yes)
{
s.erase(ans[1]); s.erase(ans[2]); s.erase(ans[3]);
vis[ans[1]] = vis[ans[2]] = vis[ans[3]] = 1;
e[gan+1] = (Op){ans[1], ans[2], ans[3]};
dfs(!op, gan+1);
s.insert(ans[1]); s.insert(ans[2]); s.insert(ans[3]);
vis[ans[1]] = vis[ans[2]] = vis[ans[3]] = 0;
}
}
}
}
int main()
{
n = read();
for(int i=1; i<=n*3; i++)
{
int x = read();
has[x] = 1;
}
for(int i=1; i<=6*n; i++) s.insert(i);
dfs(1, 0);
return 0;
}