分治法求最大连续和
注意范围是[x,y)
#include<bits/stdc++.h>
using namespace std;
int maxsum(int *A,int x,int y){
if(y-x==1) return A[x];
int m=x+(y-x)/2;
int maxs = max(maxsum(A,x,m),maxsum(A,m,y));
int v,L,R;
v=0; L=A[m-1];
for(int i=m-1;i>=x;i--) L=max(L,v+=A[i]);
v=0; R=A[m];
for(int i=m;i<y;i++) R=max(R,v+=A[i]);
return max(maxs, L+R);
}
int main()
{
int A[]={1,-1,2,3,-2};
printf("%d\n",maxsum(A,0,4));
return 0;
}
归并排序,注意范围还是[x,y)
归并就是先分治排序最小范围的序列
然后归并,归并紫书上有个图,思路很清晰
#include<bits/stdc++.h>
using namespace std;
void merge_sort(int *A,int x,int y,int *T){
if(y-x>1){
int m=x+(y-x)/2;
int p=x,q=m,i=x;
merge_sort(A,x,m,T);
merge_sort(A,m,y,T);
while(p<m||q<y){
if(q>=y||(p<m&&A[p]<=A[q])) T[i++]=A[p++];
else T[i++]=A[q++];
}
for(i=x;i<y;i++) A[i]=T[i];
}
}
int main()
{
int A[]={0,2,1};
int T[100];
merge_sort(A,0,3,T);
for(int i=0;i<3;i++) printf("%d ",A[i]);
return 0;
}
逆序对问题
同样的分治归并法,利用归并排序从m往前扫的特点,去数逆序对数
因为序列会排上序,所以直接加(m-p)即可
#include<bits/stdc++.h>
using namespace std;
int cnt=0;
void merge_sort(int *A,int x,int y,int *T){
if(y-x>1){
int m=x+(y-x)/2;
int p=x,q=m,i=x;
merge_sort(A,x,m,T);
merge_sort(A,m,y,T);
while(p<m||q<y){
if(q>=y||(p<m&&A[p]<=A[q])) T[i++]=A[p++];
else { T[i++]=A[q++]; cnt+=m-p; }
}
for(int i=x;i<y;i++) A[i]=T[i];
}
}
int main()
{
int A[]={0,2,1,-1,-3};
int T[100];
merge_sort(A,0,5,T);
printf("%d\n",cnt);
return 0;
}
快速排序:
从起始点选定一个点,从后往前扫,再从前往后扫
再选第二个点,知道选到最后
#include<bits/stdc++.h>
using namespace std;
void quickSort(int a[],int left,int right)
{
int i=left;
int j=right;
int temp=a[left];
if(left >= right) return ;
while(i!=j){
while(i<j&&a[j]>=temp) j--;
if(j>i) a[i]=a[j];
while(i<j&&a[i]<temp) i++;
if(i<j) a[j]=a[i];
}
a[i]=temp;
quickSort(a,left,i-1);
quickSort(a,i+1,right);
}
int main()
{
int A[]={0,2,1,-1,-3};
int T[100];
quickSort(A,0,4);
for(int i=0;i<5;i++)
printf("%d ",A[i]);
return 0;
}
快速选择问题
根据快速排序划分后的标记点,判断序列要找的k是否大于或者小于它,然后进行方向性的枚举
#include<bits/stdc++.h>
using namespace std;
int k=2;
int quickSort(int a[],int left,int right)
{
int i=left;
int j=right;
int temp=a[left];
printf("temp:%d ** \n",a[left]);
if(left >= right) return 0;
while(i!=j){
while(i<j&&a[j]>=temp) j--;
if(j>i) a[i]=a[j];
while(i<j&&a[i]<temp) i++;
if(i<j) a[j]=a[i];
}
for(int i=0;i<5;i++)
printf("%d ",a[i]); printf("\n");
a[i]=temp; printf("i = %d\n",i);
if(k<i)
quickSort(a,left,i-1);
else if(k>i)
quickSort(a,i+1,right);
else return a[k];
}
int main()
{
int A[]={0,2,1,-1,-3}; // -3 -1 0 1 2
int T[100];
k=0;
printf("%d\n",quickSort(A,0,4));
return 0;
}
二分查找折半查找
迭代实现
#include<bits/stdc++.h>
using namespace std;
int bsearch(int *A,int x,int y,int v){
int m;
while(x<y){
m=x+(y-x)/2;
if(A[m]==v) return m;
else if(A[m]>v) y=m;
else x=m+1;
}
return -1;
}
int main()
{
int A[]={0,2,1,-1,-3}; // -3 -1 0 1 2
int T[100];
sort(A,A+5);
printf("%d\n",bsearch(A,0,5,-3));
return 0;
}
二分查找求上下界
STL中也有这两个函数可以直接用
#include<bits/stdc++.h>
using namespace std;
int lower_bound(int *A,int x,int y,int v){
int m;
while(x<y){
m=x+(y-x)/2;
if(A[m]>=v) y=m;
else x=m+1;
}
return x;
}
int upper_bound(int *A,int x,int y,int v){
int m;
while(x<y){
m=x+(y-x)/2;
if(A[m]<=v) x=m+1;
else y=m;
}
return y;
}
int main()
{
int A[]={0,2,1,-1,-3,2}; // -3 -1 0 1 2
int T[100];
sort(A,A+5);
printf("%d\n",lower_bound(A,0,5,2));
printf("%d\n",upper_bound(A,0,5,2));
return 0;
}
棋牌覆盖问题
递归分治解决,边界为k==1
#include<bits/stdc++.h>
using namespace std;
int cnt=0;
int merge_qipan(int k)
{
if(k==1) return ++cnt;
merge_qipan(k-1);
merge_qipan(k-1);
merge_qipan(k-1);
merge_qipan(k-1);
return ++cnt;
}
int main()
{
int k;
scanf("%d",&k);
printf("%d\n",merge_qipan(k));
return 0;
}
循环日程表问题:出不来
巨人与鬼:暂时忽略
贪心法:
最优装载问题:纯贪心
部分背包问题:按比值贪心
区间相关问题:选择不相交区间,按b排序
第一种:a1>a2 选择a1 然后:a1<a2<a3从前面不相交的往后选
区间选点问题:按b排序,第一个区间取最后一个点
区间覆盖问题:
预处理:
先切掉[s,t]外的部分
按a排序,起点非s,无解
否则选择起点为s的最长区间
然后新的起点为上一个区间的终点
Huffman编码:
先排序把字符排成表p,创建新节点队列,每次两两合并
构造法:
例题8-1:UVA120煎饼
书上给的分析很明白,其实就是让去找一个可行解,但不知道是不是最优
这种就叫构造算法吧,每次拍一次最大的,最大的排号就不用管了
问题是用数组实现起来真的很麻烦,看到一个666的题解,双端队列,还有reverse翻转,注意翻转是有顺序的。
#include<bits/stdc++.h>
using namespace std;
int main()
{
for(string strLine;getline(cin,strLine);cout<<'0'<<endl){
cout<<strLine<<endl;
istringstream iss(strLine);
deque<int> Stack;
for(int nDiam;iss>>nDiam;Stack.push_front(nDiam)) ;
for(deque<int>::iterator i=Stack.begin();i!=Stack.end();i++){
deque<int>::iterator iMax = max_element(i,Stack.end());
if(iMax != i) {
if(iMax != Stack.end()-1){
reverse(iMax,Stack.end());
cout<<distance(Stack.begin(),iMax) + 1<<' ';
}
reverse(i,Stack.end());
cout<<distance(Stack.begin(),i)+1<<' ';
}
}
}
return 0;
}
例题8-2联合国大厦UVa1605
也是按紫书给的思路,共两层,第一层跟i一样放国家,下一层按列,这样每个第一层的国家会和第二层而且是每一个国家都挨着
注意国家只能用大小写字母
#include<bits/stdc++.h>
using namespace std;
int a[60][60];
int b[60][60];
int main()
{
int n;
while(~scanf("%d",&n)){
printf("%d %d %d\n",2,n,n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
a[i][j]=i;
b[i][j]=j;
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(a[i][j]>25) printf("%c",a[i][j]-26+'a');
else
printf("%c",a[i][j]+'A');
}
printf("\n");
}
printf("\n");
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(b[i][j]>25) printf("%c",b[i][j]-26+'a');
else
printf("%c",b[i][j]+'A');
}
printf("\n");
}
printf("\n");
}
return 0;
}
中途相遇法:从两个方向解决问题,最后汇总
8-3 UVa1152 和为0的四个值
这个题的Hash写的很精妙,好好体会
#include<bits/stdc++.h>
using namespace std;
struct HashMAP{
static const int mask = 0x7fffff;
int p[8388608],q[8388608];
void Clear()
{
for(int i=0;i<=mask; ++ i) q[i]=0;
}
int & operator [] (int k){
int i;
for(i=k&mask; q[i]&&p[i]!=k;i=(i+1)&mask) ;
p[i]=k;
return q[i];
}
}Hash;
int main()
{
int t;
scanf("%d",&t);
while(t--){
Hash.Clear();
int n,sum=0,a[4100],b[4100],c[4100],d[4100];
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
Hash[a[i]+b[j]]++;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
sum+=Hash[-(c[i]+d[j])];
printf("%d\n",sum);
if(t) printf("\n");
}
return 0;
}
问题分解:
例题8-4 传说中的车 UVa11134
和前面讲的区间相关问题类似,首先把图分为两个一维轴,然后根据给定的区间,按b排序,从前面找i的位置,找到便正确
#include<bits/stdc++.h>
using namespace std;
struct pp {
int x1,x2,y1,y2,x,y,num;
}B[5050];
bool cmp1(pp a,pp b) { if(a.x2==b.x2) return a.x1<b.x1; else return a.x2<b.x2; }
bool cmp2(pp a,pp b) { if(a.y2==b.y2) return a.y1<b.y1; else return a.y2<b.y2; }
bool cmp3(pp a,pp b) { return a.num<b.num; }
int main()
{
int n;
while(scanf("%d",&n)&&n){
for(int i=0;i<n;i++) { scanf("%d%d%d%d",&B[i].x1,&B[i].y1,&B[i].x2,&B[i].y2); B[i].x=0; B[i].y=0; B[i].num=i+1; }
sort(B,B+n,cmp1);
// for(int i=0;i<n;i++) printf("%d %d %d %d\n",B[i].x1,B[i].y1,B[i].x2,B[i].y2);
int AA=0,BB=0;
for(int i=1;i<=n;i++)
for(int j=0;j<n;j++) {
if(B[j].x1<=i &&B[j].x2>=i &&!B[j].x)
{
B[j].x=i; AA++; break;
}
}
sort(B,B+n,cmp2);
// for(int i=0;i<n;i++) printf("%d %d %d %d\n",B[i].x1,B[i].y1,B[i].x2,B[i].y2);
for(int i=1;i<=n;i++)
for(int j=0;j<n;j++) {
if(B[j].y1<=i &&B[j].y2>=i &&!B[j].y)
{
B[j].y=i; BB++; break;
}
}
if(AA==n&&BB==n){
sort(B,B+n,cmp3); for(int i=0;i<n;i++) printf("%d %d\n",B[i].x,B[i].y);
} else printf("IMPOSSIBLE\n");
}
return 0;
}
等价转换
例题8-5 UVa11054酒的交易
等价转换就是每一次都只考虑a2家酒庄需要多少劳力给a1家酒庄送酒,结果便是最优
#include<bits/stdc++.h>
using namespace std;int main(){
int n;while(cin>>n&&n){
int ans=0,a,last=0;
for(int i=0;i<n;i++)
{cin>>a;ans+=abs(last);last+=a; }
cout<<ans<<endl;}
return 0;}
扫描法:扫面法再枚举时维护一些重要的量,简化计算
例题8-6 两性亲子 UVa1606
极角排序的这个题并不会
#include<bits/stdc++.h>
using namespace std;
struct Node{
public :
int x,y,z;
double rad;
bool operator < (const Node& rhs) const {
return rad < rhs.rad;
}
}c[1000],d[1000];
bool Turn(Node&a,Node&b)
{
return a.x*b.y>=a.y*b.x;
}
int main()
{
int N;
while(~scanf("%d",&N)&&N){
for(int i=0;i<N;i++) scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].z);
if(N<=2) { printf("%d\n",N); continue; }
int maxn=0;
for(int i=0;i<N;i++){
int k=0;
for(int j=0;j<N;j++){
if(i!=j){
d[k].x = c[j].x-c[i].x;
d[k].y = c[j].y-c[i].y;
if(c[j].z){
d[k].x = -d[k].x;
d[k].y = -d[k].y;
}
d[k].rad = atan2(d[k].y,d[k].x);
k++;
}
}
sort(d,d+k);
int L=0;int ans=1;int R=0;
while(L<k){
if(L==R){
R=(R+1)%k; ans++;
}
while(L!=R&&Turn(d[L],d[R])){
ans++; R=(R+1)%k;
}
maxn = max(maxn,ans); L++; ans--;
}
}
printf("%d\n",maxn);
}
return 0;
}
窗口滑动法
例题8-7 唯一的雪花 UVa11572
其实就是一个循环便利,只不过根据题意如果遇到与当前序列重复的数,就需要从前面找到这个重复的数然后删掉
每次都记录最大的序列长度,便利一遍,即得结果,下面两个实现,一个set,一个map
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int B[maxn];
int main()
{
int t,N;
scanf("%d",&t);
while(t--){
scanf("%d",&N);
for(int i=0;i<N;i++) scanf("%d",&B[i]);
set<int> s;
int L=0,R=0,ans=0;
while(R<N){
while(R<N&&!s.count(B[R])) { s.insert(B[R]); R++; }
ans=max(ans,R-L);
s.erase(B[L++]);
}
printf("%d\n",ans);
}
return 0;
}
用map算出上一次相同数的位置last数组
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int A[maxn],last[maxn];
map<int,int > cur;
int main()
{
int t,n;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
cur.clear();
for(int i=0;i<n;i++){
scanf("%d",&A[i]);
if(!cur.count(A[i])) last[i]=-1;
else last[i]=cur[ A[i] ];
cur[ A[i] ]=i;
}
int L=0,R=0,ans=0;
while(R<n){
while(R<n&&last[R]<L) R++;
ans=max(ans,R-L);
L++;
}
printf("%d\n",ans);
}
return 0;
}
使用数据结构:
再不用改变主算法的情况下,提高运行效率
求f(i)i到k的最小值,要求f(1)...f(n-k+1)