CQOI2013vp记

新Nim游戏

update on 2023.6.8

进行了一些小修改

因为第一次操作与其它操作不同,考虑拿出来单独做,剩下的操作就变成了 Nim游戏 了。

回忆一下 Nim游戏 先手必胜的条件是什么,是所有数的异或和不为 0,那么这题就转化为求原集合的一个子集,该子集中的数不能被子集中的其它数的异或和表示,且子集内的数的和要尽量大。

首先这题输出 1 显然没分,因为先手可以只留下一堆,后手的第一次操作就只能什么都不取,剩下的异或和不为 0 ,先手必胜。然后我们看到异或和,想到线性基。一个数如果可以插入线性基,那就插入,如果不可以插入,则说明该数可以被线性基里的数的异或和表示,抛弃该数。那么现在就只剩下怎么取数了。想到若一个线性基可以被两个数插入,插入大的数必不劣于插入小的数,所以从大到小试插入即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=110;
int n,a[N],p[40];
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    read(n);
    for(int i=1;i<=n;i++){
        read(a[i]);
    }
    sort(a+1,a+n+1);
    int ans=0;
    for(int i=n;i;i--){
        int x=a[i];
        for(int j=30;j>=0;j--){
            if(x>>j&1){
                if(!p[j]){
                    p[j]=x;
                    break;
                }
                else{
                    x^=p[j];
                }
            }
        }
        if(!x){
            ans+=a[i];
        }
    }
    write_endl(ans);
    return 0;
}

[CQOI2013]棋盘游戏

可以发现白棋能赢当且仅当两个棋子初始距离为 1,否则白棋必输。

那么问题只剩下黑棋最少需要几步才能吃掉白棋。可以发现最多需要 3n 步,要求出准确答案,这里涉及一个叫做对抗搜索的博弈论问题。

对抗搜索

定义:竞争环境中多个玩家之间的目标是有冲突的,称为对抗搜索问题。
特点:

  1. 确定的、完全可查的环境。
  2. 智能体轮流行动。
  3. 零和博弈
  4. 每一步行动的结果确定

这题中先手的目的是尽量拖时间,后手的目的是尽快结束游戏,所以搜索时记录操作人,操作步数,位置状态,然后每次操作按照两者的目的操作即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int inf=1e9;
int n,r1,c1,r2,c2,f[2][62][21][21][21][21],dx[2][8],dy[2][8];
int dfs(int opt,int step,int r1,int c1,int r2,int c2){
    if(step>n*3){
        return inf;
    }
    if(f[opt][step][r1][c1][r2][c2]&&f[opt][step][r1][c1][r2][c2]!=inf){
        return f[opt][step][r1][c1][r2][c2];
    }
    if(r1==r2&&c1==c2){
        if(opt)return inf;
        return 0;
    }
    int ans;
    if(opt==0){
        ans=0;
        for(int i=0;i<4;i++){
            int x=r1+dx[0][i],y=c1+dy[0][i];
            if(x>n||x<1||y>n||y<1){
                continue;
            }
            ans=max(dfs(1,step+1,x,y,r2,c2),ans);
        }
    }
    else{
        ans=inf;
        for(int i=0;i<8;i++){
            int x=r2+dx[1][i],y=c2+dy[1][i];
            if(x>n||x<1||y>n||y<1){
                continue;
            }
            ans=min(dfs(0,step+1,r1,c1,x,y),ans);
        }
    }
    return (f[opt][step][r1][c1][r2][c2]=ans+1);
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    dx[0][0]=1,dy[0][1]=1,dx[0][2]=-1,dy[0][3]=-1;
    dx[1][0]=1,dx[1][1]=2,dy[1][2]=1,dy[1][3]=2,dx[1][4]=-1,dx[1][5]=-2,dy[1][6]=-1,dy[1][7]=-2;
    read(n),read(r1),read(c1),read(r2),read(c2);
    if(abs(r1-r2)+abs(c1-c2)==1){
        puts("WHITE 1");
        return 0;
    }
    printf("BLACK %d",dfs(0,0,r1,c1,r2,c2));
    return 0;
}

图的逆变换

发现一个性质,如果两个点有相同的出点那么两个点的出点集合必然相同,考虑用种类并查集维护两个点是否有相同的出点,如果从 uv 有边,将 u+nv 分到一类。最后 O(n2) 扫一遍,若存在 i+nj 属于同一类,且 i 没有到 j 的边,输出 No;否则输出 Yes

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=310,base=1145141;
int n,m,fa[N<<1],e[N][N];
int getfa(int x){
    if(fa[x]!=x){
        fa[x]=getfa(fa[x]);
    }
    return fa[x];
}
void solve(){
    read(n),read(m);
    for(int i=1;i<=n*2;i++){
        fa[i]=i;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            e[i][j]=0;
        }
    }
    for(int i=1;i<=m;i++){
        int u,v;
        read(u),read(v);
        u++,v++;
        e[u][v]=1;
        if(getfa(u+n)!=getfa(v)){
            fa[getfa(u+n)]=getfa(v);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(getfa(i+n)==getfa(j)&&!e[i][j]){
                puts("No");
                return;
            }
        }
    }
    puts("Yes");
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t;
    read(t);
    while(t--){
        solve();
    }
    return 0;
}

二进制A+B

本题有两种做法,这里使用的是构造。

先考虑无解情况:若 c=0,a0b0,显然无解;若 popcount(c)>popcount(a)+popcount(b),也是无解;令 len 为原来的基准位数,若答案在二进制下的位数大于 len,无解;其它情况均为有解。

popcount(a)popcount(b),对于有解的考虑分类讨论

  1. popcount(c)popcount(b)
    类似于下面情况
00111111
01000011
10000010

因为 c1 的个数少于 b1 的个数,所以要删去一些 b 中的 1,将要删去的 1 放在高位即可。
2. 若 popcount(c)popcount(a)
类似于下面情况

0111111
0011100
1011011

因为 c1 的个数少于 a1 的个数,所以要删去一些 a 中的 1,将要删去的 1 放在高位即可。
3. 若 popcount(c)popcount(a)+popcount(b)
类似于以下情况

011111100
011100011
111011111

考虑到 $\operatorname{popcount}(c)= \operatorname{popcount}(a)+\operatorname{popcount(b)} 时肯定是两个串前后拼起来,现在位数少了,选择消掉一些位数,可以发现两个长度为 l 的全 1 串加起来会消掉 l 位。设少了 cnt 位,将两个数前 cnt 位设为 1,剩下的全部以不重复占位的方式放在后面。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
int a,b,c,ans;
int Count(int x){
    int cnt=0;
    while(x){
        cnt+=(x&1ll);
        x>>=1ll;
    }
    return cnt;
}
int get_len(int x){
    int cnt=0;
    while(x){
        cnt++;
        x>>=1ll;
    }
    return cnt;
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    read(a),read(b),read(c);
    int cnta=Count(a),cntb=Count(b),cntc=Count(c),len=max(get_len(a),max(get_len(b),get_len(c))),ans;
    if(cnta<cntb){
        swap(cnta,cntb);
    }
    if(!c&&(a||b)){
        ans=-1;
    }
    else if(cntc<=cntb){
        ans=(1<<cntc)-2+(1<<(cnta+cntb-cntc));
    }
    else if(cntc<=cnta){
        ans=(1<<cnta)+(1<<cntc)-1-(1<<(cntc-cntb));
    }
    else if(cntc<=cnta+cntb){
        int cnt=cnta+cntb-cntc;
        cnt=cntc-cnt;
        ans=(1<<(cntc+1))-(1<<cnt)-1;
    }
    else{
        ans=-1;
    }
    if(get_len(ans)>len){
        ans=-1;
    }
    write_endl(ans);
    return 0;
}
posted @   luo_shen  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
点击右上角即可分享
微信分享提示