目录
1,氧气少年的lcm
2.数组段数
3.氧气少年的水滴
4.数组操作
5.天平
6.树形dp
氧气少年的lcm
这道题,牛客难度为3,主要考察二进制分解和gcd,lcm的求法
这道题有两种操作
1.任选集合中两数,向集合插入两者的gcd(两数可以相等但不能相同)
2.任选集合中两数,向集合插入两者的和(两数可以相等,但不能相同)
思路:
因为要获得lcm,而lcm是gcd的整数倍,所以令t=lcm(x,y)/gcd(x,y).题目转化为获得t*gcd(x,y)
步骤:
1,先用两次操作1,插入两个gcd(x,y).
2.再持续连用两次操作2,加入两个2*gcd(x,y),两个4*gcd(x,y).......直至获得小于t的最大二进制数
3.由于我们获得了小于t的最大的二进制数,也就是t的最高位1代表的数,后面还有数,故我们对后面的数进行二进制分解.例如3=1+2.13=1+2+4+8.由此获得我们想要的t后面的尾巴.举个例子t=30=16+14.16是我们之前求得的小于t的最大二进制数,而14是我们要求的尾巴,14=2+4+8,而2,4,8我们之前就加入了我们可依次获得6,14.
#include//求得t
using namespace std;
#define int long long
struct node
{
int op;
int a,b;
};
int gcd(int x,int y)
{
if(y==0)return x;
return gcd(y,x%y);
}
void solve()
{
int x,y;
cin>>x>>y;
int g=gcd(x,y);
int lcm=x*y/g;
if(x==lcm||y==lcm)
{
cout<<0<<"\n";
return;
}
vectorv;
int t=lcm/g;
v.push_back({1,x,y});
v.push_back({1,x,y});
int mx=1;
while(mx*2>T;
while (T--)
{
solve();
}
return 0;
}
数组段数
本题定义了一个东西,叫数组段数(链接:
最小的 kkk 使得将数组划分成连续的 kkk 段后,每个元素都属于某一段,且每段数组中的元素种类应该全部相同)
思路就是,从左端点开始记录每个点的段数,举个例子
元素 :2 2 3 1 3 1
段数 1 1 2 3 4 5
容易发现,当前元素可以先继承前面元素的段数,如果与前面元素不同,就递增1,因此,数组段数是相对位置而言的,所以用他们的差值求数组段数毫无问题,如区间[2,4]=3-1+1=3.注意不要每次查询都重新计算数组段数,会超时
#include
using namespace std;
#define int long long
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,m;
cin>>n>>m;
vectorv(n+5);
vectorans(n+5);
ans[1]=1;
for(int i=1;i<=n;i++)
{
cin>>v[i];
}
for(int i=2;i<=n;i++)
{
ans[i]=ans[i-1];
if(v[i]!=v[i-1])
ans[i]++;
}
while(m--)
{
int l,r;
cin>>l>>r;
cout<
氧气少年的水滴
这个题目有意思,考察了双指针的应用
用l记录左边接收水滴的点,用r记录右边接收水滴的点,直至l<1剩下往左飞的水滴会飞向无穷,r>size,剩下往右飞的水滴会飞向无穷用lnum记录往左飞的水滴数,用rnum记录往右的水滴数,还有这题适合用do while循环,因为引爆第一个水滴后,总要执行一次操作
#include
using namespace std;
#define int long long
const int maxn=2e5+5;
int a[maxn];
signed main()
{
int t;
cin>>t;
while(t--)
{
int n,p;
cin>>n>>p;
for(int i=1;i<=n;i++)cin>>a[i];
if(a[p]!=9)
{
cout<<0<<" "<<0<<"\n";
continue;
}
int l=p-1,r=p+1,lnum=1,rnum=1;
do{
while(a[l]+lnum>=10&&l>=1)
{
lnum-=10-a[l];
lnum++;rnum++;
l--;
}
while(a[r]+rnum>=10&&r<=n)
{
rnum-=10-a[r];//接收水滴
rnum++;lnum++;
r++;
}
}while((l>=1&&a[l]+lnum>=10)||(r<=n&&a[r]+rnum>=10));
if(l>=1)lnum=0;
if(r<=n)rnum=0;
cout<
数组操作
小红拿到了两个正整数x,y,她可以进行以下两种操作:
1. 将两个数同时乘以aaa。
2. 若aaa既是xxx的因子,也是yyy的因子,则将两个数同时除以aaa。
小红希望最终两个数都在[l,r][l,r][l,r]区间内。她想知道最终的xxx有多少种不同的取值方案?
这到底要求最终的x的个数,先求gcd(x,y),再将x,y都除以gcd.再以此为基础算个数,
题目转化可成乘数有多少个,x的乘数区间是[l/x,r/x],y的乘数区间是[l/y,r/y].求两个区间的交集即可
注意求左区间时,l%x为真时要向上取整如x=3,l=4,l/x=1此时1显然不能让x>l,如果x=4,l=4,左区间取1刚刚好,右区间都向下取整即可,因为不能超过右区间.
#include
using namespace std;
#define int long long
int gcd(int x,int y)
{
if(y==0)return x;
return gcd(y,x%y);
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int x,y,l,r;
cin>>x>>y>>l>>r;
int divide=gcd(x,y);
x/=divide;
y/=divide;
int l1=l%x?l/x+1:l/x,l2=l%y?l/y+1:l/y;
int ll=max(l1,l2);
int rr=min(r/x,r/y);
cout<
天平
这个题的原理就是裴蜀定理ax+by=gcd(a,b)一定有解,结论可以扩展到n个整数x,y可以为负数,正负代表放在天平的不同端
所以只要判断m是否是gcd(a,b,c)的倍数即可
#include
using namespace std;
int gcd(int x,int y)
{
if(y==0)return x;
return gcd(y,x%y);
}
int main()
{
int t;
cin>>t;
while(t--)
{
int a,b,c,m;
cin>>a>>b>>c>>m;
if(m%gcd(a,gcd(b,c))==0)
cout<<"YES"<<"\n";
else
cout<<"NO"<<"\n";
}
return 0;
}
树形dp
树形dp板子(其实我不太会,但听别人说这个很重要)
//树形dp
#include
using namespace std;
#define int long long
vectorg[202020];
int mod=1e9+7;
int dp[101010][2];//dp[i][0]代表i节点不染红i号子树的方案数,dp[i][1]代表节点染红
void dfs(int x,int pr)
{
dp[x][0]=dp[x][1]=1;//初始化
for(auto i:g[x])
{
if(i==pr)continue;
dfs(i,x);
dp[x][0]=dp[x][0]*dp[i][1]%mod;//根节点不染色,孩子都要染
dp[x][1]=dp[x][1]*(dp[i][0]+dp[i][1])%mod;//根节点染色,孩子染不染均可
}
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n;
cin>>n;
for(int i=1;i>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);//默认以1为根,从1开始搜索
cout<<(dp[1][0]+dp[1][1])%mod;
return 0;
}