一、数论
1.1排序
1.1.1快速排序
输入:
10
6 1 2 7 9 3 4 5 10 8
输出:
1 2 3 4 5 6 7 8 9 10
/*
快速排序
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[100];
int n;
void quicksort(int L,int R)
{
if(L>R)
return ;
int temp=a[L];//temp中存的是基准数
int i=L;
int j=R;
while(i!=j)
{
while(a[j]>=temp&&i<j)//因为基准数在左边,所以每次必须是哨兵j先出发(特别注意)
j--;
while(a[i]<=temp&&i<j)
i++;
if(i<j)//当哨兵i和哨兵j没有相遇的时候
swap(a[i],a[j]);//交换两个数在数组中的位置
}
swap(a[i],a[L]);//最终将基准数归位
quicksort(L,i-1);
quicksort(i+1,R);
return;
}
int main()
{
while(~scanf("%d",&n))
{
memset(a,0,sizeof(a));
//输入
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
quicksort(1,n);
//输出
printf("%d",a[1]);
for(int i=2; i<=n; i++)
printf(" %d",a[i]);
printf("\n");
}
return 0;
}
1.1.2归并排序
归并排序可以用来求逆序对的个数。
逆序对:
对于序列:3 1 8 7 2
有 3和1,3和2,8和7,8和2,7和2,总共5对逆序对。
/*
归并排序
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[110];
int temp[110];
int n;
int num;//求逆序对的个数
void mergesort(int L,int R)
{
if(L==R)
return;
int mid=(L+R)>>1;
mergesort(L,mid);
mergesort(mid+1,R);
int i=L;
int j=mid+1;
int r=L;
while(i<=mid&&j<=R)
{
if(a[i]<=a[j])
{
temp[r++]=a[i++];
}
else
{
temp[r++]=a[j++];
//num+=(mid-i+1);左边元素的个数
}
}
while(i<=mid)
{
temp[r++]=a[i++];
}
while(j<=R)
{
temp[r++]=a[j++];
}
for(int s=L;s<=R;s++)
a[s]=temp[s];
}
int main()
{
while(~scanf("%d",&n))
{
memset(a,0,sizeof(a));
memset(temp,0,sizeof(temp));
num=0;
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
mergesort(1,n);
//printf("%d\n",num);
printf("%d",a[1]);
for(int i=2; i<=n; i++)
printf(" %d",a[i]);
printf("\n");
}
return 0;
}
1.2 快速幂
1.2.1 人见人爱A^B HDU - 2035
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll long long
ll quick_pow(ll a,ll b)
{
ll res=1;
while(b)
{
if(b%2!=0)
res=res*a%1000;//保留几位对10的几次方取余
a=a*a%1000;
b/=2;
}
return res;
}
int main()
{
ll a,b;
while(scanf("%lld%lld",&a,&b)&&(a||b))
{
ll ans=quick_pow(a,b);
printf("%lld\n",ans);
}
return 0;
}
1.3 素数打表
1.3.1 埃氏筛法
bool book[10000010];//0为素数 1不是
int su[1000100];//存放素数
int r;//素数的个数
void prime()
{
r=0;
memset(book,0,sizeof(book));
book[0]=book[1]=1;
for(int i=2;i<=10000000;i++)
{
if(!book[i])
{
su[r++]=i;
for(int j=i*2;j<=10000000;j+=i)
book[j]=1;
}
}
}
1.3.2 欧拉筛法
bool book[10000010];//0为素数 1不是
int su[1000100];//存放素数
int r;//素数的个数
void prime()
{
r=0;
memset(book,0,sizeof(book));
book[0]=book[1]=1;
for(int i=2; i<=10000000; i++)
{
if(book[i]==0)
su[r++]=i;
for(int j=0; j<r&&i*su[j]<=10000000; j++)
{
book[i*su[j]]=1;
if(i%su[j]==0)
break;
//i%su[j]==0此处是重点,避免了很多的重复判断,
//比如i=9,现在素数是2,3,5,7,进入二重循环,visit[2*9]=1;visit[3*9]=1;
//这个时候9%3==0,要跳出。因为5*9可以用3*15来代替,
// 如果这个时候计算了,i=15的时候又会被重复计算一次
// ,所以这里大量避免了重复运算。
}
}
}
1.4 欧几里德算法
/*
辗转相除法
*/
#include<stdio.h>
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);
int ans=gcd(a,b);
printf("%d\n",ans);
return 0;
}
1.5 区间筛法
1.5.1 Help Hanzo LightOJ - 1197
/*
题目大意:给出一个区间,求这个区间的素数个数。
思路:因为范围值大,所以需要用区间筛法。
b以内的合数最小质因数一定不超过sqrt(b);
如果有sqrt(b)以内的素数表的话,
就可以把埃氏筛法运用在[a,b)上了。
也就是说,先分别做好[a,sqrt(b))的表和[a,b)的表,
然后从[a,sqrt(b))的表中删除的同时,
也将倍数从[a,b)的表中划去,
最后剩下的就是区间[a,b)内的素数了。
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define ll long long
ll sum;
bool su[1000010];
bool book[1000010];
void solve(ll a,ll b)
{
for(ll i=0; i*i<b; i++)//sqrt(b)
su[i]=0;
for(ll i=0; i<b-a; i++)//[a,b)
book[i]=0;
for(ll i=2; i*i<b; i++)
{
if(su[i]==0)
{
for(ll j=i*2; j*j<b; j+=i)//sqrt(b)
su[j]=1;
for(ll k=max(2ll,(a+i-1)/i)*i; k<b; k+=i)//[a,b)
book[k-a]=1;//2ll是2的长整型
//((a+i-1)/i)*i是满足>=a&&%i==0的离a最近的数
}
}
for(int i=0; i<b-a; i++)
{
if(book[i]==0)
sum++;
}
}
int main()
{
int t;
int o=1;
scanf("%d",&t);
while(t--)
{
ll a,b;
scanf("%lld%lld",&a,&b);
sum=0;
solve(a,b+1);
if(a==1)
sum--;
printf("Case %d: %lld\n",o++,sum);
}
return 0;
}
1.6 前三位和后三位
/*
Leading and Trailing LightOJ - 1282
题目大意:对于给的数一个啊,一个b,求a的b次方的前三位数,和后三位数。
思路:对于后三位直接使用快速幂运算,对1000取余即可,
对于前三位,任意数都可以用10的w次方进行表示。详细看代码。
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define ll long long
ll mod_pow(ll x,ll n)//快速幂
{
ll res=1;
while(n>0)
{
if(n&1)
res=res*x%1000;
x=x*x%1000;
n>>=1;
}
return res;
}
int main()
{
int o=1;
int t;
scanf("%d",&t);
while(t--)
{
ll n,k;
scanf("%lld%lld",&n,&k);
ll ans1,ans2;
double w;
w=k*log10(n);//10的w次方==n的k次方,两边同时取log10
w=w-(ll)w;//取w的小数部分
ans1=(ll)pow(10,w+2);//10的w次方,w<0,所得的数应该>=1,所以再乘100
ans2=mod_pow(n,k);//后三位
printf("Case %d: %lld %03lld\n",o++,ans1,ans2);
}
return 0;
}
二、常用技巧
2.1尺取法
2.1.1 Subsequence (POJ No.3061)
方法一:预先计算好前缀和sum,然后使用二分搜索快速确定使序列和不小于S的的结尾的t的最小值。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[100010];
int sum[100010];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
int n,s;
scanf("%d%d",&n,&s);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
for(int i=1; i<=n; i++)
sum[i]=sum[i-1]+a[i];
if(sum[n]<s)
printf("0\n");
else
{
int res=n;
for(int i=0; sum[i]+s<=sum[n]; i++)
{
int t=lower_bound(sum+i,sum+n,sum[i]+s)-sum;
res=min(res,t-i);
}
printf("%d\n",res);
}
}
return 0;
}
方法二:尺取法
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[100010];
int sum[100010];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
int n,s;
scanf("%d%d",&n,&s);
for(int i=0; i<n; i++)
scanf("%d",&a[i]);
int res=n+1;
int i=0,j=0;
int sum=0;
while(1)
{
while(i<n&&sum<s)
sum+=a[i++];
if(sum<s)
break;
res=min(res,i-j);
sum-=a[j++];
}
if(res>n)
printf("0\n");
else
printf("%d\n",res);
}
return 0;
}
2.1 二分
2.1.1 整数定义域上的二分
/*
整数定义域上的二分
*/
int erfen()
{
int L=1;
int R=n;
int ans;
while(L<=R)
{
int mid=(L+R)>>1;
if(check(mid))
{
ans=mid;
L=mid+1;
}
else
R=mid-1;
}
return ans;
}
2.1.2 实数域上的二分
/*
实数域上的二分
*/
double erfen(double L,double R)
{
double mid;
while(fabs(L-R)>dlt)//dlt=0.001(根据题目要求确定精度)
{
mid=(L+R)/2.0;
if(check(mid))
R=mid;
else
L=mid;
}
return L;
}
3.1 三分
double L=0,R=1e9;
while(R-L>=1e-3)
{
double Lmid=L+(R-L)/3;
double Rmid=R-(R-L)/3;
if(f(Lmid)<f(Rmid))
L=Lmid;
else
R=Rmid;
}
三、字符串
3.1 KMP算法
3.1.1 剪花布条 HDU - 2087
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char s1[1010];
char s2[1010];
int nex[1010];
int L1,L2;
void getf()//得到next数组
{
memset(nex,0,sizeof(nex));
int i=0,j=-1;
nex[0]=-1;
while(i<L2)
{
if(j<0||s2[i]==s2[j])
nex[++i]=++j;
else
j=nex[j];
}
}
int main()
{
while(~scanf("%s",s1))
{
if(strcmp(s1,"#")==0)
break;
scanf("%s",s2);
L1=strlen(s1);
L2=strlen(s2);
getf();
int j=0;
int ans=0;
for(int i=0; i<L1; i++)
{
while(j>0&&s1[i]!=s2[j])//j不为0且当前串不匹配
j=nex[j];
if(s1[i]==s2[j])
j++;
if(j==L2)
{
ans++;
//j=nex[j];
j=0;//匹配串不能重叠
}
}
printf("%d\n",ans);
}
return 0;
}
3.1.2 Power Strings POJ - 2406
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char s[1000010];
int nex[1000010];
int L;
void getf()
{
memset(nex,0,sizeof(nex));
int i=0,j=-1;
nex[0]=-1;
while(i<L)
{
if(j<0||s[i]==s[j])
nex[++i]=++j;
else
j=nex[j];
}
}
int main()
{
while(1)
{
scanf("%s",s);
if(strcmp(s,".")==0)
break;
L=strlen(s);
getf();
if(L%(L-nex[L])==0)//循环节为L-next[L]
printf("%d\n",L/(L-nex[L]));//循环次数L/(L-nex[L])
else
printf("1\n");
}
return 0;
}
3.2 Manacher算法
3.2.1 最长回文 HDU - 3068
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char a[110010];
char s[210100];
int p[210030];
int r;
void manacher()
{
memset(p,0,sizeof(p));
int l=strlen(a);
r=2;
s[0]='@';
s[1]='#';
for(int i=0; i<l; i++)
{
s[r++]=a[i];
s[r++]='#';
}
int mx=0,id=0;
for(int i=1; i<r; i++)
{
p[i]=mx>i?min(mx-i,p[2*id-i]):1;
while(s[i+p[i]]==s[i-p[i]])
p[i]++;
if(i+p[i]>mx)
{
mx=i+p[i];
id=i;
}
}
}
int main()
{
while(~scanf("%s",a))
{
manacher();
int ans=0;
for(int i=1; i<r; i++)
ans=max(ans,p[i]-1);
printf("%d\n",ans);
}
return 0;
}
四、动态规划
4.1 最长公共子序列
/*
最长公共子序列
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int L1,L2;//输入的数组长度
char s1[1010];
char s2[1010];
int dp[1010][1010];
int main()
{
scanf("%s",s1);
scanf("%s",s2);
L1=strlen(s1);
L2=strlen(s2);
for(int i=0;i<L1;i++)
{
for(int j=0;j<L2;j++)
{
if(s1[i]==s2[j])
{
dp[i+1][j+1]=dp[i][j]+1;
}
else
{
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
}
}
}
printf("%d\n",dp[L1][L2]);
return 0;
}
4.2 最长上升子序列
4.2.1 方法一:动态规划
/*
最长上升子序列
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n;
int a[1010];
int dp[1010];
int main()
{
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&a[i]);
int ans=0;
for(int i=0; i<n; i++)
{
dp[i]=1;
for(int j=0; j<i; j++)
{
if(a[j]<a[i])
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
printf("%d\n",ans);
return 0;
}
4.2.2 方法二:贪心+二分
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int n;
int a[1010];
int dp[1010];
int main()
{
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&a[i]);
//最长上升子序列
fill(dp,dp+n,inf);
for(int i=0; i<n; i++)
{
*lower_bound(dp,dp+n,a[i])=a[i];
}
printf("%d\n",lower_bound(dp,dp+n,inf)-dp);
return 0;
}
4.3 背包问题
4.3.1 0-1背包
Bone Collector HDU - 2602
方法一:一维数组
/*
01背包
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int v[1010],w[1010];//价值、重量(体积)
int dp[1010];
int main()
{
int n,m;
scanf("%d %d",&n,&m);//物品的数量n和背包的容量m
for(int i=1;i<=n;i++)
scanf("%d",&v[i]);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)//逆序***
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[m]);
return 0;
}
方法二:二维数组
/*
01背包
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int v[1010],w[1010];//价值、重量(体积)
int dp[1010][1010];
int main()
{
int n,m;
scanf("%d %d",&n,&m);//物品的数量n和背包的容量m
for(int i=1; i<=n; i++)
scanf("%d",&v[i]);
for(int i=1; i<=n; i++)
scanf("%d",&w[i]);
for(int i=1; i<=n; i++)
{
for(int j=0; j<=m; j++)
{
dp[i][j]=dp[i-1][j];
}
for(int k=w[i]; k<=m; k++)
dp[i][k]=max(dp[i-1][k],dp[i-1][k-w[i]]+v[i]);
}
printf("%d\n",dp[n][m]);
return 0;
}
4.3.2 完全背包
湫湫系列故事——减肥记I HDU - 4508
/*
完全背包
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int v[1010],w[1010];//价值、重量(体积)
int dp[1010];
int main()
{
int n,m;
scanf("%d %d",&n,&m);//物品的数量n和背包的容量m
for(int i=1; i<=n; i++)
scanf("%d",&v[i]);
for(int i=1; i<=n; i++)
scanf("%d",&w[i]);
for(int i=1; i<=n; i++)
{
for(int j=w[i];j<=m;j++)//与01背包唯一不同
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
printf("%d\n",dp[m]);
return 0;
}
4.3.3 多重背包
悼念512汶川大地震遇难同胞——珍惜现在,感恩生活 HDU - 2191
/*
多重背包
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int v[1010],w[1010];//价值、重量(体积)
int num[1010];//每种物品的数量
int dp[1010];
int main()
{
int n,m;
scanf("%d %d",&n,&m);//物品的数量n和背包的容量m
for(int i=1; i<=n; i++)
scanf("%d",&v[i]);
for(int i=1; i<=n; i++)
scanf("%d",&w[i]);
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
for(int i=1; i<=n; i++)
{
for(int j=m;j>=0;j--)
{
for(int k=0;k<=num[i];k++)
{
if(j-k*v[i]<0)
break;
dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
}
}
}
printf("%d\n",dp[m]);
return 0;
}
五、数据结构
5.1线段树
5.1.1 更新一段区间,求区间和
/*
A Simple Problem with Integers POJ - 3468
Just a Hook HDU - 1698
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
long long sum[500000],lazy[500000];
void creat(int l,int r,int o)//建立线段树
{
if(l==r)
{
scanf("%lld",&sum[o]);
return;
}
int mid=(l+r)>>1;
creat(l,mid,o<<1);
creat(mid+1,r,o<<1|1);
sum[o]=sum[o<<1]+sum[o<<1|1];
}
void push(int o,int l)//更新左儿子和右儿子,下移操作
{
if(lazy[o])
{
lazy[o<<1]+=lazy[o];
lazy[o<<1|1]+=lazy[o];
sum[o<<1]+=lazy[o]*(l-(l>>1));
sum[o<<1|1]+=lazy[o]*(l>>1);
lazy[o]=0;//**********
}
}
void update(int x,int y,int l,int r,int o,int c)
{
if(l>=x&&r<=y)//****x和y为所要的区间。l-r为访问的区间
{
sum[o]+=c*(r-l+1);
lazy[o]+=c;
return;
}
push(o,r-l+1);
int mid=(r+l)>>1;
if(mid>=x)
update(x,y,l,mid,o<<1,c);
if(y>mid)
update(x,y,mid+1,r,o<<1|1,c);
sum[o]=sum[o<<1]+sum[o<<1|1];
}
long long ask(int x,int y,int l,int r,int o)
{
if(l>=x&&r<=y)
return sum[o];
push(o,r-l+1);
int mid=(r+l)>>1;
long long num=0;
if(mid>=x)
num+=ask(x,y,l,mid,o<<1);
if(y>mid)
num+=ask(x,y,mid+1,r,o<<1|1);
return num;
}
int main()
{
int n,q;
while(~scanf("%d%d",&n,&q))
{
memset(sum,0,sizeof(sum));
memset(lazy,0,sizeof(lazy));
creat(1,n,1);
while(q--)
{
char a[10];
int l,r,c;
scanf("%s",a);
if(a[0]=='Q')
{
scanf("%d%d",&l,&r);
printf("%lld\n",ask(l,r,1,n,1));
}
else
{
scanf("%d%d%d",&l,&r,&c);
update(l,r,1,n,1,c);
}
}
}
return 0;
}
5.1.2 求区间最值的差值
/*
Balanced Lineup POJ - 3264
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int ma[220000],mi[220000];
void creat(int l,int r,int o)//建立树状树
{
if(l==r)
{
scanf("%d",&ma[o]);
mi[o]=ma[o];
return ;
}
int mid=(r+l)>>1;
creat(l,mid,o<<1);
creat(mid+1,r,o<<1|1);
ma[o]=max(ma[o<<1],ma[o<<1|1]);
mi[o]=min(mi[o<<1],mi[o<<1|1]);
}
int ask1(int x,int y,int l,int r,int o)//最大值
{
if(l>=x&&r<=y)
return ma[o];
int mid=(l+r)>>1;
int t1=0,t2=0;
if(x<=mid)
t1=ask1(x,y,l,mid,o<<1);
if(y>mid)
t2=ask1(x,y,mid+1,r,o<<1|1);
return max(t1,t2);
}
int ask2(int x,int y,int l,int r,int o)//最小值
{
if(l>=x&&r<=y)
return mi[o];
int mid=(l+r)>>1;
int t1=0x3f3f3f3f,t2=0x3f3f3f3f;
if(x<=mid)
t1=ask2(x,y,l,mid,o<<1);
if(y>mid)
t2=ask2(x,y,mid+1,r,o<<1|1);
return min(t1,t2);
}
int main()
{
int n,m;
while(~scanf("%d",&n))
{
scanf("%d",&m);
memset(ma,0,sizeof(ma));
memset(mi,0,sizeof(mi));
creat(1,n,1);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
int t1=ask1(a,b,1,n,1);
int t2=ask2(a,b,1,n,1);
printf("%d\n",t1-t2);
}
}
return 0;
}
5.1.3 更新某点,求区间和
/*
敌兵布阵 HDU - 1166
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
long long sum[220000];
void creat(int l,int r,int o)//建立线段树
{
if(l==r)
{
scanf("%lld",&sum[o]);
return;
}
int mid=(l+r)>>1;
creat(l,mid,o<<1);
creat(mid+1,r,o<<1|1);
sum[o]=sum[o<<1]+sum[o<<1|1];
}
void update(int x,int l,int r,int o,int c)//更改某个点,一个线段二分的思想
{
if(l>r)return;
if(l==r)
{
sum[o]+=c;
return;
}
int mid=(l+r)>>1;
if(mid>=x)
update(x,l,mid,o<<1,c);
else
update(x,mid+1,r,o<<1|1,c);
sum[o]=sum[o<<1]+sum[o<<1|1];
}
long long ask(int x,int y,int l,int r,int o)//查找
{
if(l>=x&&r<=y)
return sum[o];
int mid=(l+r)>>1;
long long num=0;
if(mid>=x)
num+=ask(x,y,l,mid,o<<1);
if(y>mid)
num+=ask(x,y,mid+1,r,o<<1|1);
return num;
}
int main()
{
int t,o=1;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
memset(sum,0,sizeof(sum));
creat(1,n,1);
char a[10];
printf("Case %d:\n",o++);
while(1)
{
scanf("%s",a);
if(strcmp(a,"End")==0)
break;
int l,r;
int x;
int c;
if(a[0]=='Q')
{
scanf("%d%d",&l,&r);
printf("%lld\n",ask(l,r,1,n,1));
}
if(a[0]=='A')
{
scanf("%d%d",&x,&c);
update(x,1,n,1,c);
}
if(a[0]=='S')
{
scanf("%d%d",&x,&c);
update(x,1,n,1,-c);
}
}
}
return 0;
}
5.1.4 区间加,区间乘,求区间和
/*
维护序列 LibreOJ - 10129
*/
#include<bits/stdc++.h>
#define ll long long
#define N 400010
using namespace std;
ll mod,Sum[N],add[N],mul[N];
void init()
{
for(int i=1;i<N;i++)
add[i]=0,mul[i]=1;
}
void build(int l,int r,int o)
{
if(l==r)
{
scanf("%lld",&Sum[o]);
Sum[o]%=mod;
return;
}
int mid=(l+r)>>1;
build(l,mid,o<<1);
build(mid+1,r,o<<1|1);
Sum[o]=(Sum[o<<1]+Sum[o<<1|1])%mod;
}
void pushdown(int o,int len)
{
if(add[o]||mul[o]!=1)
{
int lson=o<<1;
int rson=o<<1|1;
Sum[lson]=Sum[lson]*mul[o]%mod;
Sum[rson]=Sum[rson]*mul[o]%mod;
Sum[lson]=(Sum[lson]+add[o]*(len-(len>>1)))%mod;
Sum[rson]=(Sum[rson]+add[o]*(len>>1))%mod;
mul[lson]=mul[lson]*mul[o]%mod;
mul[rson]=mul[rson]*mul[o]%mod;
add[lson]=add[lson]*mul[o]%mod;
add[rson]=add[rson]*mul[o]%mod;
add[lson]=(add[lson]+add[o])%mod;
add[rson]=(add[rson]+add[o])%mod;
add[o]=0,mul[o]=1;
}
}
void update(int x,int y,int l,int r,int o,ll c,int flag)
{
if(l>=x&&r<=y)
{
if(flag)
{
Sum[o]=Sum[o]*c%mod;
add[o]=add[o]*c%mod;
mul[o]=mul[o]*c%mod;
}
else
{
Sum[o]=(Sum[o]+c*(r-l+1))%mod;
add[o]=(add[o]+c)%mod;
}
return;
}
pushdown(o,r-l+1);
int mid=(l+r)>>1;
if(x<=mid)update(x,y,l,mid,o<<1,c,flag);
if(y>mid)update(x,y,mid+1,r,o<<1|1,c,flag);
Sum[o]=(Sum[o<<1]+Sum[o<<1|1])%mod;
}
ll query(int x,int y,int l,int r,int o)
{
if(l>=x&&r<=y)
return Sum[o];
pushdown(o,r-l+1);
int mid=(l+r)>>1;
ll sum=0;
if(x<=mid)sum=(sum+query(x,y,l,mid,o<<1))%mod;
if(y>mid)sum=(sum+query(x,y,mid+1,r,o<<1|1))%mod;
return sum;
}
int main()
{
init();
int n;
scanf("%d%lld",&n,&mod);
build(1,n,1);
int m;
scanf("%d",&m);
while(m--)
{
int op,l,r;
ll c;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d%lld",&l,&r,&c);
update(l,r,1,n,1,c,1);
}
else if(op==2)
{
scanf("%d%d%lld",&l,&r,&c);
update(l,r,1,n,1,c,0);
}
else
{
scanf("%d%d",&l,&r);
printf("%lld\n",query(l,r,1,n,1));
}
}
return 0;
}
5.2 树状数组
/*
lowbit(x)表示取出非负整数x在二进制表示下:
最低位的1以及它后面的0构成的数值
*/
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int v)//在序列第x个位置上加上v,并在树状数组中修改相应元素
{
while(x<=n)
{
c[x]+=v;
x+=lowbit(x);
}
}
int sum(int x)//计算序列中第1数到第x数的和
{
int res=0;
while(x>0)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
5.3 二叉搜索树
5.3.1 判断是否为同一棵树
/*
二叉搜索树 HDU - 3791
题目大意:给的第一组数据建成标准树,
下面的n组数据让判断是否和标准树是同一棵树。
思路: 如果两个是同一棵树的话,它们的节点应该是相同的。
所以采用一种遍历方式,依次判断它们的节点是否相同。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int flag;
struct node
{
int data;
node *lson;
node *rson;
// node()
// {
// lson=NULL;
// rson=NULL:
// }
};
node *inser(node *root,int x)
{
if(root==NULL)
{
node *p=new node;//申请新空间
p->data=x;//赋值
p->lson=p->rson=NULL;
return p;
}
else
{
if(root->data>x)//左子树
root->lson=inser(root->lson,x);
if(root->data<x)//右子树
root->rson=inser(root->rson,x);
return root;
}
}
void check(node *root,node *room)
{
if(root!=NULL&&room!=NULL)
{
//前序遍历,根-左-右
//中序遍历,左-根-右
//后序遍历,左-右-根
if(root->data!=room->data)
flag=0;
check(root->lson,room->lson);
check(root->rson,room->rson);
}
else if(root!=NULL||room!=NULL)//所在位置,一个有值,一个没有
flag=0;
}
int main()
{
int n;
while(~scanf("%d",&n)&&n)
{
char a[20];
char b[20];
node *root=NULL;//地址为空
scanf("%s",a);
for(int i=0; i<strlen(a); i++)
root=inser(root,a[i]-'0');//构建一棵树
for(int i=0; i<n; i++)
{
flag=1;
node *room=NULL;
scanf("%s",b);
for(int i=0; i<strlen(b); i++)
room=inser(room,b[i]-'0');
check(root,room);
if(flag)
printf("YES\n");
else
printf("NO\n");
}
}
return 0;
}
5.3.2 知前序和中序求后序
/*
Tree Recovery POJ - 2255
前序的遍历方式:根->左->右,中序的遍历为左->根->右,
在前序中从前向后进行遍历,在中序中找到根在的位置,
从而判断当前的左右子树,递归的过程完成左子树,然后回溯的过程完成右子树。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct node
{
char data;
node *lson;
node *rson;
node()
{
lson=NULL;
rson=NULL;
}
};
node *inser(char *s1,char *s2,int l)
{
if(l<=0)
return NULL;
node *root=new node();
root->data=*s1;
char *p;
for(p=s2; p!=NULL; p++)//在中序遍历结果中找到 当前根,此时在中序序列中此根左边是左子树,右边是右子树
{
if(*p==*s1)
break;
}
int kk=p-s2;//左子树的长度
root->lson=inser(s1+1,s2,kk);//访问左子树
root->rson=inser(s1+kk+1,s2+kk+1,l-kk-1);//访问右子树
return root;
}
void out(node *root)
{
if(root==NULL)
return ;
out(root->lson);
out(root->rson);
printf("%c",root->data);
}
int main()
{
char a[10010],b[10010];
while(~scanf("%s%s",a,b))
{
int l=strlen(a);
node *root=new node();
root=inser(a,b,l);
out(root);
printf("\n");
}
return 0;
}
5.3.3 输出前序遍历
/*
Tree Recovery POJ - 2255
题目大意:已知一棵树,求一棵树和现在的树相同,但字典序最小。
思路: 二叉搜索树需要满足左儿子小于父节点,右儿子大于父节点,
要求是同一棵树,所以应该根节点相同,先根后左最后右,即前序遍历。
*/
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>V;
struct node
{
int data;
node *lson;//左儿子
node *rson;//右儿子
node()//初始化
{
lson=NULL;
rson=NULL;
}
};
void inser(node *&root,int x)//BST插入函数
{
if(root==NULL)//遇到为NULL,即应该插入的位置
{
root=new node();//新建节点
root->data=x;//赋权值
return ;
}
if(root->data==x)//当前节点为插入的值,直接返回
return;
else if(root->data>x)//插入的值小于当前节点,需插入左子树
inser(root->lson,x);
else if(root->data<x)//插入的值大于当前节点,需插入右子树
inser(root->rson,x);
}
node *build()//BST建树********
{
node *root=NULL;
vector<int>::iterator it;//迭代器
for(it=V.begin(); it!=V.end(); it++)//依次插入树中
{
inser(root,*it);
}
return root;//返回的是根节点
}
void out(node *root,node *flag)//输出函数
{
if(root==NULL)//当访问的为空,即返回(递归的边界)
return ;
if(root!=flag)
printf(" ");
printf("%d",root->data);//输出当前节点的值
out(root->lson,flag);//访问左子树
out(root->rson,flag);//访问右子树
}
int main()
{
int n;
while(~scanf("%d",&n))
{
int x;
V.clear();//容器的清空
for(int i=0; i<n; i++)
{
scanf("%d",&x);
V.push_back(x);
}
node *root=build();
out(root,root);
printf("\n");
}
return 0;
}
5.3.4 根节点到指定节点的路径
/*
Elven Postman HDU - 5444
题目大意:已知一棵二叉树(倒置),
求从根节点到所要访问的节点的路径。
思路:正着建二叉树,在建树的同时将路径记录下来,
倒置后,左右方向相反。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct node
{
int data;
node *lson;
node *rson;
node()
{
lson=rson=NULL;
}
};
node *inser(node *root,int x)
{
if(root==NULL)//****************
{
node *p=new node;
p->data=x;
return p;
}
if(x<root->data)
root->lson=inser(root->lson,x);
if(x>root->data)
root->rson=inser(root->rson,x);
return root;
}
char ans[12000];
int flag;
void find(node *root,int x)
{
if(root==NULL)
return;
if(root->data==x)
return ;
if(root->data>x)//左
{
ans[flag++]='E';
find(root->lson,x);
}
if(root->data<x)//右
{
ans[flag++]='W';
find(root->rson,x);
}
}
int main()
{
int c;
scanf("%d",&c);
while(c--)
{
int m,n;
scanf("%d",&n);
node *root=NULL;
int x;
for(int i=0; i<n; i++)
{
scanf("%d",&x);
root=inser(root,x);
}
scanf("%d",&m);
for(int i=0; i<m; i++)
{
flag=0;
scanf("%d",&x);
find(root,x);
if(!flag)
printf("\n");
else
{
for(int i=0;i<flag;i++)
printf("%c",ans[i]);
printf("\n");
}
}
}
return 0;
}
六、图论
6.1 邻接表
/*
邻接表
u v w 三个数组来记录每条边的信息
u[i] v[i] w[i] 表示第i条边是从u[i]号顶点到v[i]号顶点,且权值为w[i]
first[u[i]]保存顶点u[i]的第一条边的编号
next[i]存储 "编号为i的边" 的 "下一条边" 的编号
*/
memset(first,-1,sizeof(first));
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
next[i]=first[u[i]];
first[u[i]]=i;
}
6.2 最短路
6.2.1 Floyd
/*
Floyd
有n个点
*/
void Floyd()
{
//初始化
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)
e[i][j]=0;
else
e[i][j]=0x3f3f3f3f;
}
}
//五行代码
for(int k=1; k<=n; k++)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(e[i][j]>e[i][k]+e[k][j])
{
e[i][j]=e[i][k]+e[k][j];
}
}
}
}
}
6.2.2 dijkstra
/*
dijkstra
有n个点
e数组用来存图
dis数组用来存1号点到各点的距离
book数组用来标记点是否走过
*/
void dijkstra()
{
//初始化
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)
e[i][j]=0;
else
e[i][j]=0x3f3f3f3f;
}
}
//1号点到各点的距离
for(int i=1;i<=n;i++)
{
dis[i]=e[1][i];
}
memset(book,0,sizeof(book));
book[1]=1;
//dijkstra算法核心语句
int mi,u;
for(int i=1;i<=n-1;i++)
{
mi=0x3f3f3f3f;
for(int j=1;j<=n;j++)
{
if(!book[j]&&dis[j]<mi)
{
mi=dis[j];
u=j;
}
}
book[u]=1;
for(int v=1;v<=n;v++)
{
if(dis[v]>dis[u]+e[u][v])
{
dis[v]=dis[u]+e[u][v];
}
}
}
}
6.2.3 Bellman-Ford 解决负权边
/*
Bellman-Ford
dis[v[i]]为1到v[i]的最短距离
dis[u[i]]为1到u[i]的最短距离
w[i]为u[i]到v[i]的距离
*/
void Bellman()
{
//初始化dis数组,这里是1号点到其余各点的初始路程
for(int i=1;i<=n;i++)
{
dis[i]=inf;
}
dis[1]=0;
//Bellman-Ford算法核心语句
for(int k=1;k<=n-1;k++)
{
int check=0;//用来标记在本轮松弛中数组dis是否会发生更新
//进行一轮松弛
for(int i=1;i<=m;i++)
{
if(dis[v[i]]>dis[u[i]]+w[i])
{
dis[v[i]]=dis[u[i]]+w[i];
check=1;
}
}
//松弛完毕后检测数组dis是否有更新
if(check==0)
break;//如果数组dis没有更新,提前结束算法
}
//检测负权回路
flag=0;
for(int i=1;i<m;i++)
{
if(dis[v[i]]>dis[u[i]]+w[i])
{
flag=1;
}
}
if(flag==1)
printf("此图含有负权回路\n");
}
6.2.4 SPFA(队列优化的Bellman-Ford)
/*
SPFA
book[now]=0;//出队
需要出队的样例
4 5
1 2 18
1 4 10
2 3 4
3 4 3
4 2 2
*/
void SPFA()
{
//初始化
memset(book,0,sizeof(book));
for(int i=1; i<=n; i++)
dis[i]=inf;//1号点到其余各点的路程
dis[1]=0;
book[1]=1;//标记1号点已经入队
queue<int>q;
int now;
q.push(1);
while(!q.empty())
{
now=q.front();
q.pop();
for(int k=first[now]; k!=-1; k=next[k])
{
if(dis[v[k]]>dis[u[k]]+w[k])//判断是否松弛成功
{
dis[v[k]]=dis[u[k]]+w[k];//更新顶点1到顶点v[k]的路程
//book数组用来判断顶点v[k]是否在队列中
//如果不使用一个数组来标记的话,每次都要把队列中的元素进行遍历,很浪费时间
if(!book[v[k]])
{
book[v[k]]=1;
q.push(v[k]);
}
}
}
book[now]=0;//出队
}
}
6.3 最小生成树
6.3.1 Kruskal
/*
Networking POJ - 1287
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int f[11000];
int n,m;
void init()
{
for(int i=1; i<=n; i++)
f[i]=i;
}
//并查集寻找祖先的函数
int getf(int v)
{
if(f[v]!=v)//路径压缩
f[v]=getf(f[v]);
return f[v];
}
//并查集合并两子集合的函数
int unite(int u,int v)
{
int t1=getf(u);
int t2=getf(v);
if(t1!=t2)//判断两个点是否在同一个集合中
{
f[t2]=t1;
return 1;
}
return 0;
}
struct node
{
int u;
int v;
int w;
} q[11000];
//按照边的权值从小到大排序
bool cmp(node x,node y)
{
return x.w<y.w;
}
int main()
{
while(scanf("%d",&n)&&n)
{
scanf("%d",&m);
init();//初始化
int ans=0;
for(int i=0; i<m; i++)
scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);
sort(q,q+m,cmp);
int num=0;
//Kruskal算法核心部分
for(int i=0; i<m; i++)
{
//判断一条边的两个顶点是否已经连通,即判断是否已经在同一个集合中
if(unite(q[i].u,q[i].v))//如果目前尚未连通,则选用这条边
{
ans+=q[i].w;
num++;
}
if(num==n-1)//直到选用n-1条边之后退出循环
break;
}
printf("%d\n",ans);
}
return 0;
}
6.3.2 Prim
/*
Networking POJ - 1287
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int e[55][55];
int dis[55];
int book[55];
int n,m;
int ans;
void Prim()
{
memset(book,0,sizeof(book));
for(int i=1; i<=n; i++)
dis[i]=inf;
dis[1]=0;
int mi;
int u;
for(int i=1; i<=n; i++)
{
mi=inf;
for(int j=1; j<=n; j++)
{
if(!book[j]&&mi>dis[j])
{
mi=dis[j];
u=j;
}
}
ans+=dis[u];
book[u]=1;
for(int v=1; v<=n; v++)
{
if(!book[v]&&dis[v]>e[u][v])
dis[v]=e[u][v];
}
}
}
int main()
{
while(scanf("%d",&n)&&n)
{
scanf("%d",&m);
ans=0;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i==j)
e[i][j]=0;
else
e[i][j]=inf;
}
}
int t1,t2,t3;
for(int i=0; i<m; i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
if(e[t1][t2]>t3)
e[t2][t1]=e[t1][t2]=t3;
}
Prim();
printf("%d\n",ans);
}
return 0;
}
6.4 拓扑排序
6.4.1 Sorting It All Out POJ - 1094
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char ans[30];
int n,m,num;
int book[30][30],s[30],t[30],mark[30];
int topsort()
{
int l=0,flag=0;
for(int i=1; i<=num; i++)
{
int k=-1;
int cnt=0;
for(int j=1; j<=n; j++)
{
if(mark[j]&&s[j]==0)
{
k=j;
cnt++;
}
}
if(k==-1)//*********
return -1;
if(cnt!=1)
flag=1;
s[k]=-1;
ans[l++]='A'+(k-1);
for(int i=1; i<=n; i++)
{
if(book[k][i])
s[i]--;
}
}
ans[l]=0;
if(num==n&&!flag)
return 1;
return 0;
}
int main()
{
while(~scanf("%d%d",&n,&m)&&(n||m))
{
num=0;//初始化
int flag=0;
char str[10];
//清空函数
memset(t,0,sizeof(t));
memset(s,0,sizeof(s));
memset(book,0,sizeof(book));
memset(mark,0,sizeof(mark));
//一边输入一边判断
for(int i=1; i<=m; i++)
{
scanf("%s",str);
if(flag!=0)
continue;
//将字符转化为数字
int t1=str[0]-'A'+1;
int t2=str[2]-'A'+1;
//将出现过的字符进行标记
if(!mark[t1])
{
mark[t1]=1;
num++;
}
if(!mark[t2])
{
mark[t2]=1;
num++;
}
//进行拓扑排序的模拟
if(!book[t1][t2])
{
t[t2]++;
book[t1][t2]=1;
//t数组将之前的数据
for(int j=0; j<30; j++)
s[j]=t[j];
int kk=topsort();
if(kk==-1)
{
printf("Inconsistency found after %d relations.\n",i);
flag=1;
}
else if(kk==1)
{
printf("Sorted sequence determined after %d relations: %s.\n",i,ans);
flag=1;
}
}
}
if(!flag)
printf("Sorted sequence cannot be determined.\n");
}
return 0;
}
6.5 二分图最大匹配
/*
输入:
3 5
1 1
1 2
2 2
2 3
3 1
输出:
3
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,m,k;
int e[110][110];
int match[110];
int book[110];
int dfs(int u)
{
for(int i=1; i<=m; i++)
{
if(!book[i]&&e[u][i])
{
book[i]=1;//标记i点已经访问过
//如果i未被配对,或者找到了新的配对
if(!match[i]||dfs(match[i]))
{
match[i]=u;
return 1;
}
}
}
return 0;
}
int solve()
{
int ans=0;
memset(match,0,sizeof(match));
for(int i=1; i<=n; i++)
{
memset(book,0,sizeof(book));//清空上次搜索时的标记
if(dfs(i))
ans++;//寻找增广路,如果找到,匹配数加1
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);//n个点 m条边
memset(e,0,sizeof(e));
for(int i=0; i<m; i++)
{
int a,b;
scanf("%d%d",&a,&b);
e[a][b]=1;
}
int ans=solve(); //ans为最大匹数
printf("最大匹数: ");
printf("%d\n",ans);
return 0;
}
6.6 强连通分量 Tarjan
/*
Network of Schools POJ - 1236
*/
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
int n;
vector<int>v[110];
stack<int>s;
int dfn[110],low[110];
int book[110];//是否在栈中
int vis[110];//在同一个组内
int num;//访问的第几个数
int cnt;//多少组
int in[110];
int out[110];
void init()
{
for(int i=0; i<=n; i++)
v[i].clear();
while(!s.empty())
s.pop();
num=0;
cnt=0;
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
memset(book,0,sizeof(book));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(vis,0,sizeof(vis));
}
void tarjan(int x)
{
dfn[x]=low[x]=++num;
s.push(x);
book[x]=1;
for(int i=0; i<v[x].size(); i++)
{
int y=v[x][i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(book[y])
{
low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]==low[x])
{
int now;
++cnt;
vis[x]=cnt;
do
{
now=s.top();
s.pop();
vis[now]=cnt;
book[now]=0;
}while(now!=x);
}
}
int main()
{
while(~scanf("%d",&n))
{
init();
for(int i=1; i<=n; i++)
{
int a;
while(scanf("%d",&a)&&a)
{
v[i].push_back(a);
}
}
for(int i=1; i<=n; i++)
{
if(!dfn[i])
tarjan(i);
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j];
if(vis[i]!=vis[x])
{
in[vis[x]]++;
out[vis[i]]++;
}
}
}
int sum1=0,sum2=0;
for(int i=1;i<=cnt;i++)
{
if(in[i]==0)
sum1++;
if(out[i]==0)
sum2++;
}
if(cnt==1)
printf("1\n0\n");
else
printf("%d\n%d\n",sum1,max(sum1,sum2));
}
return 0;
}