(杭电多校)2023“钉耙编程”中国大学生算法设计超级联赛(2)

Contest Login

1001 Alice 

大致题意是n个怪兽站成一排,Alice和Bob轮流操作,每次操作有两种选择,当一个玩家不能再操作时,就算输

当个数大于k时,可以消灭区间长度刚好为k的怪兽,然后将两边分开即该区间两边的区间就不连续了,保证两边区间均不为空

当区间长度小于等于k时,可以直接将该整段区间怪兽消灭

SG函数

一共有t个样例,然后每个样例都会给出一个k和一个n,也就是给出的k不同,情况不同,对于每一个k,求SG函数,打表找规律

#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int f[N];//记忆化搜索时记录sg值
int k;
//sg(x)代表区间长度为x的sg值
int sg(int x){
    if(f[x]!=-1) return f[x];
    set<int>a;
    for(int i=1;i<=x;i++){
        int j=x-i-k;//将长度为x的区间分为两部分,长度分别为i和j
        if(j<=0) continue;
        a.insert(sg(j)^sg(i));
    }
    //求mex值
    for(int i=0;;i++){
        if(!a.count(i)) return f[x]=i;
    }
}
void solve()
{
    cin>>k;
    memset(f,-1,sizeof f);
    f[0]=0;//当n为0时,即为终止状态0
    //当n为1到k时,都可以直接进行操作1,使得其变为终止状态(即为0),所以f[1到k]均为1
    for(int i=1;i<=k;i++) f[i]=1;
    for(int i=1;i<=k+1;i++) f[i]=sg(i);
    for(int i=k+1;i<=1000;i++) f[i]=sg(i);
    for(int i=1;i<=1000;i++){
        if(f[i]) continue;
        cout<<i<<" "<<f[i]<<endl;
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
//    int t;
//    cin>>t;
//    while(t--)
    solve();
    return 0;
}

当k为3时,发现n必败的值每14个一循环

当k为4时,发现n必败的值每18个一循环

当k为5时,发现n必败的值每22个一循环

所以循环len=k*4+2

而且通过打表发现n%len为k+1时必败

AC代码: 

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
void solve()
{
    int k,n;
    cin>>k>>n;
    int len=4*k+2;
    n%=len;
    if(n==k+1) cout<<"Bob"<<endl;
    else cout<<"Alice"<<endl;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    solve();
    return 0;
}

1002 Binary Number 

题意是一个01序列,一共有k次操作(刚好k次操作),每次操作都是任意选择一个区间,进行翻转(0转1,1转0),然后求出最大的序列是多少

贪心

从头到尾遍历字符串,把某段连续的0区间全部转化成1(为什么不转化又有0又有1的区间呢?因为二进制前面的一个1比后面的所有1加起来都要大),cnt记录有几段0

然后如果cnt>k的话,就直接break

然后循环过后,cnt一定是小于等于k的

如果cnt等于k,那就刚好,直接输出s就行了

但是如果cnt小于k,说明已经将所有的0都转化成1了,但是我们的操作还没有达到k次,我们设法将cnt补到刚好等于k

其实我们完全可以在前面将一次操作分为两次(当01同时存在时),比如0001转为1111可以直接将3个0转为3个1,也可以将0001转为1110再将0转为1,所以一次操作完全可以分成两次操作,需要补k-cnt次操作,如果是偶数次,只需要01转过来再转过去就行了,如果是奇数次,只需要在前面还有0的时候1次操作分成两次,然后剩下的就是偶数次了

注意需要特判:

当全部都是1并且k是1时,就得把最后一个字符变成0

当n为1且k是奇数时,则仅有的一个字符为0(题目说第一个字符初始为1)

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
const int N=1e5+10;
char s[N];
void solve()
{
    LL n;
    LL k;
    scanf("%lld%lld",&n,&k);
    scanf("%s",s);
    LL cnt=0;
    bool ok=true;
    bool flag=true;
    for(int i=0;i<n;i++){
        if(s[i]=='0'){
            ok=false;
            if(flag) cnt++;
            if(cnt>k) break;
            flag=false;
            s[i]='1';
        }
        else {
            flag=true;
        }
    }
    if(ok&&k==1) s[n-1]='0';
    if(n==1&&k%2==1) s[n-1]='0';
    printf("%s\n",s);
}
signed main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    solve();
    return 0;
}

1004 Card Game 

这题是一个汉诺塔问题,就是给你n个堆,起初第一个堆放了k个东西,下面大上面小,其它堆都是空的,需要通过其它空堆的中转作用将k个东西从第一个堆全部放到第二个堆上(也是下面大上面小),问最多可以成功转移的k的个数是多少

我们首先找一下规律,将前面小的数模拟一下

得到n=1,k=0;n=2,k=1;n=3,k=3

然后我们可以发现前后是有关系的,猜测是有个递推式的,即f[i]=f[i-1]*2+1

然后因为样例有1e5个,然后n又有1e9,所以我们需要logn的算法,差不多是对于每一个样例是直接回答,不可能枚举n,所以选择打表找规律

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
const int N=2e6+10,mod=998244353;
LL f[N];
void solve()
{
    int n;
    cin>>n;
    f[1]=0;
    cout<<1<<" "<<f[1]<<endl;
    for(int i=2;i<=n;i++){
        f[i]=(f[i-1]*2+1)%mod;
        cout<<i<<" "<<f[i]<<endl;
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

发现f[n]=2^(n-1)-1

于是用快速幂求解(时间复杂度为logn)

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
const int mod=998244353;
int qmi(int a,int k){
    int res=1;
    while(k){
        if(k&1) res=(LL)res*a%mod;
        a=(LL)a*a%mod;
        k>>=1;
    }
    return res;
}
void solve()
{
    int n;
    cin>>n;
    cout<<qmi(2,n-1)-1<<endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    solve();
    return 0;
}

1007 foreverlasting and fried-chicken

n总和不超过3000,直接双重循环暴力求解

大致思路是暴力循环,搜1和8这两个点(分别记作i,j),首先它们连着大于等于4个公共点记为cnt,然后,然后其中1个点的连的点数(cnti)减4大于等于2,那么我们就可以用组合数学的知识以及乘法原理,来求解,即在cnt中选择4个的方式数乘以在cnti-4中选择2个的方式数

利用邻接矩阵,用bitset(存放二进制数)来存储,bitsetbt[N]表示一共用N个bitset,每个bitset里有N个元素,可以理解为二维数组,比如说u,v相连,则bt[u][v]=bt[v][u]=1,为1代表相连,为0代表不相连 

 

为什么这里用bitset呢?因为这里我们要求两个点连着多少个公共点,而如果暴力求解的话,就要O(n^3)了,于是用到位运算的知识

我们要找i,j的公共点,假设公共点为x,那么bt[i][x]=1,bt[j][x]=1,所以它们如果进行与(&)操作结果是1,说明x是公共点,所以只要将两个二进制串进行与操作,然后返回有一个1就行了,有几个1就说明有一个公共点

AC代码: 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<bitset>
using namespace std;
typedef long long ll;
const int N=1010,mod=1e9+7;
int n,m;
bitset<N>bt[N];
ll C2(ll x){
    return x*(x-1)/2%mod;
}
ll C4(ll xx){
//x范围可达1e6,x^4会超出long long范围
//用__int128,大概是long long的平方
//或者用逆元
    __int128 x=xx;
    __int128 res=x*(x-1)*(x-2)*(x-3)/24%mod;
    ll ans=res;
    return ans;
}
void solve()
{
    cin>>n>>m;
    ll res=0;
    for(int i=1;i<=n;i++) bt[i].reset(); 
    for(int i=0;i<m;i++){
        int u,v;
        cin>>u>>v;
        bt[u][v]=bt[v][u]=1;
    }
 //每次都是将i作为连大于等于6个的那个顶点
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) continue;
            ll cnti=bt[i].count();
            ll cntj=bt[j].count();
            if(bt[i][j]) cnti--,cntj--;//如果i,j相连,那么与i相连的点的个数减1,与j相连的点的个数减1,为下一步算i,j相连了几个公共点做准备
            ll cnt=(bt[i]&bt[j]).count();//i和j相连了几个公共点
            (res+=C2(cnti-4)*C4(cnt))%=mod;
        }
    }
    cout<<res<<endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    solve();
    return 0;
}

1009 String Problem 

大致题意是找只含有一种字符的回文串,求符合要求的所有回文串的长度之和减去个数的最大值

其实就是从头遍历字符串,找到连续相同字符的个数,然后res加上个数-1

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
void solve()
{
    string s;
    cin>>s;
    int res=0;
    int cnt=1;
    for(int i=1;i<s.size();i++){
        if(s[i]==s[i-1]) cnt++;
        else{
            res+=cnt-1;
            cnt=1;
        } 
    }
    if(cnt) res+=cnt-1;
    cout<<res<<endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    solve();
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值