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;
}
posted @ 2022-09-27 20:26  Catherine_leah  阅读(22)  评论(0编辑  收藏  举报
/* */