CSDN话题挑战赛第2期
参赛话题:算法题解
写一下签到和对标到区域赛铜牌级别的题目,m题大概是铜牌偏上一点吧。
A. Another A+B Problem(暴力/搜索)
猜成语游戏
题意:给一个两位正整数加两位正整数等于两位正整数的等式,然而我们对于目标等式的每个位置的数字是不确定的,可知给定的等式一共8个字符,然后对于这8个字符每一位都给定一个判定,判定有三种情况,‘G’‘P’‘B’。
‘G’:说明这个位置和目标等式相等
‘P’:说明这个位置在目标等式中存在,但却并不是这个位置
‘B’:说明这个数字在目标等式中根本不存在,或者这个位置上的数字是n,目标等式中只有x个n,前面的判定中已经有了x个对于n的P和G判定,所以这个位置对于n的判定也会为‘B’
求:所有的目标等式个数。
思路:一眼题没啥好讲的,搜索或者循环暴力来就行了。
搜索的话,在搜索之前记得对每个位置标记不可能出现的数字,标记每个数字最多出现次数限制,并且对于所有推得等式都要判定是否合法。
偷个队友的代码
#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
using namespace std;
const int inf=1e18;
const int N = 4e6+10;
const int B=2e6;
char s[100005],f[100005];
int pos[100005][155],t;
int getnum(char c)
{
if(c>='0'&&c<='9')
return c-'0';
return (c-'a')+10;
}
signed main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
cin>>t;
while(t--)
{
cin>>s+1>>f+1;
int n=strlen(s+1),m=strlen(f+1);
for(int i=0;i<36;i++) pos[n][i]=0;
for(int i=n-1;i>=1;i--)
{
for(int j=0;j<36;j++)
pos[i][j]=pos[i+1][j];
pos[i][getnum(s[i+1])]=i+1;
}
// for(int i=1;i<=n;i++)
// {
// for(int j=0;j<36;j++)
// cout<<pos[i][j]<<" "<<i<<" "<<j<<endl;
// }
int minn=1e9,l=1,r=n;
for(int i=1;i<=n;i++)
{
int u=i,flag=1;
if(s[i]!=f[1]) continue;
for(int j=2;j<=m;j++)
{
if(pos[u][getnum(f[j])]==0){flag=0;break;}
//cout<<i<<" "<<u<<" "<<pos[u][getnum(f[j])]<<endl;
u=pos[u][getnum(f[j])];
}
if(!flag) break;
int res=u-i+1;
if(minn>res)
{
minn=res;
l=i;r=u;
}
}
for(int i=l;i<=r;i++) cout<<s[i];
cout<<endl;
}
system("pause");
return 0;
}
E.Expenditure Reduction(思维/预处理)
题意:给定模式串s和文本串t,你可以删除s的一个前缀和一个后缀来取得最短的字符串满足这个字符串中的某个子序列是文本串t
思路:
拿到这题看的时候发现好像很简单,就把每个字母出现的位置用vector一存。然后根据t的首字母出现的位置然后每次二分后面一个字母出现的位置,然后不断更新找到最短长度。后来发现会t第32样例,大概是aaaaaaaaaa这种东西。
然后想起ccpc网络赛队友秒的一个题,是把链式前向星的思想用上,然后能够做到o(n*m)的预处理和o(1)的查询。
我们设nex[i][a]为在i这个位置要寻找字符a的话,我们最近应该跳到那个位置上。
#include<bits/stdc++.h>
#define endl '\n';
using namespace std;
const int N = 1e5+100;
int tt,n,m,l,r;
char s[N],t[500];
int nex[N][40];//位置i要找字符j的话最近的一个应该找谁
inline int tonum(char a)
{
if(a<='9'&&a>='0')
return a-'0';
return a-'a'+10;
}
signed main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
for(cin>>tt;tt;tt--)
{
cin>>s+1>>t+1;
int n=strlen(s+1);
int m=strlen(t+1);
memset(nex[n],-1,sizeof(nex[n]));
for(int i=n-1;i>=1;i--)
{
for(int j=0;j<40;j++)
nex[i][j]=nex[i+1][j];
nex[i][tonum(s[i+1])]=i+1;
}
int l=1,r=n;
int minn=1e9;
for(int i=1,now;i<=n;i++)
{
if(s[i]!=t[1])
continue;
now=i;
for(int j=2;j<=m;j++)
{
if(nex[now][tonum(t[j])]==-1)
goto xx;
now=nex[now][tonum(t[j])];
}
if(minn>now-i+1)
{
minn=now-i+1;
l=i;
r=now;
}
}
xx:;
for(int i=l;i<=r;i++)
cout<<s[i];
cout<<endl;
}
return 0;
}
G.Gua(口舍匕匕)
题意不讲了,思路也不讲了,实名喷一下出题人,您英语水平是不是从小到大都是满分,所以才能出这么逆天的题面,签到题给大家找了一篇夜香?
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define ios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
using namespace std;
const int inf=1e18;
const int N=7e5+5;
int B,R,D,S;
void solve()
{
cin>>B>>R>>D>>S;
if(R==0)
{
if(!D)
{
cout<<"ok"<<endl;
return;
}
else
{
cout<<"gua!"<<endl;
return;
}
}
int k=(R*S/60LL+1LL)*B;
if(k<D)
cout<<"gua!"<<endl;
else
cout<<"ok"<<endl;
}
signed main()
{
ios;
int T;cin>>T;
while(T--)
solve();
return 0;
}
H(并查集)
题意:一个环形数组,我们每次可以涂连续k个位置,后来涂的东西可以覆盖之前涂的东西。问你是否能涂出目标数组,能的话输出涂的次数,不能就输出-1
思路:先开二倍空间搞出换型数组,然后并查集统计每个块的大小,如果块最大都不到k那就-1,否则涂的次数就是每个块的大小除k向上取整之和。
#include <iostream>
using namespace std;
const int N = 1e6+100;
int dsu[N],siz[N],a[N],maxx,n,m,k,ans,t;
int tfind(int x)
{
if(x==dsu[x])
return x;
return dsu[x]=tfind(dsu[x]);
}
void tmerge(int x,int y)
{
x=tfind(x),y=tfind(y);
if(x==y)
return ;
dsu[y]=x;
siz[x]+=siz[y];
maxx=max(maxx,siz[x]);
}
int chu(int x,int d)
{
if(x%d!=0)
return x/d+1;
return x/d;
}
int main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
for(cin>>t;t;t--)
{
cin>>n>>m>>k;
ans=0;maxx=1;
for(int i=1;i<=n;i++){
cin>>a[i];
dsu[i]=i;siz[i]=1;
}
for(int i=1;i<=n-1;i++)
if(a[i]==a[i+1])
tmerge(i+1,i);
if(a[n]==a[1])
tmerge(1,n);
if(maxx<k){
cout<<-1<<endl;
continue;
}
for(int i=1;i<=n;i++)
if(dsu[i]==i)
ans+=chu(siz[i],k);
cout<<ans<<endl;
}
return 0;
}
N(签到,字典序)
题意:按照顺序输出两个字符串
思路:这要什么思路,,
#include<bits/stdc++.h>
using namespace std;
signed main()
{
string s1,s2;
cin>>s1>>s2;
if(s1<s2)
cout<<s1+'<'+s2;
else if(s1>s2)
cout<<s1+'>'+s2;
else
cout<<s1+'='+s2;
return 0;
}
M(思维)
题意:给定m个含有n个元素的排列,规定元素x大于y的话只需要有一个排列中有x在y前面即可。而x大于z的话又可以通过这样的步骤来得出:
1.x在某个排列里比y靠前
2.y在某个排列里比z靠前
3.所以x比z大
输出m个数,每个数代表第i个元素比多少元素大。
思路:
突破点就是每一行都是一个排列,我们知道一个排列中每个元素仅出现一次,那么我们按照列优先去遍历他给出的n列m行矩阵。
如果遍历到的矩阵的元素是第一次出现,那么我们要记录这个数,同时记录到目前为止有多少个“第一次出现”的数。
假如第一列有两个第一次出现的数是1和2,如果我们想说1和2比剩下的所有数(n-2个数)都大,那么我们就应该确保第一列和第二列不会再出现“第一次出现的数”。否则,如果第二列出现了新的“第一次出现”的数3,那么势必代表在这个3出现的哪一行里1和2中有一个跑到了第二列的3的后面,那么就可以论证3大于1也大于2。所以3就也比剩下的所有数都大。然后就继续要去遍历前三列没有新增的第一次出现的数。
具体见代码
#include<bits/stdc++.h>
using namespace std;
int n,m,vis[500005],ans[500005],num;
vector<int>a[500005];
queue<int>q;
int main()
{
scanf("%d%d",&n,&m);//n列m行
for(int i=1;i<=m;i++)
{
for(int j=1,x;j<=n;j++)
{
scanf("%d",&x);
a[i].push_back(x);//第i个数列里的元素
}
}
for(int i=0,x,las=1;i<n;i++)
{
for(int j=1;j<=m;j++)
{
if(!vis[a[j][i]])
{
vis[a[j][i]]=1;
num++;
q.push(a[j][i]);
}
}
if(num==i+1)
{
while(!q.empty())
{
x=q.front();
q.pop();
ans[x]=n-las;
}
las=num+1;
}
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}
区域赛银牌级别题目:B C I待补