3.gcd 模拟
map<pair<int,int>,int>m;
void solve(){
int n;cin>>n;
forr(i,1,n){
int ux,uy,vx,vy;
cin>>ux>>uy>>vx>>vy;
int dx=vx-ux,dy=vy-uy;
if(dx!=0&&dy!=0){
int g=abs(__gcd(dx,dy));
dx/=g,dy/=g;//dxdy中除去公共部分(gcd) 就是相邻整数点的横纵距离
int nx=ux,ny=uy;
while (nx!=vx&&ny!=vy){
m[{nx,ny}]++;
nx+=dx,ny+=dy;
}
m[{vx,vy}]++;
}else if(dx==0){//处理g=0的情况
forr(i,min(uy,vy),max(uy,vy)){
m[{ux,i}]++;
}
}else if(dy==0){
forr(i,min(ux,vx),max(ux,vx)){
m[{i,uy}]++;
}
}
}
int ans=0;
for(auto i:m){
if(i.second>1){
ans++;
}
}
cout<<ans<<endl;
}
4. 二分查找
简单的二分查找板子,但是需要注意check函数的细节
void solve(){
int n,m;cin>>n>>m;
vector<int>a(n+1);
int mnd=1e8+10;
forr(i,1,n){
cin>>a[i];
mnd=min(a[i],mnd);
}
auto check=[&](int x)->bool{
int pre=0,cnt=0,fg=0;
forr(i,1,n){
int dis=a[i]-pre;
if(dis>x){
//注意中间添加检查点的个数
if(dis%x==0)cnt+=dis/x-1;
else cnt+=dis/x;
}
pre=a[i];
}
return cnt<=m+1;//爆发技能就相当于多加一个检查点
};
int l=1,r=1e8+10;
while (l<r)
{
int mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<r<<endl;
}
5.贪心
贪心,每次往前放最小的 新添加的要放在原字符串中更大的字母的前面
void solve(){
int n,m;cin>>n>>m;
string s,c;cin>>s>>c;
sort(c.begin(),c.end());
int i,j;
string ans;
for(i=0,j=0;i<n&&j<m;)
{
if(s[i]<=c[j])ans+=s[i++];
else ans+=c[j++];
}
while (i<n)ans+=s[i++];
while (j<m)ans+=c[j++];
cout<<ans<<endl;
}
6. dp
数据量不大 1e6 三维dp [数组元素][已经用了多少反转区间][本元素是否反转]
逆转贡献时间复杂度不大 1 e 9 ≈ 2 30 1e9≈2^{30} 1e9≈230
const int N=1e3+10;
int twos[35];
int rev(int x){
int ans=0,pre=x;
stack<bool>b;
while (x)
{
b.push(x&1);
x>>=1;
}
int i=0;
while (!b.empty())
{
ans+=b.top()*twos[i++];
b.pop();
}
// cout<<ans<<' ';
// cout<<ans-pre<<endl;
return ans-pre;
}
int dp[N][N][2];
void solve(){
twos[0]=1;
forr(i,1,31)twos[i]=twos[i-1]*2;
int n,m;
cin>>n>>m;
vector<int>a(n+1),b(n+1);
int sum=0;
forr(i,1,n){
cin>>a[i];
sum+=a[i];
b[i]=rev(a[i]);//逆转后的贡献
}
forr(i,1,n){
forr(j,1,m){
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);//不用反转
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0])+b[i];//反转
//如果上一个没转,本次新开反转区间
//如果上一个反转了,延续上个区间
}
}
// cout<<sum<<endl;
cout<<max(dp[n][m][0],dp[n][m][1])+sum<<endl;
}
7.贪心 滑动窗口
注意平面是第一象限[0,1e8]的正方形
贪心(纵):最优情况是以每个圆的下边界为底往上+h
滑动窗口(横):排序 滑动窗口找长度为w的区间 能包含的圆最多
struct cir{
int x,y,r;
};
//贪心思想(纵)+滑动窗口(横)
void solve(){
int n,w,h;cin>>n>>w>>h;
vector<cir>a(n+1);
vector<int>yl;
forr(i,1,n){
cin>>a[i].x>>a[i].y>>a[i].r;
yl.push_back(a[i].y-a[i].r);//贪心 最优情况是以每个圆的下边界为底
}
sort(a.begin()+1,a.end(),[](cir cx,cir cy){
return cx.x+cx.r<cy.x+cy.r;
});//从小到大 右端排序
int ans=0;
//在纵向区间内,枚举横边,计数
auto cnt=[&](int w,int h,int cury)->void{
priority_queue<int,vector<int>,greater<int>>q;//小根堆 顶上是最左边的边
forr(i,1,n){
if(a[i].y-a[i].r>=cury&&a[i].y+a[i].r<=cury+h){//如果在纵坐标h的区间内
q.push(a[i].x-a[i].r);
while (!q.empty()&&a[i].x+a[i].r-q.top()>w)q.pop();//滑动窗口 优先队列维护横坐标
ans=max(ans,(int)q.size());
}
}
};
forr(i,1,n){
cnt(w,h,yl[i]);
cnt(h,w,yl[i]);
}
cout<<ans<<endl;
}
8.递推
借鉴(抄袭)了dalao的思路
- 每个位置都有两种跳法 2 i 2i 2i和 i + c i i+c_i i+ci
- 就像二叉树的结构,从叶子往根递推,记录哪个位置当根set.size()最大
- 问的是“可能经过的”,所以 2 i 2i 2i和 i + c i i+c_i i+ci能跳则权值放入set
- 数据很水,用set也能过,感觉是 n 2 n^2 n2的复杂度;dalao的题解中用bitset取或运算代替set.insert记录权值,很快
const int N=4e4+5,M=130;
set<int>s[N];
void solve(){
int n;cin>>n;
vector<int>a(n+1);
forr(i,1,n)cin>>a[i];
int ans=0;
reforr(i,1,n){
s[i].insert(a[i]);
if(a[i]+i<=n){
for(auto j:s[i+a[i]])s[i].insert(j);
}
if(2*i<=n){
for(auto j:s[i*2])s[i].insert(j);
}
ans=max(ans,(int)s[i].size());
}
cout<<ans<<endl;
}