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;
}