从小到大,升序排列
lower_bound(s,t,num):s-->t-1,查找到第一个大于或等于num的数的地址,不存在返回t
upper_bound(s,t,num):s-->t-1,查找到第一个大于num的数的地址,不存在返回t
从大到小,降序排列
lower_bound(s,t,num):s-->t-1,查找到第一个小于或等于num的数的地址,不存在返回t
upper_bound(s,t,num):s-->t-1,查找到第一个小于num的数的地址,不存在返回t
http://poj.org/problem?id=3061
求出总和不小于S的连续子序列的长度
普通方法
时间复杂度O(nlgn)
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
int n,S;
int A[100010];
int sum[100010];///所有元素都大于0,所以是升序排列
void solve()
{
for(int i=0; i<n; i++)
{
sum[i+1]=sum[i]+A[i];///sum[i]代表前i项的和
}
if(sum[n]<S)
{
printf("0\n");
return;
}
int res=n;
for(int s=0; sum[s]+S<=sum[n]; s++)//复杂度n,方法查找所有的(s,n]区间,直到区间和小于S
{
int t=lower_bound(sum+s,sum+n,sum[s]+S)-sum;//lgn
res=min(res,t-s);
}
printf("%d\n",res);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(sum,0,sizeof(sum));
scanf("%d%d",&n,&S);
for(int i=0; i<n; i++)
scanf("%d",&A[i]);
solve();
}
return 0;
}
尺取法
更高效: 复杂度O(n)
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
int n,S;
int A[100010];
void Scaling()
{
int res=n+1;
int s=0,t=0,sum=0;
while(true)
{
while(t<n&&sum<S)//找到区间[s,t)大于或等于S
sum+=A[t++];
if(sum<S)break;//直到最后[s,n)的和小于S结束循环
res=min(res,t-s);
sum-=A[s++];//不断移动s,当区间[s,t)的和小于S时再往后移动t;
}
if(res>n)res=0;
printf("%d\n",res);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&S);
for(int i=0; i<n; i++)
scanf("%d",&A[i]);
Scaling();
}
return 0;
}
http://poj.org/problem?id=3320
阅读其中连续的一些页把所有的知识点全都覆盖到
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
#include<set>
#include<map>
using namespace std;
int n;
int a[1000010];
map<int,int>Count;
void Scaling()
{
set<int>all;
for(int i=0; i<n; i++)
{
all.insert(a[i]);
}
int nn=all.size();//返回set容器中元素的个数(重复的元素不计算)
int res=n;
int s=0,t=0,num=0;
while(true)
{
while(t<n&&num<nn)
{
if(Count[a[t++]]++==0)//先执行if(Count[a[t]]==0),然后再执行Count[a[t]]++,最后执行t++
{
num++;
}
}
if(num<nn)
break;
res=min(res,t-s);
if(--Count[a[s++]]==0)//先执行--Count[a[t]],然后再执行if(Count[a[t]]==0),最后执行s++
{
num--;
}
}
printf("%d\n",res);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
Count[a[i]]=0;
}
Scaling();
return 0;
}
反转
http://poj.org/problem?id=3276
N头牛,F面向前方,B面向后方,有一个机器刚开始就必须设定一个数值K,每次操作可以使K头连续的牛转向
为了能让牛都面向前方,求最少的操作数M和对应最小的K;
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
int n;
int dir[5010];//牛的方向:F:0,B:1;
int f[5010];//区间[i,i+k-1]是否翻转
//固定k,求最小操作数
//无解返回-1
int calc(int k)
{
memset(f,0,sizeof(f));
int res=0;
int sum=0;//记录i前面k-1个数的操作和
for(int i=0;i+k<=n;i++)
{
//计算区间[i,i-k+1];
if((dir[i]+sum)%2!=0)
{
res++;
f[i]=1;
}
sum+=f[i];//记录i前面k-1个数的操作和
if(i-k+1>=0)//当记录f的值大于k个就要减去多余的
sum-=f[i-k+1];
}
for(int i=n-k+1;i<n;i++)//最后k-1数不能进行翻转操作,,
{
if((dir[i]+sum)%2!=0)
return -1;
if(i-k+1>=0)
sum-=f[i-k+1];
}
return res;
}
void solve()
{
int K=1,M=n;
for(int k=1;k<=n;k++)//遍历1-n;
{
int m=calc(k);
if(m>=0&&M>m)
{
M=m;
K=k;
}
}
printf("%d %d\n",K,M);
}
int main()
{
scanf("%d",&n);
char a[5];
for(int i=0;i<n;i++)
{
scanf("%s",a);
dir[i]=(a[0]=='B');
}
solve();
return 0;
}
http://poj.org/problem?id=3279
有一个M*N的格子有正反两面一面是白色0,一面是黑色1,牛每次翻转一个格子,这个格子相邻的上下左右的格子都会翻转,要求牛将M*N个格子都翻成白色,求出用最小步数完成时每个格子的翻转的次数。最小步数有多个解时,输出字典序最小的一组,不存在的话输出IMPOSSIBLE;
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
//邻接格子的坐标
const int dx[5]={-1,0,0,0,1};
const int dy[5]={0,-1,0,1,0};
//输入
int N,M;
int tile[20][20];
int opt[20][20];//保存最优解
int flip[20][20];//记录是否翻转
//查询(x,y)的颜色
int get(int x,int y)
{
int c=tile[x][y];
for(int i=0;i<5;i++)
{
int x2=x+dx[i],y2=y+dy[i];
if(0<=x2&&x2<M&&0<=y2&&y2<N)
c+=flip[x2][y2];
}
return c%2;
}
//求出第一行确定情况下的最小操作次数
//不存在返回-1
int calc()
{
for(int i=1;i<M;i++)
{
for(int j=0;j<N;j++)
{
if(get(i-1,j)!=0)//如果(i-1,j)是黑色的话,这个地方必须翻转
flip[i][j]=1;
}
}
//判断最后一行是否全白
for(int j=0;j<N;j++)
{
if(get(M-1,j)!=0)
return -1;
}
//统计
int res=0;
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
res+=flip[i][j];
return res;
}
void solve()
{
int res=-1;
//按照字典序遍历第一行所有的可能性---0,001,01,11,字典序---
for(int i=0;i<1<<N;i++)
{
memset(flip,0,sizeof(flip));
for(int j=0;j<N;j++)
flip[0][N-j-1]=i>>j&1;
int num=calc();
if(num>=0&&(res<0||res>num))
{
res=num;
memcpy(opt,flip,sizeof(flip));//储存最优解,将flip中的元素拷贝到opt中
}
}
if(res<0)//无解
printf("IMPOSSIBLE\n");
else
{
for(int i=0;i<M;i++)
{
for(int j=0;j<N;j++)
{
printf("%d%c",opt[i][j],j+1==N?'\n':' ');
}
}
}
}
int main()
{
scanf("%d%d",&M,&N);
for(int i=0;i<M;i++)
{
for(int j=0;j<N;j++)
{
scanf("%d",&tile[i][j]);
}
}
solve();
return 0;
}
弹性碰撞
http://poj.org/problem?id=3684
用N个半径为Rcm的球做实验在H米高的位置放了一个圆筒,实验开始最下面的球开始掉落,此后每一秒又有一个球掉落(不计空气阻力,所有的碰撞都是弹性碰撞),求实验开始的T秒中每个球底端的高度,(g==10.0m/s^2)
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
const double g=10.0;
int N,H,R,T;
double y[110];//球的最终位置
double calc(int T)
{
if(T<0)return H;
double t=sqrt(2*H/g);
int k=(int)T/t;
if(k%2==0)//k为偶数
{
double d=T-k*t;
return H-g*d*d/2.0;
}
else//k为奇数
{
double d=k*t+t-T;
return H-g*d*d/2.0;
}
}
void solve()
{
for(int i=0;i<N;i++)
{
y[i]=calc(T-i);
}
sort(y,y+N);//假设球可以从球中间穿过去,彼此间相互不影响,求出y值,进行排序--最下面的球肯定最低(因为球是穿不过去的)---
for(int i=0;i<N;i++)
{
printf("%.2lf%c",y[i]+2*R*i/100.0,i+1==N?'\n':' ');//第i个球的高度再加上2*R*i
}
}
int main()
{
int C;
scanf("%d",&C);
while(C--)
{
scanf("%d%d%d%d",&N,&H,&R,&T);
solve();
}
return 0;
}
折半枚举
给定各有n个整数的四个序列,A,B,C,D.要从每个序列中个取出一个数,使四个数的和为0.求出这样的组合个数.当一个数列中有多个相同的数字时,把它们作为不同的数字来看(1<=n<4000,|数字的值|<=2^28)
//如果暴力,时间为O(n^4)
//c+d=-a-b,将C,D中取数字的n^2中方法枚举出来,将这些和排好序,再用二分搜索,时间复杂度O(nlgn)
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
const int MAX_N=4010;
int n;
int A[MAX_N],B[MAX_N],C[MAX_N],D[MAX_N];
int CD[MAX_N*MAX_N];
void solve()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
CD[i*n+j]=C[i]+D[j];
}
}
sort(CD,CD+n*n);
long long res=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
int cd=-(A[i]+B[j]);
//取出C和D中和为cd的部分
res+=upper_bound(CD,CD+n*n,cd)-lower_bound(CD,CD+n*n,cd);
}
}
printf("%lld\n",res);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d%d%d%d",&A[i],&B[i],&C[i],&D[i]);
}
solve();
return 0;
}
超大背包问题(折半枚举)
有重量和价值分别为wi,vi的n个物品.从这些物品中挑选总重量不超过W的物品,求所有挑选方案中价值总和的最大值(1<=n<=40,1<=wi,vi<=10^15,1<=W<=10^15)
//因为价值和重量都很大,相比之下n比较小,但挑选物品的方法有2^n种不能直接枚举,但是把它拆成两半后再枚举的话,每部分只有20个是可行的
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
#include<string>
#include<vector>
#include<utility>
typedef long long ll;
using namespace std;
const int MAX_N=50;
const ll INF=1e18;
//输入
int n;
ll w[MAX_N],v[MAX_N];
ll W;
pair <ll,ll> ps[1<<MAX_N/2];//重量,价值;pair内设的是什么变量,里面就要写什么变量否则就会报错
void solve()
{
//枚举前半部分
int n2=n/2;
for(int i=0;i<1<<n2;i++)
{
ll sw=0,sv=0;
for(int j=0;j<n2;j++)
{
if(i>>j&1)
{
sw+=w[j];
sv+=v[j];
}
}
ps[i]=make_pair(sw,sv);
}
//去除多余元素,使重量和价值都从小到大排列
sort(ps,ps+(1<<n2));//w重量按从小到大排列,若重量相等则v价值小的放前
int m=1;
for(int i=1;i<1<<n2;i++)
{
if(ps[m-1].second<ps[i].second)
ps[m++]=ps[i];
}
//枚举后半部分并求解
ll res=0;
for(int i=0;i<1<<(n-n2);i++)
{
ll sw=0,sv=0;
for(int j=0;j<n-n2;j++)
{
if(i>>j&1)
{
sw+=w[n2+j];
sv+=v[n2+j];
}
}
if(sw<=W)
{
ll tv=(lower_bound(ps,ps+m,make_pair(W-sw,INF))-1)->second;//找到重量相等,价值相等的话就会返回这个地址,否则就会返回第一个大于重量的地址
res=max(res,sv+tv);
}
}
printf("%lld\n",res);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%lld%lld",&w[i],&v[i]);
}
scanf("%lld",&W);
solve();
}
坐标离散化
w*h的格子上画了n条垂直或水平的宽度为1的直线,求出这些线将格子划分成了多少个区域(x是列,y是行)
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
#include<string>
#include<vector>
#include<utility>
using namespace std;
const int maxn=510;
const int dx[4]={0,0,1,-1};
const int dy[4]={-1,1,0,0};
//输入
int W,H,N;
int X1[maxn],X2[maxn],Y1[maxn],Y2[maxn];
//标记
bool fld[maxn*6][maxn*6];
//对x1,x2进行坐标离散化,并返回离散化之后的宽度
int compress(int *X1,int *X2,int w)
{
vector<int> xs;
for(int i=0; i<N; i++)
{
for(int d=-1; d<=1; d++)//保留x1,x2和他们两侧的坐标
{
int tx1=X1[i]+d,tx2=X2[i]+d;
if(1<=tx1&&tx1<=W)
xs.push_back(tx1);
if(1<=tx2&&tx2<=W)
xs.push_back(tx2);
}
}
sort(xs.begin(),xs.end());
//去重
xs.erase(unique(xs.begin(),xs.end()),xs.end());/*unique去除相邻的重复元素,只保
留一个但并不是将重复原素删除,而是将它们移到了后面,返回的迭代器指向超出无重复元素范围末端的下一个位置*/
//将x1,x2化为离散化后的坐标
for(int i=0; i<N; i++)
{
X1[i]=find(xs.begin(),xs.end(),X1[i])-xs.begin();
X2[i]=find(xs.begin(),xs.end(),X2[i])-xs.begin();
}
return xs.size();//离散化后的宽度
}
void solve()
{
//坐标离散化
W=compress(X1,X2,W);
H=compress(Y1,Y2,H);
//标记有直线的部分
memset(fld,0,sizeof(fld));
for(int i=0;i<N;i++)
{
for(int y=Y1[i];y<=Y2[i];y++)
{
for(int x=X1[i];x<=X2[i];x++)
{
fld[y][x]=true;
}
}
}
//求区间个数
int ans=0;
for(int y=0;y<H;y++)
{
for(int x=0;x<W;x++)
{
if(fld[y][x])continue;
ans++;
//宽度优先搜索
queue<pair<int,int> >que;
que.push(make_pair(x,y));
while(!que.empty())
{
int sx=que.front().first,sy=que.front().second;
que.pop();
for(int i=0;i<4;i++)
{
int tx=sx+dx[i],ty=sy+dy[i];
if(tx<0||tx>=W||ty<0||ty>=H)
continue;
if(fld[ty][tx])continue;
que.push(make_pair(tx,ty));
fld[ty][tx]=true;
}
}
}
}
printf("%d\n",ans);
}
int main()
{
scanf("%d%d%d",&W,&H,&N);
for(int i=0;i<N;i++)
{
scanf("%d%d%d%d",&X1[i],&X2[i],&Y1[i],&Y2[i]);
}
solve();
}
//10 10 5
//1 6 4 4
//1 10 8 8
//4 4 1 10
//9 9 1 5
//10 10 6 10