您的位置:首页 > 其它

河南省多校连萌(七)题解(1~4题)

2017-09-09 19:31 489 查看
题目链接:A、等价交换

题目给出一个只含有加法和乘法的等式,让我们来判断等式是否成立。由于没有括号,所以从左到右扫一遍即可,注意题中是作为等式的每个数都<1e9,但是经过加法和乘法后会爆int,这个我在样例中提到了。还有等号两边都有加号和乘号,处理时需要注意。

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int T;
ll m,n;
char s[55];
int main()
{
cin>>T;
while(T--)
{
memset(s,0,sizeof(s));
scanf("%s",s);
int len=strlen(s);
m=0,n=0;
ll pre=0;
int pos=0;
while(s[pos]<='9'&&s[pos]>='0')
{
pre*=10;
pre+=s[pos++]-'0';
}
ll a[2]={0,0};
int f=0;
for(int i=pos;i<len;)
{

if(s[i]=='+'||s[i]=='*')
{
char t=s[i++];
ll buf=0;
while(s[i]<='9'&&s[i]>='0')
{
buf*=10;
buf+=s[i]-'0';
i++;
}
if(t=='+')
{
a[f]+=pre;
pre=buf;
}
else if(t=='*')
{
pre*=buf;
}
}
else if(s[i]=='=')
{
i++;
a[f]+=pre;
f++;
pre=0;
while(s[i]<='9'&&s[i]>='0')
{
pre*=10;
pre+=s[i++]-'0';
}
}
}
a[1]+=pre;
if(a[0]==a[1])
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
}


题目链接:B、arufuonsusnoufura

本题是给出一个环形字符串,判断去掉几个字符可以构成回文串。考虑非环形的情况下判断最少去掉多少构成回文串时选用的方法是构造一个反串,然后找到最长公共子序列,这里字符串的长度很短,所以可以枚举环形串拆开的位置,然后取最小值即可。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define INF (1<<29)
#define maxn 5005
char s1[maxn],s2[maxn];
int n;
int dp[2][maxn];
int main()
{
//    freopen("/Users/apple/Desktop/testBout.txt","r",stdin);
//    freopen("/Users/apple/Desktop/textioon.txt","w",stdout);
int T;
cin>>T;
while(T--)
{
scanf("%s",s1);
n=strlen(s1);
for(int i=0;i<n;i++)
s1[i+n]=s1[i];
for(int i=0;i<2*n;i++)//求逆串
s2[2*n-i-1]=s1[i];
int mins=1e9;
for(int k=0;k<n;k++)
{
memset(dp,0,sizeof(dp));//初始化
for(int i=0;i<n;i++)//LCS
for(int j=0;j<n;j++)
{
if(s1[i+k]==s2[n+j-k])
dp[(i+k+1)%2][j+1+n-k]=dp[(i+k)%2][j+n-k]+1;
else
dp[(i+1+k)%2][j+n-k+1]=max(dp[(i+k)%2][j+n-k+1],dp[(i+k+1)%2][j+n-k]);
}
mins=min(mins,n-dp[(n+k)%2][n+n-k]);
}
cout<<mins<<endl;
}
return 0;
}


题目链接:teddy的回文串

题意:选出一个子串,然后在后面填充少于子串的字符成一个回文串,问能组成多少组不同的回文串。

1、首先对于一般情况找到一个子串,然后要求填充的字符要小于子串,所以直接以最后一个字符为轴填充回文串即可,此时不需要考虑本身已经构成回文串或者一部分是回文串,假如有m个不同的子串,那就有m种不同的回文串,对于求不同子串的个数,可以套用后缀数组的模板,然后统计n-sa[i]-height[i]即可。

2、可能有的子串末尾2位是相同的,这时可以构造出偶数长度的回文串,同样如果本身已构成一部分回文串的部分不需要考虑,因为会和之前统计的重合,这里我们就要求出所有不同的末尾2位相同的子串,注意这个子串前面一定还要有字符,因为填充长度大于0.我的方法是:从第二位开始,统计和后一个字符相同 的位置,用mark[i]来存从第i位到最后一位满足上述条件的字符个数,之后会用到。然后使用后缀数组中统计到的sa数组和height数组,sa数组代表排名第i的后缀的起始位置,height数组代表排名第i的后缀和第i+1的后缀最长公共前缀,对于公共的前缀不予考虑,所以只需考虑sa[i]+height[i]后面的字符,以此来避免重复子串,从sa[i]到sa[i]+height[i]后面所有的前缀如果有末尾两位相同的ans就要+1,所以mark数组就用上了,ans直接+mark[sa[i]+height[i]]求值,注意之前的问题末尾2个字符前面一定要有字符,特判一下。最后ans要
long long。

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
using namespace std;
const int maxn=400010;
long long int mark[maxn];
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int sa[],int ranks[],int height[],int n,int m)
{
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=str[i]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1)
{
p=0;
for(i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[y[i]]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n) break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)ranks[sa[i]]=i;
for(i=0;i<n;i++)
{
if(k)k--;
j=sa[ranks[i]-1];
while(str[i+k]==str[j+k])k++;
height[ranks[i]]=k;
}
}
int ranks[maxn],height[maxn];
char str[maxn],s1[maxn];
int r[maxn],sa[maxn];
int pre[maxn],vis[maxn];
int T;
int main()
{
//    freopen("/Users/apple/Desktop/testAout.txt","r",stdin);
//    freopen("/Users/apple/Desktop/text1.txt","w",stdout);
scanf("%d",&T);
while(T--)
{
scanf("%s",str);
int len=strlen(str);
int n=len;
for(int i=0;i<=len;i++) r[i]=str[i];
r[len]=0;
memset(vis,0,sizeof(vis));
memset(pre,0,sizeof(pre));
long long int ans=0;
da(r,sa,ranks,height,n,128);
int visit[26];
memset(visit,0,sizeof(visit));
memset(mark,0,sizeof(mark));
int nums=0;
for(int i=n-1;i>=0;i--)
{
if(str[i]==str[i+1])
{
nums++;
}
mark[i]=nums;
}
long long int tt=0;
for(int i=1;i<=n;i++)
{
int buff=0;
if(i==0) buff=1;
else buff=max(height[i],1);
int j=sa[i]+buff-1;
if(j==sa[i]) j++;
tt+=mark[j];
visit[str[i-1]-'a']=1;
ans+=n-sa[i]-height[i];
}
int buf=0;
for(int i=0;i<26;i++)
{
buf+=visit[i];
}
ans-=buf;
printf("%lld\n",ans+tt);
}
}
题目链接:teddy VS bear

其实这一类问题叫做k倍动态减法,具体可以参考曹钦翔的《从“k倍动态减法游戏”出发探究一类组合游戏问题》。当k=2时有叫做fibonacci博弈。

首先考虑k=1的情况,必败态为2的次方数,对必胜态,将数二进制分解,每次取低位的1,对面肯定无法一次将剩下的所有取完。每次取最低位会必胜。
对于k=2的情况,必败态为fibonacci数,一个很重要的性质就是任意整数n都可以分解为不相邻的fibonacci数相加,不相邻的2个数之间差距2倍以上,因此可以分解后每次取最小位。如果不清楚定理,可以参考k=1的情况,我们需要构造一个数列使得每个整数可以由数列中若干个数相加并且这些数倍数差距大于k,我们用a来存这些数,用b来表示前i-1个能构成的最大的数,那么a[i+1]=b[i]+1;然后再构造b[i+1],由于b[i+1]要用到a[i+1],并且不相邻,因此要找到a[j]*2<a[i],b[i+1]=a[i+1]+b[j]。
查询时只需要不断减去最大的a[i]直到0,最后剩下的就是第一次取的值。
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2000000;
int a[maxn],b[maxn];
int main()
{
//    freopen("/Users/apple/Desktop/testBout.txt","r",stdin);
//    freopen("/Users/apple/Desktop/textioon.txt","w",stdout);
int t,n,k;
cin>>t;
for(int cas=1;cas<=t;cas++)
{
cin>>n;
k=2;
if(n<=3)
{
printf("Lose\n");
continue;
}
a[0]=b[0]=1;
int i=0,j=0;
while(a[i]<n)
{
i++;
a[i]=b[i-1]+1;
while(a[j+1]*2<a[i]) j++;
if(a[j]*2<a[i]) b[i]=b[j]+a[i];
else b[i]=a[i];
}
if(a[i]==n)printf("Lose\n");
else
{
int ans=0;
while(n)
{
if(n>=a[i])
{
n-=a[i];
ans=a[i];
}
i--;
}
cout<<ans<<endl;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: