2020牛客寒假算法基础训练营2
2020牛客寒假算法基础训练营2
A.做游戏(思维)
题意:两个人玩石头剪刀布,牛牛出石头a次,剪刀b次,布c次,牛可乐出石头x次,剪刀y次,布z次,问牛牛最多可以赢多少次
题解:对牛牛每次可以赢的出法取最小值即可
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
int main()
{
ll a,b,c,x,y,z;
cin>>a>>b>>c>>x>>y>>z;
ll ans = min(a,y)+min(b,z)+min(c,x);
cout<<ans<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
B.排数字(思维)
题意:给定一个由纯数字构成的字符串,你可以随机排列这些数字,问你排出的字符串最多包含多少个“616”子串,61616这样的算两个
题解:最好的情况肯定是“61616”这样排列,结果就是6和1的数量取最小值。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
int main()
{
int n,cnt6 = 0,cnt1 = 0;
string s;
cin>>n>>s;
for(int i = 0; i < n; i++){
if(s[i]=='6')cnt6++;
if(s[i]=='1')cnt1++;
}
cout<<min(cnt6-1,cnt1)<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
C.算概率(DP)
题意:一共n道题,每道题做对的概率为pi,问做对0-n道题的概率分别是多少,结果对1e9+7取模
题解:\(dp_{i,j}\)表示前i道题做对了j道,则有状态转移方程\(dp_{i,j}\) = \(dp_{i-1,j}\)x(1-\(p_i\))+\(dp_{i-1,j-1}\)x\(p_i\),注意j=0的特殊情况,时间复杂度O(\(n^2\))
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 2019;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
ll n,p[N],dp[N][N];
const ll mod = 1e9+7;
int main()
{
cin>>n;
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
cin>>p[i];
for(int i = 1; i <= n; i++){
dp[i][0] = dp[i-1][0]*(mod+1-p[i])%mod;
for(int j = 1; j <= i; j++){
dp[i][j] = (dp[i-1][j]*(mod+1-p[i])+dp[i-1][j-1]*p[i])%mod;
}
}
for(int i = 0; i <= n; i++){
cout<<dp[n][i]<<" ";
}
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
D.数三角(极角排序)
题意:给定n个不重复的点,求一共可以组成多少个钝角三角形
题解:对每个点进行极角排序后双指针找上下界统计钝角数量
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
struct node1
{
double x,y;
}a[1000];
double node[1500];
int main()
{
int n;
cin>>n;
for(int i = 1; i <= n; i++)cin>>a[i].x>>a[i].y;
ll ans = 0;
for(int i = 1; i <= n; i++){
int cnt = 0;
for(int j = 1; j <= n; j++){
if(i==j)continue;
node[++cnt] = atan2(a[i].y-a[j].y,a[i].x-a[j].x);
if(node[cnt] < 0)node[cnt]+=2*PI;
}
sort(node+1,node+1+cnt);
for(int j = 1; j <= cnt; j++) node[j+cnt] = node[j]+2*PI;
int l = 1,r = 1;
for(int j = 1; j <= cnt; j++){
while(r <= 2*cnt&&node[r] - node[j] < PI)r++;
while(l <= 2*cnt&&node[l] - node[j] <= 0.5*PI)l++;
ans+=r-l;
}
}
cout<<ans<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
E.做计数(完全平方数)
题意:给定一个n,问有多少个不同的三元组{i,j,k},满足\(\sqrt{i}+\sqrt{j} = \sqrt{k}\),i,j,k均为正整数,且i * j <= n,i,j,k有一者不同即为不同
题解:展开算式可得 i + j + 2\(\sqrt{ij}\) = k,可见i*j必须是完全平方数,枚举n以内的完全平方数,再枚举其因数即可
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
int main()
{
int ans = 0;
int n;
cin>>n;
for(int i = 1; i*i <= n; i++){
for(int j = 1; j <= i; j++){
if(i*i%j==0){
ans+=2;
}
}ans-=1;
}
cout<<ans<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
F.拿物品(贪心)
题意:对于n个物品,每个物品都有一个a值和b值,牛牛和牛可乐依次选择这n个物品,每次从剩下的里面选一个,牛牛先选,牛牛最后的权值为选择物品的a值总和,牛可乐的权值为选择的物品的b值总和,两者都希望自己的权值最大,在两者的最优选择方案下两者会如何选择
题解:贪心,两者都尽量选择a+b值大的物品,可以理解成削弱你就是增强我、
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
struct node
{
int a,b,v,id;
};
bool cmp(node a,node b)
{
return a.v>b.v;
}
int main()
{
int n;
cin>>n;
node a[N];
for(int i = 1; i <= n; i++){
cin>>a[i].a;
}
for(int i = 1; i <= n; i++){
cin>>a[i].b;
a[i].v = a[i].a+a[i].b;
a[i].id = i;
}
vector<int> v1,v2;
sort(a+1,a+n+1,cmp);
for(int i = 1; i <= n; i++){
if(i&1){
v1.push_back(a[i].id);
}else
v2.push_back(a[i].id);
}
for(int i = 0; i < v1.size(); i++){
cout<<v1[i]<<" ";
}cout<<endl;
for(int i = 0; i < v2.size(); i++){
cout<<v2[i]<<" ";
}cout<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
G.判正误(hash)
题意:给定t组a,b,c,d,e,f,g,a-g的范围在int内,判断\(a^d + b^e + c^f = g\)是否成立
题解:直接计算肯定是会TLE的,这里我选择了快速幂%1e9+7这个神奇的数字居然过了,跟出题人所描述的方法不太相同,也不太理解这道题目的意义
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
const ll mod = 1e9+7;
ll quick_mod(ll a,ll b)
{
ll ans = 1,t = a;
while(b){
if(b&1==1){
ans = (ans*t)%mod;
}
b>>=1;
t = (t*t)%mod;
}
return ans;
}
int main()
{
Test{
ll a,b,c,d,e,f,g,x,y,z,ans;
cin>>a>>b>>c>>d>>e>>f>>g;
x = quick_mod(a,d);
y = quick_mod(b,e);
z = quick_mod(c,f);
ans = x+y+z;
if(g==ans){
cout<<"Yes"<<endl;
}else
cout<<"No"<<endl;
};
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
H.施魔法(DP)
题意:一共n个元素,第i个元素的值为ai,每次从中最少选择k个元素,费用是每次选中的元素的极差,每个元素只能选择一次,求最小花费
题解:先从小到大排序,选择方案一定是连续的一系列数,并且选择次数越多花费越小,假设将这个数列切割m次,每次切割都会使最大花费减少切割位置左右两个元素的差值,比如在i位置切割,花费会减少\(a_{i+1} - a_i\),这样即可得到状态转移方程,dp[i] = \(max_{j\in[1,i-k]}\){dp[j]}+d[i],dp[i]表示在位置i处切割后最多减少的花费,d[i]表示在i位置切割减少的花费,其中最大值可以在计算中维护,时间复杂度O(n)
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
int a[N],d[N],dp[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i = 1; i <= n; i++){
cin>>a[i];
}
sort(a+1,a+1+n);
ll ans = a[n] - a[1];
int now = 0;
for(int i = 1; i <= n-1; i++){
d[i] = a[i+1] - a[i];
}
memset(dp,0,sizeof(dp));
int pre = 0;
for(int i = k; i <= n-k; i++){
pre = max(pre,dp[i-k]);
dp[i] = pre+d[i];
now = max(now, dp[i]);
}
cout<<ans-now<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
I.建通道(二进制)
题意:给定n个点,每个点都有一个权值v,两点之间建立一条路的花费为lowbit(v1\(\bigoplus\)v2),问连通所有点的最小花费
题解:将权值相同的点去重,设去重后有m个点,接着利用&和|运算统计出所有权值中二进制中既有1又有0的位数,找到最小的二进制第k位,结果就是\(2^k*(m-1)\)
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 1e6;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
int v[N];
int main()
{
int v1,v0,n;
cin>>n;
v1 = 0x7fffffff,v0 = 0;
for(int i = 1; i <= n; i++){
cin>>v[i];
v1 &= v[i];
v0 |= v[i];
}
v1 ^= v0;
sort(v+1,v+1+n);
int m = 0;
for(int i = n; i > 0; i--){
if(v[i]!=v[i+1])m++;
}
ll ans;
for(int i = 0; i <= 30; i++){
int cur = 1<<i;
if(cur & v1){
ans = 1ll*cur*(m-1);
break;
}
}
cout<<ans<<endl;
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}
J.求函数(线段树)
题意:有n个一次函数,第i个函数f(i) = \(k_i\)*x+\(b_i\),有m次操作,每次操作分两种
1 i k b 将\(f_i(x)\)修改为\(f_i(x) = k*x+b\)
2 l r 求\(f_r(f_{r-1}(...f_{l+1}(f_l(1))...))\),答案对1e9+7取模
题解:对于第二种操作所求结果可以计算为
需要注意的使当j=i+1>r时按\(\prod_{j=i+1}^rk_j\) = 1计算。
那么对于上式,我们可以采用两棵线段树分别维护加号左右的两个式子,重点就是如何合并这两个区间
对于区间[l,r],我们把它分成两部分\([l_1,r_1]\),\([l_2,r_2]\),令式子\(\prod_{i=l_1}^{r_1}k_i = n1\),\(\prod_{i=l_2}^{r_2}k_i = n2\)
\(\sum_{i=l_1}^rb_i*\prod_{j=i+1}^rk_j\) = m1,\(\sum_{i=l_2}^rb_i*\prod_{j=i+1}^rk_j\) = m2,那么合并区间时,加号左边部分的合并方法就是 \(n1*n2\),加号右侧的合并方法是\(m1*n2+m2\),注意这些式子累加符号的上限仍然是r
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<vector>
using namespace std;
using ll = long long;
const ll N = 2e5+5;
const ll mod = 1e9+7;
using pii = pair<int,int>;
const double PI = acos(-1.0);
#define Test ll tesnum;tesnum = read();while(tesnum--)
ll read();
ll st1[N<<2],st2[N<<2],k[N];
void modify(int id, int l, int r, int i, int k, int b)
{
if(l == r){
st1[id] = k;st2[id] = b;
return ;
}
int mid = (l+r)>>1;
if(i<=mid) modify(id<<1,l,mid,i,k,b);
else
modify(id<<1|1,mid+1,r,i,k,b);
st1[id] = (st1[id<<1]*1ll*st1[id<<1|1])%mod;
st2[id] = (st2[id<<1]*1ll*st1[id<<1|1]+st2[id<<1|1])%mod;
}
pii merge( pii a, pii b)
{
return {(a.first*1ll*b.first)%mod,(a.second*1ll*b.first+b.second)%mod};
}
pii query(int id,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return {st1[id],st2[id]};
int mid = (l+r)>>1;
if(qr<=mid){
return query(id<<1,l,mid,ql,qr);
}
if(ql>mid){
return query(id<<1|1,mid+1,r,ql,qr);
}
return merge(query(id<<1,l,mid,ql,qr),query(id<<1|1,mid+1,r,ql,qr));
}
int main()
{
int n,m,b;
cin>>n>>m;
for(int i = 1; i <= n; i++)cin>>k[i];
for(int i = 1; i <= n; i++){
cin>>b;
modify(1,1,n,i,k[i],b);
}
while(m--){
int f;
cin>>f;
if(f==1){
int i,k,b;
cin>>i>>k>>b;
modify(1,1,n,i,k,b);
}else{
int l,r;
cin>>l>>r;
pii ans = query(1,1,n,l,r);
cout<<(ans.first+ans.second)%mod<<endl;
}
}
return "BT7274", NULL;
}
inline ll read() {
ll hcy = 0, dia = 1;char boluo = getchar();
while (!isdigit(boluo)) {if (boluo == '-')dia = -1;boluo = getchar();}
while (isdigit(boluo)) {hcy = hcy * 10 + boluo - '0';boluo = getchar();}
return hcy * dia;
}