2019年中国大学生程序设计竞赛(CCPC) - 网络选拔赛部分题解


^ & ^(位运算+思维)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6702

题目大意

给定整数A,B,请求出C的最小值使得(A xor C)&(B xor C)的值最小。

若当C==0(A xor C)&(B xor C)==0,请输出1

思路

(A xor C)&(B xor C)=(A & B) xor C,再根据异或的特点可以知道 C=A&B(A xor C)&(B xor C)=0,即得到最小值

别忘了特判输出 1 1 1的情况。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+100;

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        ll a,b;
        cin>>a>>b;
        ll c=a&b;
        if(c==0) cout<<"1"<<endl;
        else cout<<c<<endl;
    }
	return 0;
}

Shuffle Card(思维)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6707

题目大意

给定 n n n张卡片,每张卡片上都有一个数字 a i a_i ai 1 < = a i < = n 1<=a_i<=n 1<=ai<=n,并且每一张卡片上的数字互不相同。一开始所有的卡片按照数字大小的顺序由小到大排列在桌上。
接下来会进行 m m m次操作,每次操作会把卡片 x x x(值为 x x x的卡片)移至序列的首位。

请输出 m m m次操作之后卡片的顺序。

思路

每一次都需要把卡片 x x x移动到首位,那就意味着在卡片 x x x前面的所有卡片都要向后退一位。
硬模拟的话太麻烦,有超时的风险,代码量还大,懒狗表示十分不情愿。
反向一想,为什么我们要让在卡片 x x x前面的所有卡片后退呢?为什么来到首位的卡片 x x x一定要是原本的那张卡片 x x x呢?

不当谜语人了,其实每次操作的时候我们并不需要把卡片 x x x移动到首位,而是做一件等价的事取代这个操作:把一张新的值为x的卡片x'放在首位。
最后输出答案的时候,由于每张卡片只会出现一次,所以每一张卡片最后的位置就应该是它在这个序列中第一次出现的位置

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+100;
bool vis[maxn];
int main()
{
    deque<int> q;
    int n,m,x;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        q.push_back(x);
        vis[i]=false;
    }
    for(int i=1;i<=m;i++)
    {
        cin>>x;
        q.push_front(x);
    }
    while(n--){
        while(vis[q.front()]){
            q.pop_front();
        }
        cout<<q.front();
        vis[q.front()]=true;
        q.pop_front();
        cout<<" ";
    }
}

Windows Of CCPC(规律+思维)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6708

题目大意

已知 n = = k n==k n==k时, A A A是一个由CP拼凑而成的大小为 2 k ∗ 2 k 2^k*2^k 2k2k的矩阵。
n = = 1 n==1 n==1时,矩阵 A A A为:
在这里插入图片描述
n = = 2 n==2 n==2时,矩阵 A A A为:
在这里插入图片描述
请求出 n = = k n==k n==k时矩阵 A A A的样子。

思路

这种题就属于那种只要看对眼了就可以牵回AC区防止爆0的题目。

样例中给出了 n = = 3 n==3 n==3时矩阵A的值:

CCCCCCCC
PCPCPCPC
PPCCPPCC
CPPCCPPC
PPPPCCCC
CPCPPCPC
CCPPPPCC
PCCPCPPC

一开始博主并没有注意到有什么规律,疯狂薅头发。
紧接着,我顺着CCPC的顺序看了一下 n = = 1 n==1 n==1时的矩阵A,然后用同样的方法看了一下 n = = 2 n==2 n==2时的矩阵 A A A,然后发现了一个东西:
在这里插入图片描述
我们把 n = = 2 n==2 n==2时的矩阵 A A A拆成四个模块之后,我们发现三个C对应的模块的内容是相同的,而P所对应的模块和C对应的模块内容上是相反的。这样的话只要知道了矩阵 A k A_k Ak红色模块的内容就可以推出其他模块的内容。
而根据题目中给出的所有矩阵 A A A我们可以发现,矩阵 A k A_k Ak的红色模块的内容就是矩阵 A k − 1 A_{k-1} Ak1

k k k的值最大为 10 10 10,所以我们直接预处理出矩阵 A 10 A_{10} A10的值,矩阵 A k A_k Ak一定是矩阵 A 10 A_{10} A10的子矩阵(见红色字体)。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+100;
char s[2500][2500];
void init()
{
    s[1][1]=s[1][2]=s[2][2]='C';
    s[2][1]='P';
    for(int i=2; i<=10; i++)
    {
        for(int j=1; j<=(1<<(i-1)); j++)
            for(int k=1+(1<<(i-1)); k<=(1<<i); k++)
            {
                s[j][k]=s[j][k-(1<<(i-1))];
                s[j+(1<<(i-1))][k]=s[j][k-(1<<(i-1))];
            }
        for(int j=1+(1<<(i-1)); j<=(1<<i); j++)
            for(int k=1; k<=(1<<(i-1)); k++)
            {
                if(s[j-(1<<(i-1))][k]=='C')
                    s[j][k]='P';
                else
                    s[j][k]='C';
            }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    init();
    while(t--)
    {
        int n;
        cin>>n;
        for(int i=1; i<=(1<<n); i++)
        {
            for(int j=1; j<=(1<<n); j++)
                cout<<s[i][j];
            cout<<'\n';
        }
    }
}

Fishing Master(贪心+优先队列)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6709

题目大意

卖鱼强正在钓鱼,他要把鱼塘里的鱼全部钓出来煮熟带回家当晚餐。

鱼塘里现在有 n n n条鱼,煮熟第 i i i条鱼的时间为 a i a_i ai分钟。
卖鱼强抛出鱼钩之后等待 k k k分钟就会钓上一条鱼( n n n条鱼中的任意一条,一定会上钩,除非鱼塘没有鱼了)。
卖鱼强在钓鱼的时候无法煮鱼,但在煮鱼的时候可以钓鱼。

请问:卖鱼强最少需要多少时间才能把鱼塘里所有的鱼煮熟带回家呢?

思路

有点像小学的时候老师讲过的煮茶问题:给出煮茶过程中每一步的内容与所需要的时间,煮茶的部分过程是可以同时进行的,求出喝到茶的最短时间
同理,对于这道题而言,我们肯定希望在煮鱼的时候钓尽可能多的鱼,让钓鱼所需要花费的额外时间全部融入在煮鱼的时间内。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+100;
ll a[maxn];
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        ll m;
        int n;
        cin>>n>>m;
        for(int i=1;i<=n;i++)
            cin>>a[i];
        sort(a+1,a+1+n,greater<int>());
        priority_queue<ll> q;
        ll num=1;
        ll ans=m;
        for(int i=1;i<=n;i++)
        {
            ans+=a[i];
            num+=a[i]/m;
            q.push(a[i]%m);
            if(num<i){
                ans+=m-q.top();
                q.pop();
            }
        }
        cout<<ans<<'\n';
    }
}

path(BFS+离线处理)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6705

题目大意

给出一张由 n n n个点, m m m条边组成的带权有向图,然后会有 q q q次询问。
每次询问需要输出第 k k k小的路线权值和。

所谓路线权值和为某一条路线上的权值之和。
需要注意的是:在下图中,a→b→a也是合理的路线:
在这里插入图片描述

思路

关于离线处理,博主在Codeforces Round #751 (Div. 2)部分题解(A ~ C)的B题中进行了说明,不太懂的小伙伴可以先去做一下那道题,理解一下离线处理的概念。

这道题实际上是一道十分普通的广搜题:

  • 出发点可以是任意一个有出边的点;
  • 每次向下搜索时用的是自己所有的出边
  • 所有的边可以被使用无数次;

只不过我们每次需要找到当前权值最小的路线,记录它的值并优先根据这条路线推衍出之后的路线,所以要用 优先队列(priority_queue) 进行存储,辅助搜索。

仅是上面的部分还不足够我们AC,我们还需要再做一些处理。

  • 定义四元组 ( u , v , w , p ) (u,v,w,p) (u,v,w,p),其意义为: v v v是点 u u u的第 p p p个连接点 ( u → v ) (u→v) (uv),两者之间的边权值为 w w w
  • 定义 G [ m a x n ] G[maxn] G[maxn]数组, G [ i ] G[i] G[i]负责存储点 i i i的所有出边信息;
  • 将所有的边按照权值大小关系由小到大排序;
vector<pair<ll,int> > G[maxn];
scanf("%d%d%d",&n,&m,&q);
for(int i=1; i<=m; i++)
{
    int u,v;
    ll w;
    scanf("%d%d%lld",&u,&v,&w);
    G[u].push_back(make_pair(w,v)); 
    //方便排序,pair类型排序时会按照pair->first的值由小到大默认排序
}
for(int i=1; i<=n; i++)
    sort(G[i].begin(),G[i].end());

接下来我们将所有的询问进行离线处理,并找出要查找的 k i k_i ki中最大的那个以确定广搜的程度。

int ans[maxn];
int maxx=-1;
for(int i=1; i<=q; i++)
{
    scanf("%d",&ans[i]);
    maxx=max(ans[i],maxx);
}

最后我们说一下如果当前四元组的信息是 ( u , v , w , p ) (u,v,w,p) (u,v,w,p),接下来的可能。

  1. 首先,我们考虑接下来选的边是 v v v的一条出边。如果 v v v具有出边,那我们就要选它权值最小的那条出边,也就是 G [ v ] [ 0 ] G[v][0] G[v][0]
    那么此时状态就由 ( u , v , w , p ) (u,v,w,p) (u,v,w,p)变成了 ( v , G [ v ] [ 0 ] . s e c o n d , w + G [ v ] [ 0 ] . f i r s t , 0 ) (v,G[v][0].second,w+G[v][0].first,0) (v,G[v][0].second,w+G[v][0].first,0)
  2. 其次,我们考虑接下来选的边是 u u u的另一条出边。如果当前 u → v u→v uv不是点u的全部出边中权值最大的那条边,那么我们就可以选比这条出边权值要大的当前最优解— G [ u ] [ p + 1 ] G[u][p+1] G[u][p+1]
    那么此时状态就由 ( u , v , w , p ) (u,v,w,p) (u,v,w,p)变成了 ( u , G [ u ] [ p + 1 ] . s e c o n d , w + G [ u ] [ p + 1 ] . f i r s t − G [ u ] [ p ] . f i r s t , p + 1 ) (u,G[u][p+1].second,w+G[u][p+1].first-G[u][p].first,p+1) (u,G[u][p+1].second,w+G[u][p+1].firstG[u][p].first,p+1)
    ( u → v u→v uv u → G [ u ] [ p + 1 ] . s e c o n d u→G[u][p+1].second uG[u][p+1].second是互斥的)

然后就没有然后了,就可以AC了。
(四元组灵感来源于大佬博客https://www.cnblogs.com/19992147orz/p/11405833.html)

AC代码

//cin会T掉,关闭输入流不知道可不可以
//博主直接就scanf了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+100;

ll value[maxn];
int ans[maxn];

struct node
{
    ll w;
    int u,v,p;
    node(){}
    node(int uu,int vv,ll ww,int pp):u(uu),v(vv),w(ww),p(pp){}

    bool operator<(const node&a)const{
        return a.w<w;
    }
};

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m,q;
        vector<pair<ll,int> > G[maxn];
        scanf("%d%d%d",&n,&m,&q);
        for(int i=1;i<=m;i++)
        {
            int u,v;
            ll w;
            scanf("%d%d%lld",&u,&v,&w);
            G[u].push_back(make_pair(w,v));
        }
        for(int i=1;i<=n;i++)
            sort(G[i].begin(),G[i].end());
        priority_queue<node> pq;
        for(int i=1;i<=n;i++)
            if(G[i].size()){
                pq.push(node(i,G[i][0].second,G[i][0].first,0));
                //cout<<i<<"→"<<G[i][0].second<<"="<<G[i][0].first<<endl;
            }
        int maxx=-1;
        for(int i=1;i<=q;i++)
        {
            scanf("%d",&ans[i]);
            maxx=max(ans[i],maxx);
        }
        int pos=1;
        while(pos<=maxx)
        {
            node tmp=pq.top();
            pq.pop();
            int u=tmp.u;
            int v=tmp.v;
            int p=tmp.p;
            ll w=tmp.w;
            value[pos]=w;
            //cout<<"value["<<pos<<"]=("<<u<<"→"<<v<<")"<<w<<endl;
            if(G[v].size())
                pq.push(node(v,G[v][0].second,w+G[v][0].first,0));
            if(p!=G[u].size()-1)
                pq.push(node(u,G[u][p+1].second,w+G[u][p+1].first-G[u][p].first,p+1));
            pos++;
        }
        for(int i=1;i<=q;i++)
            printf("%lld\n",value[ans[i]]);
        //cout<<endl;
    }
}

后话

感谢阅读,希望能对你产生一点用处。

以下台词取自《银魂》第95集:
(MADAO才是人生赢家)

在这里插入图片描述

"奉行大人,能不能稍稍听我讲一段陈年往事呢"
"『很久以前,在某个地方有一对彼此深爱的夫妇』"
"『丈夫是下级武士的儿子,而妻子是高级官僚的女儿』"
"『尽管因为身份的差别遭到反对,但是通过入赘,两个人总算是走在了一起』"
"『因为家世显赫,工作也很努力,丈夫被委任到很高的职位』"
"『但是丈夫却不满足』"
"『既然是男子汉,自己的路就应该由自己来选择,靠自己的双脚走到目的地』"
"『事到如此,肯定也要穿得体面点,所以妻子连西服都给他准备好了』"
"『但是丈夫却勃然大怒,和妻子大吵一架』"
"『丈夫穿着平时脏兮兮的衣服,开始了他就职的初次登场』"
"『看到他这个样子,保守的上司大发雷霆』"
"『丈夫没有办法,只好在那个晚上穿着西装去上司家里道歉』"
"『结果发现,早已有人先自己一步,跪在碎石地上向领导赔罪』"
"『是个把额头贴着地面道歉,打扮邋遢的女子』"
"『她口中说着,在他们家这已经是正式场合的服装了,所以请原谅她丈夫』"
"『一边哭泣,一边道歉的女子脱去平时那华丽的衣着』"
"『对着比自己家世低得多的丈夫的上司低头赔罪』"
"『丈夫看到了这一幕,也把自己的西装割得破破烂烂』"
"『然后和妻子一同,边哭边赔罪,直到天亮』"
"『从那之后,丈夫每次都会穿着那套西装出席重要场合』"
"『每当想起忍受耻辱赔罪的妻子,就下定决心自己也要为了妻子舍弃渺小的自尊加倍努力』"
"『那件破破烂烂的西装由于改的太厉害了,整个都短了一截』"
"虽然被委托说『太不像样了,把这件新西装交给他』"
"真抱歉啊,这家伙又没穿上你给他准备的西装"
"真是个相当顽固的家伙啊"
"不对,应该说真是对相当顽固的夫妇啊"
"都穿得那么邋遢,却又是那么的般配"

吾日三省吾身:日更否?刷题否?快乐否?
更新了,但不是日更;已刷;烦躁
路漫漫其修远兮,吾将上下而求索

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值