H题放了,另外两题有锅,没有写的必要了。
按照区域赛的标准分级一下。
这套题区分度挺好的,考察的内容偏向思维和小的算法知识点还有一些数学的内容,我单挑大概能到7-8题的样子(谁说vp银不算银(不是)),就是锅太多了,还好锅题没影响特别大。而且牛客的机器最多每秒1e7-2e7的样子,出题人的数据范围都挺大的,不注意的话容易被卡常。
题面都中文,自己看吧
签到:A B E M
A
思路:无思路,输出即可
#include
using namespace std;
signed main()
{
int n;
cin>>n;
cout<<n-1949;
return 0;
}
B
思路:分类之后暴力搜索
#include
#include
using namespace std;
char s[1002][1002];
int ans[3] = {0,0,0};
int main() {
int a, b,n,m,i,j,k;
char now_num;
cin >> n >> m;
for(i = 0;i < n;i++)
{
scanf("%s",s[i]);
}
for(i = 0;i < n;i++)
{
for(j = 0;j < m;j++)
{
if(s[i][j] == '0')
{
continue;
}
now_num = s[i][j];
if(i <= n-5)
{
int flag = 1;
for(k = 1;k < 5;k++)
{
if(s[i+k][j] != now_num)
{
flag = 0;
break;
}
}
if(flag == 1)
{
if(now_num == '1')
{
ans[1]++;
}
else
{
ans[2]++;
}
}
}
if(j <= m-5)
{
int flag = 1;
for(k = 1;k < 5;k++)
{
if(s[i][j+k] != now_num)
{
flag = 0;
break;
}
}
if(flag == 1)
{
if(now_num == '1')
{
ans[1]++;
}
else
{
ans[2]++;
}
}
}
if(i <= n-5 && j <= m-5)
{
int flag = 1;
for(k = 1;k < 5;k++)
{
if(s[i+k][j+k] != now_num)
{
flag = 0;
break;
}
}
if(flag == 1)
{
if(now_num == '1')
{
ans[1]++;
}
else
{
ans[2]++;
}
}
}
if(i <= n-5 && j >= 4)
{
int flag = 1;
for(k = 1;k < 5;k++)
{
if(s[i+k][j-k] != now_num)
{
flag = 0;
break;
}
}
if(flag == 1)
{
if(now_num == '1')
{
ans[1]++;
}
else
{
ans[2]++;
}
}
}
}
}
cout << ans[1] << " " << ans[2];
}
E
思路:map记录第一个人去过的地方,之后对于每个人暴力检测
#include
using namespace std;
#define int long long
#define endl '\n'
const int N = 1e5+100;
int a[N];
const int mod = 1e9+7;
signed main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
map<int,int>mp;
int n,m,k;
cin>>n>>m;
cin>>k;
for(int i=1;i<=k;i++)
{
int temp;
cin>>temp;
mp[temp]=1;
}
int ans=1;
for(int i=1;i<=n-1;i++)
{
cin>>k;
bool falg=false;
for(int j=1;j<=k;j++)
{
int temp;
cin>>temp;
if(mp[temp]&&!falg)
{
falg=true;
ans++;
}
}
}
cout<<ans;
return 0;
}
M
思路:稍微打表找一下规律,发现只要在对角线上做手脚就可以了,n是奇数和偶数要分别讨论一下。
#include
using namespace std;
#define endl '\n'
const int N = 1e3+100;
int a[N][N];
signed main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
int t;
for(cin>>t;t;t--)
{
int n;
cin>>n;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
a[i][j]=1;
if(n&1)
{
for(int i=1;i<=n;i+=2)
{
a[i][i]=0;
}
}
else
{
for(int i=2;i<=n;i+=2)
{
a[i][i]=0;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<a[i][j];
}
cout<<endl;
}
}
return 0;
}
铁:F G
F(数论)
首先发现了是可以取n/2的,那么对于奇数来说n/2直接就是答案。
然后对于n是偶数来说,他的n/2可能是有奇数偶数两种情况,但最终只要得到一个没有倍数关系的奇数就可以保证和n互质。所以有如下结论:
如果n/2是个偶数,那就让这个偶数-1得到一个奇数
如果n/2是个奇数,那就让这个奇数-2得到一个不满足倍数关系的奇数数
#include
using namespace std;
#define int long long
#define endl '\n'
const int N = 1e5+100;
int a[N];
const int mod = 1e9+7;
signed main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
int t;
for(cin>>t;t;t--)
{
int n;
int ans=0;
cin>>n;
if(n&1ll)
{
ans=n/2;
}
else
{
int temp=n/2;
if(temp&1)
{
ans=n/2-2;
}
else
{
ans=n/2-1;
}
}
if(ans>n/2||ans<n/4+(n%4!=0))
{
cout<<-1<<endl;
}
else
{
cout<<ans<<endl;
}
}
return 0;
}
G(并查集)
写这个被卡了,和队友口完题意自己想了一会儿,每次进行4操作的时候,我们希望能够利用之前的4操作,具体的利用方式就是取出已经被4操作的区间,然后让目前的值和这个区间里的一个值做gcd就可以。
然后看题目要求输出的每次都是最后一个数,猜一下这个代表值应该就是末尾的值,所以每一段区间被4操作之后末尾存储这段区间的gcd,并且把这个区间归并到一起方便直接跳过整个区间,自然想到了并查集。
丁真队居然10分钟速过这个题,,思维真恐怖。
#include
using namespace std;
const int N = 1e5+100;
#define endl '\n'
int dsu[N];
int a[N];
int tfind(int x)
{
if(x==dsu[x])
return x;
return dsu[x]=tfind(dsu[x]);
}
signed main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
int q;
cin>>q;
int n=0;
for(int i=1,op,x,k;i<=q;i++)
{
cin>>op;
if(op==1)
{
cin>>x;
a[++n]=x;
dsu[n]=n;
}
else if(op==2)
{
if(tfind(n)<=n-1)
a[n-1]=a[n];
n--;
}
else if(op==3)
{
cout<<a[n]<<endl;
}
else
{
cin>>k;
int g=0;
for(int i=n,j=n;i>=n-k+1;)
{
g=std::__gcd(g,a[i]);
i=tfind(i)-1;
dsu[j]=max(i,n-k+1);
j=i;
}
a[n]=g;
}
}
return 0;
}
铜:I(并查集,二分答案)
题目就是让你把一个图分成俩,问问分成俩之后切掉的所有边里最大价值最小是多少。
后面还有个废话,说是让尽可能少的切掉边(反正你不输出,鬼知道你切了几个边)
最大值最小这么明显的提示,一眼二分答案。也很自然的想到了二分最大值,那么大于这个最大值的就是保留的边,所有保留的边都可以把他们所连接的端点并在一起,最后dsu[i]==i的个数大于等于2即可。
#include
using namespace std;
const int N = 1e6+100;
struct node
{
int a,b,w;
}edge[N];
int dsu[N];
int n,m;
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;
}
bool check(int x)
{
for(int i=1;i<=n;i++)
dsu[i]=i;
for(int i=1;i<=m;i++)
{
if(edge[i].w>x)
tmerge(edge[i].a,edge[i].b);
}
int num=0;
for(int i=1;i<=n;i++)
{
if(dsu[i]==i)
num++;
}
return num>=2;
}
signed main()
{
cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>edge[i].a>>edge[i].b>>edge[i].w;
}
int l=0,r=1e9;
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid))
{
r=mid-1;
}
else
{
l=mid+1;
}
}
cout<<l;
return 0;
}
稳铜,手速银: D
D(概率DP,数学期望)
首先根据概率就是期望的倒数很容易就能算出第一个要求的答案就是求和k/i(i从1到k)
然后考虑n次抽卡下的期望数。
n次抽卡显然是伯努利事件,那么按照伯努利的两个模型去分析发现都不符合题意,考虑朴素的思路,发现第i次抽卡受到前面抽卡的影响,整个过程类似于递推,所以考虑概率DP。
设dp[i]为抽i次卡的期望种类数是多少。
设计状态转移方程。
dp[i]=(k-dp[i-1])/k+dp[i-1];
#include
using namespace std;
const int N = 1e6+100;
double dp[N];
signed main()
{
int n,m;
cin>>n>>m;
double ans1=0;
for(int i=1;i<=m;i++)
{
ans1+=(double)m/(double)i;
}
dp[1]=1.0;
for(int i=1;i<=n;i++)
{
dp[i]=1.0*(m*1.0-dp[i-1])/m+dp[i-1];
}
cout<<fixed<<setprecision(10)<<ans1<<" "<<dp[n]<<endl;
return 0;
}