算法题刷题目录(updating)

递归与递推

92. 递归实现指数型枚举

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 16;
int n;
int state[N];
int flag[N];

void Fun(int m,int begin)
{
	if(m>n)	
	{
		
		for(int i=1;i<=n;i++) 
			if(state[i]) printf("%d ",state[i]);
		puts("");
		return;
	}
	
	state[m]=begin;
	flag[begin]=1;
	Fun(m+1,begin+1);
	state[m]=0;
	flag[begin]=0;
	
	Fun(m+1,begin+1);
	
}


int main()
{
	cin>>n;
	
	Fun(1,1);
}

 94. 递归实现排列型枚举

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 10;
int n;
int state[N];
int flag[N];

void Fun(int m)
{
	if(m>n)	
	{
		
		for(int i=1;i<=n;i++) 
			printf("%d ",state[i]);
		puts("");
		return;
	}
	for(int j=1;j<=n;j++)
	{
		if(!flag[j])
		{
			state[m]=j;
			flag[j]=1;
			Fun(m+1);
			state[m]=0;
			flag[j]=0;
		}
	}
}


int main()
{
	scanf("%d",&n);
	
	Fun(1);
}

93. 递归实现组合型枚举

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 26;
int n,u; 
int state[N];
int flag[N];

void Fun(int m,int begin)
{
	if(m>u)
	{
		for(int j=1;j<m;j++) printf("%d ",state[j]);
		puts("");
		return;
	}
	
	for(int i=begin;i<=n;i++)
	{
		if(!flag[i])
		{
			state[m]=i;
			flag[i]=1;
			Fun(m+1,i+1);;
			state[m]=flag[i]=0;
		}
	}
	
}

int main()
{
	cin>>n>>u;
	
	Fun(1,1);
}

1209. 带分数

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 26;
int n,ans;
int state[N],copystate[N];

bool Check(int a,int c)
{
	memcpy(copystate,state,sizeof state);//使用copystate数组对保存state数组的值,目的是不用对state的值进行修改(直接修改state的值还要再完成函数后恢复现场) 
	int b=n*c-a*c;
	if(!a||!b||!c) return 0;//q,b,c中任意一个数为0,都是不符合要求的,所以返回0 
	
	while(b)//求出b的各个数位并进行判断和修改 
	{
		if(!(b%10)||copystate[b%10]) return 0;//如果b有数位为0或者已被使用过,那么返回0 
		copystate[b%10]=1;//对于未使用过的数字进行标记 
		b/=10;
	}
	
	for(int i=1;i<10;i++)//检验是否0~9所有的数都被使用过 
	{
		if(!copystate[i]) return 0;//若有未使用过的,那么返回0 
	}
	
	return 1;//否则此种组合成立,返回1 
}

void FindC(int m,int a,int c)//确定c,c是分数的分母 
{
	if(m==n) return;
	if(Check(a,c)) ans++;//此时a,c都有值了,那么可以尝试看看是否已经满足一种情况 ,所以进入check函数检验 
	
	for(int i=1;i<10;i++)
	{
		if(!state[i])
		{
			state[i]=1;
			FindC(m+1,a,10*c+i);//对c进行遍历 
			state[i]=0;//恢复现场 
		}
	}
}

void FindA(int m,int a)//确定a,a是那个整数 
{
	if(a>=n) return;
	if(a) FindC(m+1,a,0);//给a一个取值后,开始对c进行递归求解 
	
	for(int i=1;i<10;i++)
	{
		if(!state[i])
		{
			state[i]=1;
			FindA(m+1,10*a+i);
			state[i]=0;//恢复现场 
		}
	}
}

int main()
{
	cin>>n;
	
	FindA(0,0);
	
	printf("%d",ans);
}

717. 简单斐波那契

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

int main()
{
	cin>>n;
	int fib1,fib2,fib3;
	
	fib1=0,fib2=1;
	if(n==1) printf("%d ",fib1);
	else if(n==2) printf("%d %d",fib1,fib2);
	else if(n>2) 
	{
		printf("%d %d ",fib1,fib2);
		for(int i=3;i<=n;i++)
		{
			fib3=fib1+fib2;
			fib1=fib2;
			fib2=fib3;
			printf("%d ",fib3);
		}
	}
}

此为最优算法

95.费解的开关

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;

const int N = 5;

bool light[N][N];

int main()
{
	int x,y;
	scanf("%d %d",&x,&y);
	while(x!=-1)
	{
		light[x][y]=!light[x][y];
		if(x-1>=0) light[x-1][y]=!light[x-1][y];
		if(y-1>=0) light[x][y-1]=!light[x][y-1];
		if(x+1<5) light[x+1][y]=!light[x+1][y];
		if(y+1<5) light[x][y+1]=!light[x][y+1];
	
		for(int i=0;i<5;i++)
		{
			for(int j=0;j<5;j++)
			{
				printf("%d ",light[i][j]);
			}
			puts("");
		}
		scanf("%d %d",&x,&y);
		system("cls");
	}
}

1208.翻硬币

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;

char start[102],des[102];

void Turn(int i)//将硬币翻转 
{
	if(start[i]=='*') start[i]='o';
	else start[i]='*';
}

int main()
{
	cin>>start>>des;
	int res=0;
	
	for(int i=0;i<strlen(start)-1;i++)
	{
		if(start[i]!=des[i])
		{
			Turn(i),Turn(i+1);
			res++;
		}
	}
	
	cout<<res;
}

二分与前缀和

789.数的范围

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

int n,m;
int* arr=new int[n];

void Find(int x)
{
	int l=0,r=n-1,mid,i,j;
	
	while(l<r)//二分查找
	{
		mid=l+r>>1;
		if(arr[mid]<x) l=mid+1;
		else r=mid;
	}
	
	if(arr[l]==x) //查看被找到的数附近还有没有该数
	{
		for(i=l-1;arr[i]==x&&i>=0;i--);
		for(j=l+1;arr[j]==x&&j<n;j++);
		cout<<i+1<<" "<<j-1<<endl;
	}
	else
	{
		printf("-1 -1\n");
	}
}

int main()
{
	int x;
	cin>>n>>m;
	
	for(int i=0;i<n;i++)  cin>>arr[i];
	
	for(int i=0;i<m;i++)
	{
		cin>>x;
		Find(x);
	}
}

730. 机器人跳跃问题

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

int h[100],n;

bool Judge(int e)
{
	for(int i=0;i<n;i++)
	{
		e=e*2-h[i];
		if(e>=*max_element(h,h+n)) return 1;//这句很重要,因为如果不加的话,for循环里面就会缺少终止条件,导致int溢出
		if(e<0) return 0;
	}
	return 1;
}

int main()
{
	cin>>n;
	for(int i=0;i<n;i++) cin>>h[i];
	
	int l=0,r=*max_element(h,h+n),mid;
	
	while(l<r)
	{
		mid=l+r>>1;
		if(Judge(mid)) r=mid;//当前mid可以的话,试试能不能再缩小一点 
		else l=mid+1;//当前mid不可以,增大l 
	}

	cout<<l;
}

1221. 四平方和

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

const int N=5*1e6;

int a,b,c,d;

struct sum
{
	int s;
	int c;
	int d;
	
	bool operator<(const sum &temp)const
	{
		if(s!=temp.s) return s<temp.s;
		if(c!=temp.c) return c<temp.c;
		return d<temp.d;
	}
}sum[N];


int main()
{
	int n,i=0,x;
	cin>>n;
	
	for(c=0;c*c<=n;c++)
		for(d=c;c*c+d*d<=n;d++) sum[i++]={c*c+d*d,c,d};
		
	sort(sum,sum+i);
	
	for(a=0;a*a<=n;a++)
	{
		for(b=a;b*b+a*a<=n;b++)
		{
			x=n-a*a-b*b;
			
			int l=0,r=i-1;
			while(l<r)
			{
				int mid=r+l>>1;
				if(sum[mid].s>=x) r=mid;
				else l=mid+1;
			}
			
			if(sum[l].s==x)
			{
				cout<<a<<" "<<b<<" "<<sum[l].c<<" "<<sum[l].d<<endl;
				return 0;
			}
		}
	}	
}

795.前缀和(重要思想)

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

int x[1000001];

int main()
{
	int n,a,m,a1,a2;
	cin>>n>>m;
	
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&a);
		x[i]=x[i-1]+a;
	}
	
	while(m)
	{
		scanf("%d %d",&a1,&a2);
		printf("%d",x[a2]-x[a1-1]);
		m--;
	}
}
思想:一维前缀和

796.子矩阵的和(重要思想)

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

int mat[1001][1001],sum[1001][1001];
int n,m,q;

int main()
{
	int x1,y1,x2,y2;
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++) 
		for(int j=1;j<=m;j++) cin>>mat[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+mat[i][j];//容斥原理求各矩阵之和 
			
	while(q)
	{
		scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
		printf("%d",sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]);//容斥原理根据各矩阵之和求各子矩阵之和 
		q--;
	}
}
思想:二维前缀和

注意点就是:

1.求矩阵中的从matrix[0][0]到matrix[i][j]这一矩阵和的过程中为了节省时间使用了动态数组和容斥原理

2.求矩阵的子矩阵和的时候不要单纯认为是sum[x2][y2]-sum[x1][y1],这很明显是不对的,也要使用容斥原理

k倍区间(P)

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

long long x[100001],counts[100001];

int main()
{
	int n,m;
	long long res=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++) 
	{
		scanf("%lld",&x[i]);
		x[i]+=x[i-1];
	}
	
	counts[0]=1;
	for(int i=1;i<=n;i++)
	{
		res+=counts[x[i]%m];
		counts[x[i]%m]++;
	}
	cout<<res;;
}

激光炸弹(P)

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

const int N=5010;
int n,m,s[N][N];

int main()
{
	int cnt,R,res=0;
	cin>>cnt>>R;
	R=min(5001,R);
	
	n=m=R;
	while(cnt--)
	{
		int x,y,w;
		cin>>x>>y>>w;
		s[++x][++y]=w;
		n=max(n,x),m=max(m,y);
	}
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
			
	for(int i=R;i<=n;i++)
		for(int j=R;j<=m;j++)
			res=max(res,s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R]);
	
	cout<<res;
}

数学与简单DP

买不到的数

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;


bool Judge(int p,int q,int i)
{
	if(!i) return 1;
	
	if(i>=p&&Judge(p,q,i-p)) return 1;
	if(i>=q&&Judge(p,q,i-q)) return 1;
	
	return 0;
	
}

int main()
{
	int p,q,res;
	cin>>p>>q;
	
	for(int i=1;i<=1000;i++)
		if(!Judge(p,q,i)) res=i;
	
	cout<<res;
}

由于暴力搜索时间复杂度太大,所以通过寻找数学规律优化代码,代码如下:

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

int main()
{
	int p,q,res;
	cin>>p>>q;
	cout<<q*(p-1)-p;
}

通过使用暴力搜索,我们可以打表寻找规律,进而总结出上述的规律

拓展(数学知识):

1.当p,q互质时,必定存在整数a,b,使得ap+bq=1,所以存在amp+bmq=m,即此时不存在所谓的不存在的数,但是当规定a>0&&b>0时,就存在这样的解了

2.当p,q的最大公约数是d时,那么整数a,b都会使得ap+bq=nd(n是整数),此时也不存在上述解

蚂蚁感冒

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

const int N=55;
int n[N];

int main()
{
	int m;
	cin>>m;
	for(int i=0;i<m;i++) cin>>n[i];
	int left=0,right=0;
    //left表示在感冒蚂蚁的左边且向右爬的蚂蚁个数,right表示在感冒蚂蚁的右边且向左爬的蚂蚁个数
	
	if(n[0]>0)//分为两种情况,第一种是感冒的蚂蚁的朝向是右边(左边同理)
	{
		for(int i=1;i<m;i++)
		{
			if(abs(n[i])<abs(n[0])&&n[i]>0) left++;
			if(abs(n[i])>abs(n[0])&&n[i]<0) right++;
		}
	
		if(!right) cout<<1;//没有右边的蚂蚁向左爬,那么只会有一个感冒的蚂蚁
		else cout<<right+left+1;//否则,右边向左爬的蚂蚁和左边向右爬的蚂蚁都会感冒
	}
	else if(n[0]<0)
	{
		for(int i=1;i<m;i++)
		{
			if(abs(n[i])<abs(n[0])&&n[i]>0) left++;
			if(abs(n[i])>abs(n[0])&&n[i]<0) right++;
		}
	
		if(!left) cout<<1;
		else cout<<right+left+1;
	}
}

核心思想:

1.题目中提到的当两只蚂蚁相遇时会调转方向,可以理解为两只蚂蚁依然朝着原来的方向爬

2.右边的向右爬的蚂蚁和左边的向左爬的蚂蚁都不会感染,所以只需要考虑代码中提到的两种情况

饮料换购

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

int main()
{
	int n,res;
	cin>>n;
	res=n;
	
	while(n>=3)
	{
		res+=n/3;
		n=n/3+n%3;
	}
	
	cout<<res;
}
拓展(代码技巧):

1.向上取整:ceil(a/b)=floor((a+b-1)/b)

2.负数取模:设a<0,则a%b=(a%b+b)%b

枚举,模拟与排序

连号区间数

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

const int N=10010;

int n,arr[N],INF=1e8;

int main()
{	
	int res=0;
	cin>>n;
	for(int i=0;i<n;i++) cin>>arr[i];

	for(int i=0;i<n;i++)
	{
		int minv=INF,maxv=-INF;
		for(int j=i;j<n;j++)
		{
			minv=min(minv,arr[j]);
			maxv=max(maxv,arr[j]);
			if(maxv-minv==j-i) res++;
		}		
	}
	cout<<res;
}

最小三元组(重要思想)

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

const int N=10010;

int n;
int a[N],b[N],c[N];//存数 
int cnt[N];//前缀和数组 
int acount[N],ccount[N];//acount[i]表示 a数组中小于b[i]的值的个数 ,ccount表示c数组中大于吧b[i]的值的个数 

int main()
{	
	cin>>n;
	for(int i=0;i<n;i++) scanf("%d",&a[i]),a[i]++;
	for(int i=0;i<n;i++) scanf("%d",&b[i]),b[i]++;
	for(int i=0;i<n;i++) scanf("%d",&c[i]),c[i]++;
	
	for(int i=0;i<n;i++) cnt[a[i]]++;//统计各值在a数组中出现的次数 
	for(int i=1;i<N;i++) cnt[i]+=cnt[i-1];//求前缀和,统计a数组中小于等于i的值的个数 
	for(int i=0;i<n;i++) acount[i]=cnt[b[i]-1];//求a数组中小于b[i]的值的个数
	
	memset(cnt,0,sizeof(cnt));
	for(int i=0;i<n;i++) cnt[c[i]]++;//统计各值在c数组中出现的次数 
	for(int i=1;i<N;i++) cnt[i]+=cnt[i-1];//求前缀和,统计c数组中小于等于i的值的个数 
	for(int i=0;i<n;i++) ccount[i]=cnt[N-1]-cnt[b[i]];//求c数组中大于b[i]的值的个数,即用:c数组成员个数-小于等于b[i]的值的个数 
	
	long long res=0;
	for(int i=0;i<n;i++) cout<<acount[i]<<" "<<ccount[i]<<endl,res+=(long long)acount[i]*ccount[i];//对于每一种b[i]的值,求有多少种合适的方案 
	
	cout<<res;
}

相比于使用三重循环加判断,这种思路大大减少了时间复杂度

思想:即用一个值来约束另外的2个变量

特别数的和

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;

const int N=10010;
int arr[N];

int main()
{	
	int n,res=0;
	cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		int t=i;
		while(t)
		{			
			if(t%10==1||t%10==2||t%10==0||t%10==9) 
			{
			    res+=i;
			    break;
			}
			t/=10;
		}
	}
	
	cout<<res;
}

错误票据

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

const int N=10010;
int n,arr[N];

int main()
{	
	int cnt;
	cin>>cnt;
	string line;
	
	getline(cin,line);
	while(cnt--)
	{
		getline(cin,line);
		stringstream ssin(line);
		
		while(ssin>>arr[n]) n++;
	}
    //while(cin>>arr[n]) n++;用这句就可以简单代替
	
	sort(arr,arr+n);
	int res1,res2;
	
	for(int i=1;i<n;i++)
	{
		if(arr[i]==arr[i-1]) res1=arr[i];
		else if(arr[i]-arr[i-1]==2) res2=arr[i]-1;
	}
	
	cout<<res2<<" "<<res1;
}
重点:使用sstream头文件里面的stringstream和getline来读入零散的输入的数字

回文日期

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

int Month[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};//方便查询每月的最大天数

bool Check(int date)
{
	int year=date/10000;
	int month=date/100%100;
	int day=date%100;
	
	if(month<=0||month>12) return 0;//月份越界
	if(month!=2&&day>Month[month]) return 0;//日期越界
	if(month==2)//对二月进行特殊处理
	{
		if(day>28+(year%4==0&&year%100||year%400==0)) return 0;//是否为闰年的判断条件
	}
	
	return 1;
}

int main()
{	
	int year1,year2,res=0;
	cin>>year1>>year2;
	
	for(int i=1000;i<=9999;i++)
	{
		int year=i,temp=i,cnt=4;
		while(cnt--) year=year*10+temp%10,temp/=10;//根据前四位得到回文数
		
		if(year1<=year&&year<=year2&&Check(year)) res++;
	}
	
	cout<<res;
}
思路转变:在日期范围内找回文数->从回文数中找合法的日期,可以大大减少代码的复杂度

移动距离

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

int main()
{	
	int w,m,n;
	cin>>w>>m>>n;
	
	int x1,y1,x2,y2;
	x1=m/w,x2=n/w;
	y1=m%w,y2=n%w;
	
	if(x1%2) y1=w-y1+1;
	if(x2%2) y2=w-y2-1;
	
	cout<<abs(x1-x2)+abs(y1-y2);
}

日期问题

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

int Month[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};

bool Check(int date)
{
	int year=date/10000;
	int month=date/100%100;
	int day=date%100;
	
	if(month<=0||month>12)  return 0;
	if(month!=2&&day>Month[month]||day<=0) return 0;
	if(month==2)
	{
		if(day>28+(year%100&&year%4==0||year%400==0)) return 0;
	}
	return 1;
}

int main()
{	
	int a,b,c;
	scanf("%d/%d/%d",&a,&b,&c);
	
	for(int date=19600101l;date<=20591231;date++)
	{
		int year=date/10000,month=date/100%100,day=date%100;
		if(Check(date))
		{
			if(year%100==a&&month==b&&day==c
			||month==a&&day==b&&year%100==c
			||day==a&&month==b&&year%100==c)
			
			printf("%d-%02d-%02d\n",year,month,day);
		}
	}
}

航班时间

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

int n;

int Get_second(int h,int m,int s)
{
	return h*3600+m*60+s;	
}

int Get_time()
{
	string line;
	getline(cin,line);
	if(line.back()!=')') line+=" (+0)";
	
	int h1,h2,m1,m2,s1,s2,d;
	sscanf(line.c_str(),"%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d);
	
	return Get_second(h2,m2,s2)-Get_second(h1,m1,s1)+d*24*3600;
}

int main()
{	
	cin>>n;
	string line;
	getline(cin,line);
	
	while(n--)
	{
		int time=(Get_time()+Get_time())/2;
		int hour=time/3600,minute=time%3600/60,second=time%60;
		printf("%02d:%02d:%02d\n",hour,minute,second);
	}
}
重点:如何接受输入的字符串以及对字符串的复杂处理

外卖店优先级

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef pair<int, int> PII;
#define x first
#define y second

int n, m, T;
int f[100010];//  f[i]表示店铺i的优先级
int st[100010];//  st[i]表示店铺i是否处于优先缓存
int last[100010];//  last[i]表示店铺i上一次有订单的时间 
PII order[100010];//  订单 

int main()
{
    scanf("%d%d%d", &n, &m, &T);
    for (int i = 0; i < m; i++)
    {
        scanf("%d %d", &order[i].x, &order[i].y);
    } 
    sort(order, order + m);
    for (int i = 0; i < m;)
    {
        int j = i;
        while (order[i] == order[j] && j < m) j++;//查看是否同时有多个相同店铺的订单
        int t = order[i].x, id = order[i].y, cnt = j - i;
        i = j;

        f[id] -= t - last[id] - 1;//当该店铺有订单时,开始统计店铺的权重,此时先处理减去的权重
        if (f[id] < 0) f[id] = 0;//权重不能为负
        if (f[id] <= 3) st[id] = 0;

        f[id] += cnt * 2;//处理要加上的权重
        if (f[id] > 5) st[id] = 1;

        last[id] = t;
    }

    for (int i = 1; i <= n; i++)
    {
        if (last[i] < T)//若某店铺从最后一次接订单不是在T时刻,那么需要减去权重
        {
            f[i] -= T - last[i];
            if (f[i] <= 3) st[i] = 0;
        }
    }

    int ans = 0;
    for (int i = 1; i <= n; i++) ans += st[i];
    cout << ans << endl;
    return 0;
}

树状数组与线段树

动态求连续区间和(重要思想)

解1:

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

const int N=100010;

int arr[N],tree[N];
int n,m;

int Lowbit(int x)
//求出下标为x的元素在树的第几层且距离其父/子节点的距离是多少
//比如x=12(0b1100),那么return 4(12的二进制后面有两个0,即处在树的第二层,所以返回二进制的100即返回4)
{
	return x&-x;
}

void Add(int x,int y)
//对树状数组进行修改操作,主要是修改下标为x的元素和其父节点(还有父父……节点)的元素
{
	for(int i=x;i<=n;i+=Lowbit(i)) tree[i]+=y;
}

int Sum(int x)
//求前缀和
{
	int res=0;
	for(int i=x;i>0;i-=Lowbit(i)) res+=tree[i];
	return res;
}

int main()
{	
	cin>>n>>m;
	
	for(int i=1;i<=n;i++) scanf("%d",&arr[i]);

	for(int i=1;i<=n;i++) Add(i,arr[i]);
    //将arr中的与元素添加到tree中,完成对树状数组tree的初始化
	
	while(m--)
	{
		int flag,x,y;
		cin>>flag>>x>>y;
		if(flag==0) cout<<Sum(y)-Sum(x-1)<<endl;
		else if(flag==1) Add(x,y);
	}
}
思想:树状数组的建立,动态对某元素值的修改,动态求区间和(前缀和)

解2:

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

const int N=100010;

struct Tree
{
	int l,r;
	int sum;
}tr[N*4];

int arr[N],n,m;

void Push_up(int u)//通过两个子节点把父节点的sum算出来 
{
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;//每次调用这个函数的时候,俩字节点的sum都已经有值了 
}

void Build(int u,int l,int r)//创建线段树 
//u是当前节点序号,l,r是想要创建树的数组的左右端点 
{
	if(l==r) tr[u]={l,r,arr[l]};//当递归到叶子节点了,那么对叶子节点的l,r,sum进行赋值 
	else//否则,其他非叶子节点只能先赋值l,r,然后通过之前得到的子节点sum来对其sum赋值 
	{
		tr[u]={l,r}; 
		int mid=l+r>>1;//通过此时节点的mid来判断 需要往左还是右子节点递归 
		Build(u<<1,l,mid);
		Build(u<<1|1,mid+1,r);
		Push_up(u);//对非叶节点赋sum 
	}
}

int Query(int u,int l,int r)//查询指定的某段序列的和
//u是当前节点序号,l,r是想要查询的序列的左右端点 
{
	if(l<=tr[u].l&&r>=tr[u].r) return tr[u].sum;//当当前节点两端都包含在想要查询的序列两端内时,返回这一部分sum 
	else
	{
		int sum=0,mid=tr[u].l+tr[u].r>>1;//通过mid来判断是需要往左还是右子节点递归 
		if(l<=mid) sum=Query(u<<1,l,r);
		if(r>mid) sum+=Query(u<<1|1,l,r);
		return sum;
	}
}

void Add(int u,int a,int b)//改变arr中的某值 
//u是当前节点序号,a是在数组中的序号,b是对该值的改变量 
{
	if(tr[u].l==tr[u].r) tr[u].sum+=b;//当递归到叶节点时,才能开始赋新值 
	else 
	{
		int mid=tr[u].l+tr[u].r>>1;//通过mid来判断是需要往左还是右子节点递归 
		if(a<=mid) Add(u<<1,a,b);
		else Add(u<<1|1,a,b);
		Push_up(u);//当对叶节点赋新值完成后,需要递归回去把线段树更新(即把该叶节点的父父父……节点更新)) 
	}
}

int main()
{	
	cin>>n>>m;
	int f,a,b;
	for(int i=1;i<=n;i++) scanf("%d",&arr[i]);
	Build(1,1,n);
	while(m--)
	{
		scanf("%d %d %d",&f,&a,&b);
		if(!f) cout<<Query(1,a,b)<<endl;
		else Add(1,a,b);
	}
}
思想:线段树(用法与树状数组类似)

数星星

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

const int N=100010;

int tree[N];
int n;
int level[N];

int Lowbit(int x)
{
	return x&-x;
}

void Add(int x)
{
	for(int i=x;i<=N;i+=Lowbit(i)) tree[i]++;
}

int Sum(int x)
{
	int res=0;
	for(int i=x;i>0;i-=Lowbit(i)) res+=tree[i];
	return res;
}

int main()
{	
	int x,y;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>x>>y;
		x++;//由于星星只会往右上方移动,所以我们可以只考虑x坐标
		level[Sum(x)]++;
		Add(x);
	}
	
	for(int i=0;i<n;i++) cout<<level[i]<<endl;
}

动态求连续区间和

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<sstream>
using namespace std;

const int N=100010;
typedef long long LL;

struct Tree
{
	int l,r;
	int maxv;
}tr[N*4];

int arr[N],n,m;
LL INF=2147483647;

void Build(int u,int l,int r)//创建线段树 
//u是当前节点序号,l,r是想要创建树的数组的左右端点 
{
	if(l==r) tr[u]={l,r,arr[l]};//当递归到叶子节点了,那么对叶子节点的l,r,max进行赋值 
	else//否则,其他非叶子节点只能先赋值l,r,然后通过之前得到的子节点max来对其max赋值 
	{
		tr[u]={l,r}; 
		int mid=l+r>>1;//通过此时节点的mid来判断 需要往左还是右子节点递归 
		Build(u<<1,l,mid);
		Build(u<<1|1,mid+1,r);
		tr[u].maxv=max(tr[u<<1].maxv,tr[u<<1|1].maxv);
	}
}

int Query(int u,int l,int r)
//u是当前节点序号,l,r是想要查询的序列的左右端点 
{
	if(l<=tr[u].l&&r>=tr[u].r) return tr[u].maxv;//当当前节点两端都包含在想要查询的序列两端内时,返回这一部分max 
	else
	{
		int maxv=-INF,mid=tr[u].l+tr[u].r>>1;//通过mid来判断是需要往左还是右子节点递归 
		if(l<=mid) maxv=Query(u<<1,l,r);
		if(r>mid) maxv=max(maxv,Query(u<<1|1,l,r));
		return maxv;
	}
}

int main()
{	
	cin>>n>>m;
	int a,b;
	for(int i=1;i<=n;i++) scanf("%d",&arr[i]);
	Build(1,1,n);
	while(m--)
	{
		scanf("%d %d",&a,&b);
		printf("%d\n",Query(1,a,b));
	}
}

本题就是线段树的变形,把线段树里面的sum成员改成了maxv

还有一点不同是,此题不需要更新数组,所以不用写Add()和Push_up()函数(Add函数会调用Push_up函数)

小朋友排队(P)

#include<iostream>
#include<cstring>
#define ll long long
using namespace std;

const int N = 1000010;

int n;
int tr[N];
int sum[N];
int h[N];

int lowbit(int x)
{
    return x&-x;
}

void add(int x,int w)
{
    for(int i=x;i<=N;i+=lowbit(i))tr[i]+=w;
}

ll solve(int x)
{
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i))ans+=tr[i];
    return ans;
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)cin>>h[i],h[i]++; //可能有身高为0的小朋友(你小子真是二刺螈啊)

    //在某数前面比某数大的数
    for(int i=0;i<n;i++)
    {
        sum[i]+=solve(N-1)-solve(h[i]); //计算更大的数个数当然是更大数总数减更小数总数啦
        add(h[i],1);    //存入,权值为1
    }

    memset(tr,0,sizeof tr);

    //在某数后面然而更小的数
    for(int i=n-1;i>=0;i--)
    {
        sum[i]+=solve(h[i]-1);
        add(h[i],1);
    }

    ll ans=0;
    for(int i=0;i<n;i++)ans+=sum[i]*1ll*(sum[i]+1)/2;   //你好,高斯

    cout<<ans<<'\n';

    return 0;
}

油漆面积(P)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

const int N = 10010;

int n;

struct Segment
{
    int x, y1, y2; //x为横坐标,y1,y2为线段树维护的两个纵坐标
    int k; //k表示我们要加的权值,为+1或-1
    //由于要把所有的线段按照横坐标排序,所以要重载小于号
    bool operator< (const Segment &t)const
    {
        return x < t.x;
    }
}seg[N * 2]; //线段最多有两倍(每个矩形两条竖边)

struct Node
{
    int l, r;
    int cnt, len;
}tr[N * 4];

void pushup(int u) //用子节点的信息更新父结点的信息
{
    if(tr[u].cnt > 0) tr[u].len = tr[u].r - tr[u].l + 1;
    else if(tr[u].l == tr[u].r) tr[u].len = 0;
    else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0}; //cnt和len默认为0
    if(l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

void modify(int u, int l, int r, int k) //维护的是一段小段区间的值,k为要在这个区间加上或减去的权值
{
    //当前区间已经被完全包含
    if(tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].cnt += k;
        pushup(u);
    }
    else //往下更新
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, k);
        if(r > mid) modify(u << 1 | 1, l, r, k);
        pushup(u);
    }
}

int main()
{
    int n;
    scanf("%d", &n);
    int m = 0; //表示当前线段的数量
    for(int i = 0; i < n; i ++ )
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        seg[m ++ ] = {x1, y1, y2, 1}; //矩形左竖边
        seg[m ++ ] = {x2, y1, y2, -1}; //矩形右竖边
    }

    sort(seg, seg + m); //给所有线段排序

    build(1, 0, 10000);

    int res = 0;
    for(int i = 0; i < m; i ++ ) //遍历所有线段
    {
        if(i > 0) //不能是第一条线,需要等第二条过来才能算面积
        {
            res += tr[1].len * (seg[i].x - seg[i - 1].x); //根结点的len * 阴影部分高度
        }
        modify(1, seg[i].y1, seg[i].y2 - 1, seg[i].k); //维护的是一小段区间
    }

    printf("%d\n", res);
    return 0;
}

三体攻击(P)

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 2000010;
int A, B, C, m;
LL s[N];  // 原数组
LL b[N], bp[N];  // 差分数组
int d[8][4] = {  // 差分数组和原数组 相互转换 使用到的偏移量和系数
    {0, 0, 0, 1},
    {0, 0, 1, -1},
    {0, 1, 0, -1},
    {0, 1, 1, 1},
    {1, 0, 0, -1},
    {1, 0, 1, 1},
    {1, 1, 0, 1},
    {1, 1, 1, -1},
};
int op[N / 2][7];
int get(int i, int j, int k) 
{
    return (i * B + j) * C + k;
}
bool check(int mid) 
{
    memcpy(b, bp, sizeof bp);
    for (int i = 1; i <= mid; i++) 
    {
        // 给(x1, y1, z1), (x2, y2, z2)之间加上c
        int x1 = op[i][0], x2 = op[i][1];
        int y1 = op[i][2], y2 = op[i][3];
        int z1 = op[i][4], z2 = op[i][5];
        int c = -op[i][6];
        b[get(x1    , y1    , z1)]      += c;
        b[get(x1    , y1    , z2 + 1)]  -= c;
        b[get(x1    , y2 + 1, z1)]      -= c;
        b[get(x1    , y2 + 1, z2 + 1)]  += c;
        b[get(x2 + 1, y1    , z1)]      -= c;
        b[get(x2 + 1, y1    , z2 + 1)]  += c;
        b[get(x2 + 1, y2 + 1, z1)]      += c;
        b[get(x2 + 1, y2 + 1, z2 + 1)]  -= c;
    }
    memset(s, 0, sizeof s);
    for (int i = 1; i <= A; i++)
        for (int j = 1; j <= B; j++)
            for (int k = 1; k <= C; k++) 
            {
                s[get(i, j, k)] += b[get(i, j, k)];
                for (int u = 1; u < 8; u++) 
                {
                    int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                    s[get(i, j, k)] -= s[get(x, y, z)] * t;
                }
                if (s[get(i, j, k)] < 0) return true;
            }
    return false;
}
int main() {
    // 读取原数组
    scanf("%d%d%d%d", &A, &B, &C, &m);
    for (int i = 1; i <= A; i++)
        for (int j = 1; j <= B; j++)
            for (int k = 1; k <= C; k++)
                scanf("%lld", &s[get(i, j, k)]);
    // 根据原数组s计算差分数组
    for (int i = 1; i <= A; i++)
        for (int j = 1; j <= B; j++)
            for (int k = 1; k <= C; k++)
                for (int u = 0; u < 8; u++) 
                {
                    int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                    bp[get(i, j, k)] += s[get(x, y, z)] * t;
                }
    // 读取操作
    for (int i = 1; i <= m; i++)
        for (int j = 0; j < 7; j++)
            scanf("%d", &op[i][j]);
    // 二分答案
    int l = 1, r = m;
    while (l < r) 
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%d\n", r);
    return 0;
}

双指针,BFS和图论

螺旋折线

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

int main()
{
    LL x, y;
    cin >> x >> y;
    //1.寻找点所在的层数
    //-------
    LL k = max(abs(x) , abs(y) );   //注意先取绝对值,然后取max,若是负数取max之后,绝对值是小的,对应层数就错误了
    //-------
    //找规律发现 y = x对角线的步数恰好是 4 * x^2 (x 和 y相等,此处写哪个都可以)
    //2.根据在中间节点的前后,判断前进还是后退。(以y=x直线作为划分)
    LL res = 0;
    if(x >= y) //对角线的下半部分,加上距离对角点的曼哈顿距离
        res = 4 * k * k + abs(k - x) + abs(k - y); //前进
    else   
        res = 4 * k * k - abs(k - x) - abs(k - y); //后退

    cout << res << endl;
    return 0;
}

找规律题

日志统计

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;   //根据题目数据范围,定N大小,此题则主要根据N,ts,id来确定的
typedef pair<int, int> PII; //定义一个PII类型以及一对int整型用于存储ts和id

#define x first //为了代码简洁,则分别把first, second 宏定义为x, y
#define y second
int n, d, k;
int cnt[N]; //用于存储每个id号获得赞数,所以后面代码为cnt[t] ++;
bool st[N]; //用于存储每个帖子是否是热帖,所以用bool类型
PII logs[N];//用于存储日志ts, id

int main()
{
    scanf("%d %d %d", &n, &d, &k);
    for(int i = 0; i < n; i ++) scanf("%d %d\n", &logs[i].first, &logs[i].second);

    sort(logs, logs + n);

    for(int i = 0, j = 0; i < n; i ++)//双指针算法,i在前,j在后
    {
        int t = logs[i].second;//把每个获赞的帖子id存入t
        cnt[t] ++;//获得一个赞,所以此刻 ++;
        while(logs[i].first - logs[j].first >= d)//如果俩个帖子时间相差超过d,说明该赞无效
        {
            cnt[logs[j].second] --;//所以此刻--;
            j ++;//要把指针j往后,否则死循环
        }
        if(cnt[t] >= k) st[t] = true; //如果该id贴赞超过k,说明是热帖
    }
    for(int i = 0; i < 100000; i ++)
    {
        if(st[i])//如果为真,则把热帖的id打印出来
            cout << i << endl;
    }

    return 0;
}

献给阿尔吉侬的花束

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>

#define x first
#define y second

using namespace std;

const int N = 210;
typedef pair<int, int> PII;

int n, m;
char g[N][N];
int dist[N][N];  // 把判重和距离数组合为一个

int bfs(PII start, PII end)  // 注意 start end都是PII类型的 别写错了
{
    queue<PII> q;
    memset(dist, -1, sizeof dist);  // 把距离数组都初始化成-1
    dist[start.x][start.y] = 0;  // 起点开始,距离为0

    q.push(start);  // 起点 入队

    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    while(q.size())
    {
        PII t = q.front();

        // 弹出队头
        q.pop();

        for (int i = 0; i < 4; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i]; 

            if (a < 0 || a >= n || b < 0 || b >= m) continue;  // 出界,跳出本次循环
            if (dist[a][b] != -1) continue;    // 走过了,跳出本次循环
            if (g[a][b] == '#') continue;    // 撞到障碍物

            dist[a][b] = dist[t.x][t.y] + 1;

            if (end == make_pair(a, b)) return dist[a][b];  // 走到终点了,返回距离

            q.push({a, b});
        }

    }

    return -1;
}

int main()
{
    int T;
    cin >> T;

    while(T --)
    {

        scanf("%d%d", &n, &m);

        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);

        PII start, end;
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                if (g[i][j] == 'S') start = {i, j};
                else if (g[i][j] == 'E') end = {i, j};

        int distance = bfs(start, end);

        if (distance == -1) printf("oop!\n");
        else printf("%d\n", distance);

    }

    return 0;
}

交换瓶子

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1e5+5;
int a[N];
bool st[N];

int main(){
    int n;
    cin >> n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int cnt=0;

    for(int i=1;i<=n;i++)
    {
        if(!st[i]){
            cnt++;

            for(int j=i;!st[j];j=a[j])//位置j指向a[j]的元素
            {
                st[j]=true;
            }
        }
    }

    cout << n-cnt;
}

完全二叉树的权值

#include <iostream>

using namespace std;
const int N = 100010;

long long q[N];

int main() {
    int n;
    cin >> n;

    for(int i = 1; i <=n; i++) cin >> q[i];
    int max_ = -1e18;

    /*
    完全二叉树 每层的开头为   2^(n-1) 结尾则是 2^n - 1
    计算每层的数值只需要两个positioner 分别指向开头和结尾
    */

    int depth = 1;
    int res = 1;

    for(int i = 1; i <= n; i *= 2){
        long long s = 0;
        for(int j = i;  j <=  i*2 - 1 && j <= n; j ++) s += q[j];

        if(s > max_) 
        {
            max_ = s;
            res = depth;
        }

        depth++;
    }

    cout << res;
}

地牢大师

#include <bits/stdc++.h>
using namespace std;

const int N=110;

int l,r,c;  //迷宫参数
int px,py,pz,ex,ey,ez;  //pi为S的位置,ei为E的位置

char mp[N][N][N];  //记录迷宫
int ans[N][N][N];  //存储答案
bool vis[N][N][N];  //记录该点是否走过的状态

struct point{  //点的坐标
    int x,y,z;
};

queue<point> st;  //搜索队列

int dx[]={1,-1,0,0,0,0},dy[]={0,0,1,-1,0,0},dz[]={0,0,0,0,1,-1};  //偏移量数组

int bfs()
{
    while(!st.empty())
    {  //当队头不为空时,扩展搜索队头
        auto p=st.front();

        for(int i=0;i<6;i++)
        {
            int m_x=p.x+dx[i],m_y=p.y+dy[i],m_z=p.z+dz[i];  //偏移之后的点的坐标

            if(m_x<=l&&m_y<=r&&m_z<=c&&m_x>=1&&m_y>=1&&m_z>=1&&!vis[m_x][m_y]                                                    
              [m_z]&&mp[m_x][m_y][m_z]!='#')
            {  //判断条件
                vis[m_x][m_y][m_z]=1;  //更新该点走过的状态
                ans[m_x][m_y][m_z]=ans[p.x][p.y][p.z]+1;  //更新偏移后的点距离S的步骤
                if(mp[m_x][m_y][m_z]=='E') return  ans[m_x][m_y][m_z];  //搜到E直接返回答案
                st.push({m_x,m_y,m_z});  //将该点入队,继续扩展搜索
            }
        }

        st.pop();  //队头扩展搜索完毕后出队
    }

    return 0;  //所有的点扩展搜索完后若还未返回搜到E,说明无解
}

int main()
{
    while(cin>>l>>r>>c&&l&&r&&c)
    {  //多实例读入
        //还原数据
        memset(ans,0,sizeof(ans)); 
        memset(mp,0,sizeof(mp));
        memset(vis,0,sizeof(vis));

        while(st.size())
        {
            st.pop();
   
        //读入迷宫
        for(int i=1;i<=l;i++)
        {
            for(int j=1;j<=r;j++)
            {
                for(int k=1;k<=c;k++)
                {
                    cin>>mp[i][j][k];

                    if(mp[i][j][k]=='S') px=i,py=j,pz=k;  
                    if(mp[i][j][k]=='E') ex=i,ey=j,ez=k;  
                }
            }
        }

        vis[px][py][pz]=1;  //标记S已经走过

        st.push({px,py,pz}); //S点入队

        int cnt=bfs();  //调用搜索,将从S点开始搜索

        if(cnt!=0) printf("Escaped in %d minute(s).\n",cnt);
        else cout<<"Trapped!"<<endl;
    }

    return 0;
}

全球变暖

#include<bits/stdc++.h>
using namespace std;

#define x first
#define y second

const int N = 1010;

typedef pair<int, int> PII;

int n;
char g[N][N];
bool st[N][N], under[N][N];
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
int ans;

void bfs(int x, int y) 
{

    queue<PII> q;
    q.push({x, y});
    st[x][y] = true;

    int sum = 1, down = 0;
    while (q.size()) 
    {
        PII t = q.front();
        q.pop();

        bool is_bound = false;

        for (int i = 0; i < 4; i ++ ) 
        {
            int x = t.x + dx[i], y = t.y + dy[i];

            if (x < 0 || y < 0 || x >= n || y >= n) continue;
            if (st[x][y]) continue;

            if (g[x][y] == '.')
             {
                is_bound = true;
                continue;
            }

            sum ++ ;
            st[x][y] = true;
            q.push({x, y});
        }

        if (is_bound) down ++ ;
    }

    if (down >= sum) ans ++ ;
}

int main() {
    scanf("%d", &n);

    for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);

    for (int i = 1; i < n; i ++ ) 
        for (int j = 1; j < n; j ++ ) 
            if (g[i][j] == '#' && !st[i][j]) bfs(i, j);

    cout << ans << endl;

    return 0;
}

大臣的旅费

#include <iostream>
#include <vector>

using namespace std;

const int N = 100010;

int n;
struct Node
{
    int id,w;
};
vector<Node> h[N];
int dist[N];

void dfs(int u,int father,int distance)
{
    dist[u] = distance;

    for(auto node : h[u])
        if(node.id != father)
            dfs(node.id, u, distance + node.w);
}

int main()
{
    cin >> n;
    for(int i = 0;i < n - 1;i ++)
    {
        int a,b,w;
        cin >> a >> b >> w;
        h[a].push_back({b,w}); // 无向图建双向边
        h[b].push_back({a,w});
    }

    dfs(1,-1,0);

    int u = 1;
    for(int i = 1;i <= n; i++ )
        if(dist[i] > dist[u])
            u = i;

    dfs(u,-1,0);

    for(int i = 1;i <= n; i++ )
        if(dist[i] > dist[u])
            u = i;

    int s = dist[u]; // s就是树的直径
    printf("%lld\n",s * 10 + s * (s + 1ll) / 2); // 注意最坏情况下爆int

    return 0;
}

贪心

股票买卖

#include<iostream>
using namespace std;
const int maxn = 100010;
int f[maxn];

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;++i) cin>>f[i];

    int ans = 0;
    for(int i=0;i<n-1;++i)
    {
        ans += max(0,f[i+1]-f[i]);
    }

    cout<<ans<<endl;
}

货仓选址

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std ;

typedef long long LL ;
const int N = 100010 ;

int q[N] ;
int n ;

int main(){
    cin >> n ;

    for(int i=1;i<=n;i++){
        cin >> q[i] ;
    }

    sort(q+1,q+1+n) ;
    LL res = 0 ;

    for(int i=1;i<=n;i++){
        res += abs(q[i]-q[(n+1)/2]) ;
    }

    cout << res << endl ;

    return 0 ;
}

糖果传递

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1000010;

int n;
int a[N];
LL c[N];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);

    LL sum = 0;
    for (int i = 1; i <= n; i++) sum += a[i];

    LL avg = sum / n;
    for (int i = n; i > 1; i--) {
        c[i] = c[i + 1] + avg - a[i];
    }
    c[1] = 0;

    sort(c + 1, c + n + 1); // 将数轴上所有点排序

    LL res = 0;
    for (int i = 1; i <= n; i++) res += abs(c[i] - c[(n + 1) / 2]); //距离和

    printf("%lld\n", res);

    return 0;
}

雷达设备

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

typedef pair<double, double> PDD;
const int N = 1010;
const double eps = 1e-6, INF = 1e10;

int n, d;
PDD seg[N];

int main()
{
    cin >> n >> d;

    bool success = true;

    for (int i = 0; i < n; i ++ )
    {
        int x, y;
        cin >> x >> y;
        if (y > d)
        {
            success = false;
            break;
        }
        auto len = sqrt(d * d - y * y);
        seg[i] = {x + len, x - len};
    }

    if (!success) puts("-1");
    else
    {
        sort(seg, seg + n);
        int res = 0;
        double last = -INF;
        for (int i = 0; i < n; i ++ )
        {
            if (seg[i].second > last + eps)
            {
                res ++ ;
                last = seg[i].first;
            }
        }
        cout << res << endl;
    }
    return 0;
}

付账问题

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

int n;
double s;
int a[500010];

int main()
{
    scanf("%d %lf", &n, &s);
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);

    sort(a, a + n);

    double ave = s / n, sum = 0;
    for (int i = 0; i < n; i++)
    {
        double cur=s/(n-i);
        if (a[i] < cur) cur=a[i];
        
        s -= cur;
        sum += (cur - ave) * (cur - ave); 
    }

    printf("%.4lf\n", sqrt(sum / n));
    return 0;
} 

乘积最大

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long LL ;
const int N = 100010 , mod = 1000000009 ;
int a[N];


int main()
{
    int n , k ;
    scanf("%d%d",&n,&k);
    for(int i = 0 ; i < n ; i ++) scanf("%d",&a[i]);

    sort(a,a + n);

    LL res = 1 ; //乘积初始化
    int l = 0 , r = n - 1 ;//双指针初始化
    int sign = 1 ; // 符号初始化

    //由于4种情况除了 k 是奇数且 k < n 的时候需要特判一下处理一下符号 ,其他的时候都可以转化为双指针做法
    //k 是奇数是先选出最大的数, k-- 就是偶数,两边再同时取对,转化成相同的双指针做法
    if(k % 2 )
    {
        res =  a[r--]; // 取出最大的一个数,右指针移动
        k -- ; //个数减1

        if(res < 0) sign = -1;  // 如果最大值都是负数,就证明全是负数,那么符号要发生改变
    }
    while(k) // 双指针做法
    {
        LL x = (LL)a[l] * a[l + 1] , y = (LL)a[r] * a[r - 1];//两边同时取对
        //选择更大的一对,和归并排序思路相近
        if(x * sign > y * sign)
        {
            res = x % mod * res % mod; // 需要注意的是 :不可以写成(x * res) % mod ,也不可以写成是 res % mod * x % mod
                                       // 因为x最大是 10^10,如果不先取模的话,和res相乘的结果最大是 10^19,会暴long long。            
            l += 2; // 指针移动                                 
        }
        else 
        {
            res = y % mod * res % mod; 
            r -= 2; 
        }
        k -= 2; 
    }
    printf("%lld",res);
    return 0;
}

后缀表达式

#include<iostream>
#include<algorithm>

using namespace std;

const int N=2E5+10;//因为数据总量实际是M+N+1
int a[N];

int main()
{
    int m,n;
    cin>>n>>m;
    int k = m+n+1;

    for(int i=0;i<k;i++)cin>>a[i];//读入数据
    sort(a,a+k);

    long long res=0;
    if(!m)for(int i=0;i<k;i++)res+=a[i];//如果没有-号
    else 
    {
        res=a[k-1]-a[0];
        for(int i=1;i<k-1;i++)res+=abs(a[i]);
    }

    cout<<res;
    return 0;
}

灵能传输(P)

#include "bits/stdc++.h"

using namespace std;

typedef long long LL;

const int N = 3e5 + 10;

int t, n;
LL s[N], a[N]; // s为前缀和数组 a为存放前缀和顺序的数组
bool st[N];

int main()
{
    cin >> t;
    while (t--)
    {
        cin >> n;
        memset(st, 0, sizeof st);
        for (int i = 1; i <= n; i++)
            cin >> s[i], s[i] += s[i - 1];
        LL s0 = s[0], sn = s[n];
        if (s0 > sn)
            swap(s0, sn);
        sort(s, s + n + 1);
        // 寻找排完序后s0和sn的位置
        // 如果s0和sn相同的话则前面的为s0 后面的为sn
        for (int i = 0; i <= n; i++)
            if (s[i] == s0)
            {
                s0 = i;
                break;
            }
        for (int i = n; i >= 0; i--)
            if (s[i] == sn)
            {
                sn = i;
                break;
            }
        int l = 0, r = n;
        for (int i = s0; i >= 0; i -= 2)
        {
            a[l++] = s[i];
            st[i] = 1;
        }
        for (int i = sn; i <= n; i += 2)
        {
            a[r--] = s[i];
            st[i] = 1;
        }
        for (int i = 0; i <= n; i++)
            if (!st[i])
                a[l++] = s[i];
        LL res = 0;
        for (int i = 1; i <= n; i++)
            res = max(res, abs(a[i] - a[i - 1]));
        cout << res << endl;
    }
    return 0;
}

数论

等差数列

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010;

int a[N];

//求最大公约数
int gcd(int a, int b){
    return b ? gcd(b, a % b) : a;
}

int main(){
    int n;
    scanf("%d", &n);

    for(int i = 0; i < n; i ++){
        scanf("%d", &a[i]);
    }

    sort(a, a + n);

    //求后面各项与第一项所有差值的最大公约数
    int d = 0;
    for(int i = 1; i < n; i ++) d = gcd(d, a[i] - a[0]);

    if(!d) printf("%d\n", n);
    else printf("%d\n", (a[n - 1] - a[0]) / d + 1);

    return 0;
}
用到的数论知识:辗转相除法

辗转相除法(gcd)求两个数的最大公约数_gcd辗转相除法-CSDN博客

算法:辗转相除算法

X的因子链

#include<cstdio>

typedef long long ll;

const int N = (1 << 20) + 10;

int primes[N];// 存质数
int min_p[N]; //存最小质因子
int cnt;
bool st[N]; // 表示对应元素是否被筛过

// 线性筛法(欧拉筛法)
void get_primes(int n) 
{
    for (int i = 2; i <= n; i++) 
    {
        if (!st[i]) 
        {
            min_p[i] = i;
            primes[++cnt] = i;
        }
        for (int j = 1; primes[j] * i <= n; j++) 
        {
            int t = primes[j] * i;
            st[t] = true; //标记合数
            min_p[t] = primes[j];

            if (i % primes[j] == 0) 
            {
                //如果i是前面某个素数的倍数时, 说明i以后会由某个更大的数乘这个小素数筛去
                //同理, 之后的筛数也是没有必要的, 因此在这个时候, 就可以跳出循环了
                break;
            }
        }
    }
}
int sum[N]; //记录每个质因子出现的次数

int main()
 {
    get_primes(N);
    int x;

    while (scanf("%d", &x) != EOF) 
    {
        //tol用于记录最大长度,k表示第i个质因子的下标, 从0开始
        int k = 0, tol = 0;

        // 依次处理各个质因子, 求出对应质因子出现的次数
        while (x > 1)
        {
            int p = min_p[x]; // 通过while, 依次取出最小质因子
            sum[k] = 0;

            //处理当前质因子, 求其出现的次数
            while (x % p == 0) 
            {
                sum[k]++;
                tol++;
                x /= p;
            }

            k++; // 准备处理下一个质因子
            /*
             例:
                x=12 --> 3*2^2
                p = 2
                sum[1]=1
                x=6

                sum[1]=2
                x=3
                ****************
                p=3
                sum[2]=1
                x=1 --> 结束
             */
        }

        //求所有质因子出现总次数的全排列
        ll res = 1;
        for (int i = 2; i <= tol; i++)  res *= i; // tol!
        //去除各个质因子重复出现的次数
        for (int i = 0; i < k; i++)
            for (int j = 1; j <= sum[i]; j++) res /= j;

        printf("%d %lld\n", tol, res); //输出最长序列的长度, 以及满足最大长度的序列的个数
    }

    return 0;
}
用到的数论知识:算数基本定理

算术基本定理 - gongpixin - 博客园 (cnblogs.com)

算法:线性筛法

 聪明的燕姿

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 50000;//N = sqrt(2e9)
bool st[N];
int primes[N],cnt = 0;//线性筛
int S,res[N],len;

void get_primes(int n)
{
    for(int i = 2;i <= n;i++)
    {
        if(!st[i]) primes[cnt++] = i;
        for(int j = 0;primes[j]*i <= n;j++)
        {
            st[primes[j]*i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

int is_prime(int n)
{
    if(n < N) return !st[n];//没有被筛过说明就是质数,返回true
    for(int i = 0;primes[i] <= n / primes[i];i++)
    {
        if(n % primes[i] == 0) return false;
    }
    return true;
}

void dfs(int last,int product,int S)//last表示上一个用的质数的下标是什么,product当前最高次项的结果,S表示每次处理后剩余多少
{
    if(S == 1)
    {
        res[len++] = product;
        return ;
    }
    //比如20 = 2^2 * 5
    //N = P1^a1 * P2^a2 * ... * Pn^an
    //S = (1+p1+p1^2+...+p1^a1)(1+p2+p2^2+...+p2^a2)...(1+pn+pn^2+...+pn^an)
    //42 = (1 + 2 + 2^2)*(1 + 5),其中2^2和5就分别是最高次项p1^2*p2^1
    if(S-1 > ((last < 0) ? 0 : primes[last]) && is_prime(S-1)) 
        res[len++] = product * (S-1);

    for(int i = last+1;primes[i] <= S / primes[i];i++)
    {
        int p = primes[i];

        for(int j = 1+p,t = p;j <= S;t *= p,j += t)
        {
            if(S % j == 0) dfs(i,product*t,S/j);
        }
    }
}

int main()
{
    get_primes(N-1);
    while(cin>>S)
    {
        len = 0;
        dfs(-1,1,S);

        sort(res,res+len);

        cout << len << endl;
        if (len)
        {
            sort(res, res + len);
            for (int i = 0; i < len; i ++ ) cout << res[i] << ' ';
            cout << endl;
        }
    }
    return 0;
}

五指山

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

typedef long long LL;

LL exgcd(LL a,LL b,LL &x,LL &y)//拓展欧几里得算法
{
    if(!b)
    {
        x = 1,y = 0;
        return a;
    }

    int d = exgcd(b,a%b,y,x);
    y -= a/b*x;
    return d;
}

int main()
{
    int T;
    scanf("%d",&T);

    while(T--)
    {
        LL n,d,x,y,a,b;
        scanf("%lld%lld%lld%lld",&n,&d,&x,&y);

        int gcd = exgcd(n,d,a,b);

        if((y-x)%gcd)
        {
            printf("Impossible\n");
        }
        else
        {
            b*=(y-x)/gcd;
            n/=gcd;
            printf("%lld\n",(b%n+n)%n);
        }
    }

    return 0;
}
用到的数论知识:裴蜀定理(扩展欧几里得定理)

数论基础——扩展欧几里得【详细】_欧几里得引理-CSDN博客

算法:扩展欧几里得算法

最大比例(P)

#include<iostream>
#include<algorithm>

using namespace std;

const int N=110;

typedef long long LL;

LL x[N],a[N],b[N];
int cnt=0;

//假设原数列为 a,a*(p/q)^1,a*(p/q)^2,...,a*(p/q)^(n-1)
//假设抽取的数列  b0,b1,...,b(N-1)  (从小到大排好序了)
//  b1/b0,b2/b0,...,b(N-1)/b0-->  (p/q)^x1,(p/q)^x2,...,(p/q)^x(N-1)
//  我们要求的是:  (p/q)^k  (p/q)>1,所以使k最大,即求 x1~x(N-1)的最大公约数
//这里我们使用更相减损术,因为我们没有得到确切的x1~x(N-1)是多少,我们只有(p/q)^x1,(p/q)^x2,...,(p/q)^x(N-1)这些的值

/*更相减损术:第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2的积与第二步中等数的乘积就是所求的最大公约数。*/

//更相减损术总用较大的减去较小的
/*例子:
    98-63=35
    63-35=28
    35-28=7
    28-7=21
    21-7=14
    14-7=7
所以,98和63的最大公约数等于7。*/

//我们这里要用更相减损术的是指数,所以要让(p/q)^x1,(p/q)^x2,...,(p/q)^x(N-1),两两计算,互除,除到结果为1,即x1=x2,此时幂次为0,结果为1,这其实就是y总的思路,再次感叹y总的才华
//把分子分母分别去算,结果是相同的因为,分子分母的幂次是相同的
LL gcd(LL a,LL b)
{
    return b? gcd(b,a%b):a;
}

LL gcd_sub(LL a,LL b)  
{
    if(a<b)  swap(a,b);  //更相减损术总是大减小(它们的底数是一样的)
    if(b==1)  return a;
    return gcd_sub(b,a/b);
}

int main()
{
    int n;
    cin>>n;

    for(int i=0; i<n; i++)  scanf("%lld",&x[i]);

    sort(x,x+n);

    for(int i=1; i<n; i++)
    {
        if(x[i] != x[i-1])
        {
            LL d=gcd(x[i],x[0]);  
            a[cnt]=x[i] / d;  //得到x[i]/x[0]的分子
            b[cnt]=x[0] / d;  //得到x[i]/x[0]的分母
            cnt++;
        }
    }

    LL up=a[0],down=b[0];

    for(int i=1; i<cnt; i++)
    {
        up=gcd_sub(up,a[i]);  //两两求最大公约数
        down=gcd_sub(down,b[i]);
    }

    cout<<up<<"/"<<down<<endl;

  
}
用到的数论知识:更相减损术,欧几里得算法

C语言 用更相减损术求最大公约数,最小公倍数_最小公倍数c语言 更像减损-CSDN博客

C循环(P)

#include <iostream>
#include <cstdio>
using namespace std;

typedef long long LL;

LL exgcd (LL &x,LL &y,LL a,LL b)
{
    if (!b) 
    {
        x = 1,y = 0;
        return a;
    }

    LL d = exgcd (y,x,b,a % b);
    y -= a / b * x;

    return d;
}

int main () 
{
    LL a,b,c,k;
    while (cin >> a >> b >> c >> k,a || b || c || k) 
    { //注意要用或者,否则会提前退出的,(POJ没有样例,acwing看到数据才知道)
        LL x,y;
        LL gcd = exgcd (x,y,c,1ll << k);
        LL mod = (1ll << k) / gcd;

        if ((b - a) % gcd) puts ("FOREVER");
        else cout << ((x * (b - a) / gcd) % mod + mod) % mod << endl;
    }

    return 0;
}
用到的数论知识:扩展欧几里得算法

正则问题(P)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std ;

int k ;
string str ;

int dfs()
{
    int res = 0 ;

    while(k<str.size())
    {
        if(str[k] == '(')
        {
            k ++ ;
            res += dfs() ;
            k ++ ;
        }
        else if(str[k] == ')')
        {
            // k ++ ;
            break ;
        }
        else if(str[k] == '|')
        {
            k ++ ;
            res = max(res,dfs()) ;
        }
        else
        {
            k ++ ;
            res ++ ;
        }
    }

    return res ;
}

int main()
{
    cin >> str ;

    cout << dfs() << endl ;

    return 0 ;

}

糖果(P)

//时间复杂度为O(n * 2^m) 为1e8勉强能过
//状态表示:dp[i][s]为从前i个糖果中选,选出来的糖果种类状态为s的所需最少糖果数量
//阶段划分:类似于01背包问题中,从前i个物品中选
//转移方程:划分依据:对于第i个物品选还是不选
//dp[i][s] = min(dp[i-1][s],dp[i-1][s & (~w[i])] + 1) 
//dp[i-1][s]好说 表示不选第i个物品
//关键是dp[i-1][s & (~w[i])],举个例子若现在s为 11011,表示已经选出来的糖果种类为1,2,8,16
//假设第i包糖果的状态为01011,那么他是从10000这个状态转移过来的
//那么s & (~w[i])的目的就是先将w[i]的糖果种类去掉,~w[i]按位取非,在与s相于就行了,实质上就是s & (w[i]在s中的补集)

//边界:dp初始化为INF dp[0][0] = 0;
//目标:dp[n][(1<<m)-1]
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110,M = 1 << 20,INF = 0x3f3f3f3f;
int w[N],dp[M],n,m,k;//注意优化成一维不然暴空间
int main(void)
{
    cin>>n>>m>>k;
    for(int i = 1;i<=n;i++) 
        for(int j = 1;j<=k;j++)
        {
            int num;
            cin>>num;
            w[i] |= (1 << num - 1);//将每一件糖果所包含的糖果种类压缩
        }

    memset(dp,0x3f,sizeof dp);

    dp[0] = 0;

    for(int i = 1;i<=n;i++)
        for(int s = 0;s < (1 << m);s ++) dp[s] = min(dp[s],dp[s & (~w[i])] + 1);

    if(dp[(1<<m)-1] == INF) cout<<-1<<endl;
    else cout<<dp[(1<<m) - 1]<<endl;

    return 0;
}

复杂DP

背包九讲

背包问题汇总参考:背包九讲——全篇详细理解与代码实现-CSDN博客

01背包

最基础DP解法:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010;
int w[N], v[N];
int n, m;
int x[N][N];

int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++) scanf("%d %d", &v[i], &w[i]);

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			x[i][j] = x[i - 1][j];
			if (j >= v[i]) x[i][j] = max(x[i][j], x[i - 1][j - v[i]] + w[i]);
		}
	}

	cout << x[n][m];
}

空间优化:把结果数组x[N][N]从二维数组优化为一维数组

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010;
int w[N], v[N];
int n, m;
int x[N];

int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++) scanf("%d %d", &v[i], &w[i]);

	for (int i = 1; i <= n; i++)
		for (int j = m; j >= v[i]; j--)
			x[j] = max(x[j], x[j - v[i]] + w[i]);

	cout << x[m];
}

思路参考: AcWing 2. 01背包问题 - AcWing

完全背包

最基础DP解法:(数字太大会超时)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010;
int w[N], v[N];
int n, m;
int x[N][N];

int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++) scanf("%d %d", &v[i], &w[i]);

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			for(int k = 0; k <= j/v[i]; k++)
			    x[i][j] = max(x[i][j], x[i - 1][j - k * v[i]] + k * w[i]);

	cout << x[n][m];
}

时间空间优化:省去第三重循环,把结果数组x[N][N]从二维数组优化为一维数组

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010;
int w[N], v[N];
int n, m;
int x[N];

int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++) scanf("%d %d", &v[i], &w[i]);

	for (int i = 1; i <= n; i++)
		for (int j = v[i]; j <= m; j++)
				x[j] = max(x[j], x[j - v[i]] + w[i]);

	cout << x[m];
}

思路参考:AcWing 3. 完全背包问题(逐步优化) - AcWing

多重背包1

数据范围很小,不需做优化,只需要在完全背包代码上加一层判断条件

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010;
int w[N], v[N], s[N];
int n, m;
int x[N][N];

int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++) scanf("%d %d %d", &v[i], &w[i], &s[i]);

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			for (int k = 0; k <= j / v[i] && k <= s[i]; k++)
				x[i][j] = max(x[i][j], x[i - 1][j - k * v[i]] + k * w[i]);

	cout << x[n][m];
}
多重背包2

数据范围变大,需要进行二进制优化以缩短时间复杂度

优化思路是将多重背包问题转化为01背包问题(将s[i]个第i个物品变为排列开的1,2,4,8,16……个第i个物品的集合),通过二进制来处理,例如将15个第2个物品变为1,2,4,8这4个i物品的集合

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 12010, M = 2010;
int w[N], v[N];
int n, m;
int f[M];

int main()
{
	cin >> n >> m;

	int a, b, c;
	int cnt = 0;
	for (int i = 1; i <= n; i++)//通过二进制将多重背包问题转化为01背包
	{
		scanf("%d %d %d", &a, &b, &c);

		int flag = 1;
		while (flag <= c)
		{	
			v[++cnt] = a * flag;
			w[cnt] = b * flag;

			c -= flag;
			flag *= 2;
		}

		if (c)
		{
			v[++cnt] = c * a;
			w[cnt] = c * b;
		}
	}

	n = cnt;
	
	for (int i = 1; i <= n; i++)//进行01背包解法
		for (int j = m; j >= v[i]; j--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);

	cout << f[m];
}

思路参考:AcWing 5. 二进制优化,它为什么正确,为什么合理,凭什么可以这样分?? - AcWing

多重背包3

数据范围巨大,进行空间时间上的优化

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 20010;
int n, m;
int f[N], cop[N], q[N];

int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++)
	{
		memcpy(cop, f, sizeof f);
		int v, w, s;
		scanf("%d %d %d", &v, &w, &s);

		for (int j = 0; j < v; j++)
		{
			int tail = -1, head = 0;

			for (int k = j; k <= m; k += v)
			{
				if (head <= tail && k - s * v > q[head]) head++;
				if (head <= tail) f[k] = max(cop[k], cop[q[head]] + (k - q[head]) / v * w);
				while (head <= tail && cop[k] >= cop[q[tail]] + (k - q[tail]) / v * w) tail--;
				q[++tail] = k;
			}
		}
	}

	cout << f[m];
}

思路参考:


AcWing 6. 多重背包问题 III 详解 + yxc大佬代码解读 - AcWing

E13 背包DP 多重背包 单调队列优化_哔哩哔哩_bilibili

混合背包问题

 将三种背包问题(01,完全,多重)都转化为多重背包2

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 10100;
int n, m;
int f[N], v[N], w[N], cnt;

int main()
{
	int a, b, s;
	cin >> n >> m;

	for (int i = 1; i <= n; i++)
	{
		scanf("%d %d %d", &a, &b, &s);
		if (!s) s = m / a;//如果是完全背包,那么s=m/a,即1取上限
		else if (s < 0) s = 1;//如果是01背包,那么s=1

		int flag = 1;
		while(flag <= s)
		{
			v[++cnt] = a * flag;
			w[cnt] = b * flag;

			s -= flag;
			flag *= 2;
		}

		if (s)
		{
			v[++cnt] = a * s;
			w[cnt] = b * s;
		}
	}

	n = cnt;

	for (int i = 1; i <= n; i++)
		for (int j = m; j >= v[i]; j--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);

	cout << f[m];
}

或者是把计算f[]的循环写到第一个for循环里面,看起来会干净一点

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 10100;
int n, m;
int f[N], v[N], w[N], cnt;

int main()
{
	int a, b, s;
	cin >> n >> m;

	for (int i = 1; i <= n; i++)
	{
		scanf("%d %d %d", &a, &b, &s);
		if (!s) s = m / a;
		else if (s < 0) s = 1;

		int flag = 1, pre = cnt;
		while(flag <= s)
		{
			v[++cnt] = a * flag;
			w[cnt] = b * flag;

			s -= flag;
			flag *= 2;
		}

		if (s)
		{
			v[++cnt] = a * s;
			w[cnt] = b * s;
		}

		for(int j=pre+1;j<=cnt;j++)
			for (int k = m; k >= v[j]; k--)
				f[k] = max(f[k], f[k - v[j]] + w[j]);
	}

	cout << f[m];
}

思路参考:AcWing 7. 混合背包问题 - AcWing

二维背包的费用问题

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010;
const int M = 110;
int n, v, m;
int f[M][M];

int main()
{
	cin >> n >> v >> m;

	for (int i = 1; i <= n; i++)
	{
		int v1, m1, w1;
		scanf("%d %d %d", &v1, &m1, &w1);

		for (int j = v; j >= v1; j--)
			for (int k = m; k >= m1; k--)
				f[j][k] = max(f[j][k], f[j - v1][k - m1] + w1);
	}

	cout << f[v][m];
}

直接把一维情况下的进行简单拓展处理就可以

分组背包问题

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 110;
int n, m, v[N][N], w[N][N], s[N];
int f[N][N];


int main()
{
	cin >> n >> m;

	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &s[i]);
		for (int j = 1; j <= s[i]; j++) scanf("%d %d", &v[i][j], &w[i][j]);
	}

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			f[i][j] = f[i - 1][j];
			for (int k = 1; k <= s[i]; k++)
			{
				if (j >= v[i][k]) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
			}
		}
	}

	cout << f[n][m];
}

类比为多重背包1就可以,只是每组里面的各物品数值不一样

有依赖的背包问题

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 110;
int n, m, v[N], w[N];
int f[N][N],root;
vector<int> tr[N];

void Find(int u)
{
	for (int i = v[u]; i <= m; i++) f[u][i] = w[u];

	for (int i = 0; i < tr[u].size(); i++)
	{
		int son = tr[u][i];
		Find(son);

		for (int j = m; j >= v[son]; j--)
		{
			for (int k = 0; k <= j - v[u]; k++)
			{
				f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
			}
		}
	}
}

int main()
{
	cin >> n >> m;

	int c;
	for (int i = 1; i <= n; i++)
	{
		cin >> v[i] >> w[i] >> c;

		if (c == -1) root = i;
		else tr[c].push_back(i);
	}

	Find(root);

	cout << f[root][m];
}

思路参考:

AcWing 10. 有依赖的背包问题(思路不同于dxc,但是个人感觉更好理解) - AcWingAcWing 10. 有依赖的背包问题 - 哔哩哔哩 (bilibili.com)

背包问题求方案数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], cnt[N];

int main()
{
	cin >> n >> m;
	for (int i = 0; i <= m; i++) cnt[i] = 1;

	int v, w;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d %d", &v, &w);

		for (int j = m; j >= v; j--)
		{
			int temp = f[j - v] + w;
			if (temp > f[j])
			{
				f[j] = temp;
				cnt[j] = cnt[j - v];
			}
			else if (temp == f[j])
				cnt[j] = (cnt[j - v] + cnt[j]) % mod;
		}
	}

	cout << cnt[m];
}

思路参考:

AcWing 11. 背包问题求方案数 - AcWing

背包问题求具体方案

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int w[N], v[N];
int f[N][N];
int path[N], cnt;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i) cin >> v[i] >> w[i];
    for (int i = n; i >= 1; -- i)
    {
        for (int j = 0; j <= m; ++ j)
        {
            f[i][j] = f[i + 1][j];
            if (j >= v[i]) f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
        }
    }
    for (int i = 1, j = m; i <= n; ++ i)
    {
        if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
        {
            path[cnt ++ ] = i;
            j -= v[i];
        }
    }
    for (int i = 0; i < cnt; ++ i) cout << path[i] << " ";
    cout << endl;
    return 0;
}

思路参考:

AcWing 12. 背包问题求具体方案【01背包 + 背包DP输出方案】 - AcWing

鸣人的影分身

#include<iostream>
using namespace std;
const int N = 15;
int f[N][N];
int m,n;
int T;
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>m>>n;
        for(int i=0;i<=n;++i)f[0][i]=1;
        for(int i=1;i<=m;++i)
        {
            for(int j=1;j<=n;++j)
            {
                f[i][j]=f[i][j-1];
                if(i-j>=0)
                    f[i][j]+=f[i-j][j];
            }
        }
        cout<<f[m][n]<<endl;
    }
    return 0;
}

DP思路:

糖果

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 100 + 5;
int n, k, w[N];
//dp(i, j)代表前i个物品 总价值%k=j的集合
int dp[N][N];

int main(void)
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i) cin >> w[i];
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; ++j)
            dp[i][j] = INT_MIN;

    //dp(0 ,i) (i != 1)都是不存在的状态,所以必须要初始化为负无穷,不然的话可以选择先初始化第一件物品的所有情况,再迭代第二个物品之后
    //dp(i ,j) <-  max(dp(i - 1, j), dp(i - 1, (j - w[i]) % k) + w[i])
    // (s + w[i]) % k = j -> s % k = (j - w[i]) % k

    dp[0][0] = 0;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 0; j <= k - 1; ++j)
            dp[i][j] = max(dp[i - 1][j], dp[i - 1][((j - w[i]) % k + k) % k] + w[i]);
    }

    cout << dp[n][0];
    return 0;
}

DP思路:

密码脱落

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

char s[N];
int f[N][N];//区间 [l, r] 中所有子序列的最大长度

int main(){
    scanf("%s", s);

    int n = strlen(s);

    for(int len = 1; len <= n; len ++)
    {
        for(int l = 0; l + len - 1 < n; l ++)
        {
            int r = l + len - 1;

            if(len == 1) f[l][r] = 1;
            else
            {
                if(s[l] == s[r]) f[l][r] = f[l + 1][r - 1] + 2;
                f[l][r] = max(f[l][r], f[l][r - 1]);
                f[l][r] = max(f[l][r], f[l + 1][r]);
            }
        }
    }

    printf("%d\n", n - f[0][n - 1]);

    return 0;
}

DP思路:

也是闫氏分析法(),但是下面这个在它基础上更好理解 

1222. 密码脱落 - AcWing题库

生命之树

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010, M = N * 2;// N个顶点至多2*N条有向边

int n;
int w[N];// 节点的权值
int h[N], e[M], ne[M], idx;
// h是顶点集,e[i]=b表示a指向b(一条边),ne[i]表示结点i的next的指针,idx指向当前需要插入(已经用过)的结点
LL f[N];// 在以u为根的子树中包含u的所有连通块的权值的最大值

void add(int a, int b)// 这里采用数组实现邻接表来存储图,也就是将多个单链表h[i]拼起来
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;// 头插法创建单链表,新节点指向已有节点
    // h[a]是单链表a的起点,最后一个插入元素的地址,也是idx区间的终点
}

void dfs(int u, int father)// 求f[i],第二个参数记录父节点,防止往回走
{
    f[u] = w[u];
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];

        if (j != father)
        {
            dfs(j, u);
            f[u] += max(0ll, f[j]);// long long的0,和0比较一下,如果<=0没必要加上
        }
    }
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);// 记得h数组置-1

    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    for (int i = 0; i < n - 1; i ++ )// 一共n-1条边
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);// 构建无向图
    }

    dfs(1, -1);

    LL res = f[1];
    for (int i = 2; i <= n; i ++ ) res = max(res, f[i]);// 求f[1]到f[n]的max

    printf("%lld\n", res);

    return 0;
}
无向图的邻接表的新写法Get!(数组实现邻接表)

算法如下:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010, M = N * 2;// N个顶点至多2*N条有向边

int n;
int w[N];// 节点的权值
int h[N], e[M], ne[M], idx;

void add(int a, int b)// 这里采用数组实现邻接表来存储图,也就是将多个单链表h[i]拼起来
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;// 头插法创建单链表,新节点指向已有节点
    // h[a]是单链表a的起点,最后一个插入元素的地址,也是idx区间的终点
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);// 记得h数组置-1

    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    for (int i = 0; i < n - 1; i ++ )// 一共n-1条边
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);// 构建无向图
    }
}

构建图的代码中的各全局变量解释如图:

图解&手撸构建过程:

 包子凑数(P)

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 105; 
const int M = 1e4 + 5;

int arr[N], f[M];

int gcd(int a, int b)
{
    return b? gcd(b, a%b):a;
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
        scanf("%d", &arr[i]);
    int tmp = arr[0];
    for (int i = 1; i < n; ++i)
        tmp = gcd(tmp, arr[i]);
    if (tmp != 1)
    {
        puts("INF");
        return 0;
    }
    f[0] = 1;
    for (int i = 0; i < n; ++i)
    {
        for (int j = arr[i]; j < M; ++j)
        {
            f[j] = max(f[j], f[j-arr[i]]);
        }
    }

    int res = 0;
    for (int i = 1; i < M; ++i)
    {
        if (!f[i])
            res++; 
    }
    printf("%d", res);
    return 0;
}

DP思路:

 

 括号配对(P)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 110, inf = 0x3f3f3f3f;

char s[N];
int f[N][N]; // f[i][j]代表将i~j个字符变成GBE的所有方案的集合的最小值

bool check(int a, int b)
{
    if (s[a] == '[' && s[b] == ']' || s[a] == '(' && s[b] ==')') return true;
    return false;
}

int main()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);

    for (int len = 1; len <= n; len++)
        for (int l = 1; l + len - 1 <= n; l++)
        {
            int r = l + len - 1;
            if (len == 1) f[l][r] = 1;
            else
            {
                f[l][r] = inf;
                if (check(l, r)) f[l][r] = min(f[l][r], f[l + 1][r - 1]);
                for (int k = l; k <= r; k++)
                    f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
            }
        }

    cout << f[1][n] << endl;

    return 0;
}

 旅游规划(P)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010, M = N * 2;

int n;
int h[N], e[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];//p1存下u节点往下最大路走的子节点,up存下u节点往上走的最长路径
int maxd;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs_d(int u, int father)//dfs一下往下走的路径的次大值和最大值
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != father)
        {
            dfs_d(j, u);
            int distance = d1[j] + 1;
            if (distance > d1[u])//更新最大值和次大值
            {
                d2[u] = d1[u], d1[u] = distance;
                p1[u] = j;
            }
            else if (distance > d2[u]) d2[u] = distance;
        }
    }

    maxd = max(maxd, d1[u] + d2[u]);
}

void dfs_u(int u, int father)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != father)
        {
            up[j] = up[u] + 1;
            if (p1[u] == j) up[j] = max(up[j], d2[u] + 1);
            else up[j] = max(up[j], d1[u] + 1);
            dfs_u(j, u);
        }
    }
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    dfs_d(0, -1);
    dfs_u(0, -1);

    for (int i = 0; i < n; i ++ )
    {
        int d[3] = {d1[i], d2[i], up[i]};
        sort(d, d + 3);
        if (d[1] + d[2] == maxd) printf("%d\n", i);
    }

    return 0;
}

垒骰子 (P)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 6, P = 1e9 + 7;

int n, m;
int op[] = {3, 4, 5, 0, 1, 2};
bool st[N][N];

void mul(int c[], int a[], int b[][N])
{
    int temp[N] = {0};
    for (int i = 0; i < N; i ++ )
    {
        for (int j = 0; j < N; j ++ )
        {
            temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % P;
        }
    }
    memcpy(c, temp, sizeof(temp));
}

void mul(int c[][N], int a[][N], int b[][N])
{
    int temp[N][N] = {0};
    for (int i = 0; i < N; i ++ )
    {
        for (int j = 0; j < N; j ++ )
        {
            for (int k = 0; k < N; k ++ )
            {
                temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % P;
            }
        }
    }

    memcpy(c, temp, sizeof(temp));
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        cin >> a >> b;
        st[a - 1][b - 1] = st[b - 1][a - 1] = true;
    }

    int f1[N] = {4, 4, 4, 4, 4, 4};
    int a[N][N];
    for (int i = 0; i < N; i ++ )
    {
        for (int j = 0; j < N; j ++ )
        {
            if (st[j][op[i]]) a[j][i] = 0;
            else a[j][i] = 4;
        }
    }

    n -- ;
    while(n)
    {
        if (n & 1) mul(f1, f1, a);
        mul(a, a, a);
        n >>= 1;
    }

    int res = 0;
    for (int i = 0; i < N; i ++ )
    {
        res = (res + f1[i]) % P;
    }
    cout << res;

    return 0;
}

其他题

修改数组

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1e6+10;
int n, a[N];
int pre[N];

void Init()//初始化并查集
{
	cin >> n;
	for (int i = 0; i <= N; i++) pre[i] = i;//最开始根节点就是自己
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);
}

int Find(int x)//并查集的Find功能
{
	if (pre[x] != x) return pre[x] = Find(pre[x]);//压缩路径
	return pre[x];
}

int main()
{
	Init();
    
	for (int i = 0; i < n; i++)
	{
		a[i] = Find(a[i]);//将a[i]更新为其根节点的值,若a[i]还没被使用过,那这个更新的值就是自己,否则就是下一个还没有被使用过的值,即a[i]的根节点
		printf("%d ", a[i]);
		pre[a[i]] = a[i] + 1;//每次将a[i]的根节点更新为之前根节点加一,此时a[i]的值已经是它之前的根节点
	}
} 

思路:使用并查集的结构,将每个值从这个值开始,到最后一个可以达到的值看作一个棵并查集的树。每次进行pre[]的更新相当于并查集中的union操作

斐波那契(P)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

LL p;

//龟速乘(解决相乘爆longlong的问题)
LL qmul(LL a, LL b) {
    LL res = 0;
    while (b) {
        if (b & 1) res = (res + a) % p;
        a = (a + a) % p;
        b >>= 1;
    }
    return res;
}
//计算2x2矩阵相乘,c=a*b
void mul(LL c[][2], LL a[][2], LL b[][2]) {
    static LL t [2][2];
    memset(t, 0, sizeof t);
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            for (int k = 0; k < 2; ++k) {
                t[i][j] = (t[i][j] + qmul(a[i][k], b[k][j])) % p;
            }
        }
    }
    memcpy(c, t, sizeof t);
}
//快速幂(解决求斐波那契数列第n项的问题)
LL F(LL n) {
    if (!n) return 0;
    LL f[2][2] = {1, 1}; //f(1)=f(2)=1
    LL a[2][2] = {
        {0, 1},
        {1, 1}
    };
    //快速幂
    for (LL k = n - 1; k; k >>= 1) {
        if (k & 1) mul(f, f, a); //f = f * a
        mul(a, a, a); //a = a * a
    }
    return f[0][0];
}
//(F(m-1) * F(n mod m) - 1) mod F(m)
LL H(LL m, LL k) {
    if (k % 2) return F(m - k) - 1;
    else {
        if (k == 0 || m == 2 && m - k == 1) return F(m) - 1;
        else return F(m) - F(m - k) - 1;
    }
}
//求(F(n) - 1)mod F(m)
LL G(LL n, LL m) {
    //m是偶数
    if (m % 2 == 0) {
        //n/2m也是偶数
        if (n / m % 2 == 0) {
            if (n % m == 0) return F(m) - 1;
            else return F(n % m) - 1;
        }
        //n/2m是奇数
        else {
            return H(m, n % m);
        }
    }
    //m是奇数
    else {
        //n/m是偶数,且n/2m是偶数
        if (n / m % 2 == 0 && n / 2 / m % 2 == 0) {
            if (n % m == 0) return F(m) - 1;
            else return F(n % m) - 1;
        }
        //n/m是偶数,且n/2m是奇数
        else if (n / m % 2 == 0 && n / 2 / m % 2) {
            if (m == 2 && n % m == 1) return F(m) - 1;
            else return F(m) - F(n % m) - 1;
        }
        //n/m是奇数,且n/2m是偶数
        else if (n / m % 2 && n / 2 / m % 2 == 0) {
            return H(m, n % m);
        }
        //n/m是奇数,且n/2m是奇数
        else {
            if (n % m % 2) {
                if (m == 2 && m - n % m == 1) return F(m) - 1;
                else return F(m) - F(m - n % m) - 1;
            } else {
                return F(m - n % m) - 1;
            }
        }
    }

}
int main() {
    LL n, m;
    while (cin >> n >> m >> p) cout << (G(n + 2, m) % p + p) % p << endl;
    return 0;
}

剪格子(P)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
#include <vector>
#define x first
#define y second

using namespace std;

const int N = 10, INF = 1e8, P = 131;

typedef unsigned long long ULL;
typedef pair<int, int> PII;

int n, m;
int g[N][N];
bool st[N][N];   // st用于判断剩余方块是否是一个连通块
int sum, ans = INF;  // sum是所有数的总和,ans是答案
PII cands[N * N];  // 搜索路径上已经选择的点
int p[N * N];   // 并查集判断连通块
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; // 偏移量

unordered_set<ULL> hash_table;

int find(int x)  // 并查集模板
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

bool check_connet(int k)  // 判断剩余方块是否是一个连通块
{
    for(int i = 0; i < n * m; i ++) p[i] = i; // 初始化并查集

    int cnt = n * m - k;  // 剩余的点的数量

    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
            if(!st[i][j])
            {
                for(int u = 0; u < 4; u ++)
                {
                    int a = i + dx[u], b = j + dy[u];
                    // 如果越界或者已经搜过则跳过
                    if(a < 0 || a >= n || b < 0 || b >= m) continue;
                    if(st[a][b]) continue;
                    // 得到对应方格的编号对应的并查集中的父节点
                    int p1 = find(i * m + j), p2 = find(a * m + b);
                    if(p1 != p2) // 合并
                    {
                        p[p1] = p2;
                        cnt --;
                    }
                }
            }
    if(cnt != 1) return false;
    return true;
}

bool check_exists(int k) // 这是剪枝的关键,判断扩展后的结果在之前是否出现过
{
    PII bk[N * N];
    for(int i = 0; i < k; i ++) bk[i] = cands[i];

    sort(bk, bk + k); // 这里不排序就会tle, 不知道为啥,求评论区大佬解惑?

    //严格来说,这并不是字符串哈希,只是运用那种思想来对每种情况进行区分而已
    ULL x = 0;
    for(int i = 0; i < k; i ++)
    {
        x = x * P + bk[i].x + 1;
        x = x * P + bk[i].y + 1;
    }
    if(hash_table.count(x)) return true; // 如果之前已经出现过,则返回真

    hash_table.insert(x);
    return false;
}

void dfs(int s, int k) // s 表示当前搜索数的总和,k 表示当前搜索路径的点的数量
{
    if(s == sum / 2)
    {
        // 如果剩下的方块是一个连通块,则更新答案
        if(check_connet(k)) ans = min(ans, k);
        return;
    }

    vector<PII> points; // 存储可扩展的点

    for(int i = 0; i < k; i ++)  // 对搜索路径上的每个点进行扩展
    {
        int x = cands[i].x, y = cands[i].y;
        for(int j = 0; j < 4; j ++)
        {
            int a = x + dx[j], b = y + dy[j];
            if(a < 0 || a >= n || b < 0 || b >= m) continue;
            if(st[a][b]) continue;

            cands[k] = {a, b};  // 将该点加入点集,才可以判断有这个点的连通块的情况
            // 如果新扩展的点使得数量比答案要大或者前面已经有过这种扩展方案,则不用扩展该点
            if(k + 1 < ans && !check_exists(k + 1))
                points.push_back({a, b});
        }
    }

    // 对可扩展的点要从大到小排序
    sort(points.begin(), points.end());
    reverse(points.begin(), points.end());

    for(int i = 0; i < points.size(); i ++)
        if(!i || points[i] != points[i - 1]) // 如果是第一个点或者该点与上一个点不同
        {
            cands[k] = points[i];
            int x = points[i].x, y = points[i].y;
            st[x][y] = true;
            dfs(s + g[x][y], k + 1);  // 往下扩展
            st[x][y] = false;   // 恢复现场
        }
}

int main()
{
    cin >> m >> n;
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
        {
            cin >> g[i][j];
            sum += g[i][j];
        }

    if(sum % 2 == 0){  // 偶数才可以平分
        st[0][0] = true;  // 从左上角开始搜
        cands[0] = {0, 0};
        dfs(g[0][0], 1);
    }

    if(ans == INF) ans = 0;
    cout << ans << endl;
    return 0;
}

组合数问题

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 2010;

int t, k;
int c[N][N], s[N][N];

int main()
{
    scanf("%d%d", &t, &k);

    for(int i = 0; i < N ; i ++) //处理所有组合数
        for(int j = 0; j <= i; j ++)
            if(!j) c[i][j] = 1 % k; //c[][0] 都为 1
            else c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % k;


    for(int i = 0; i < N; i ++)
        for(int j = 0 ; j < N ; j ++) //前缀和
        {
            if(j <= i && c[i][j] == 0) s[i][j] = 1;
            //当组合数合法并且可以整除k时,它的值为1
            if(i - 1 >= 0) s[i][j] += s[i - 1][j];
            if(j - 1 >= 0) s[i][j] += s[i][j - 1];
            if(i - 1 >= 0 && j - 1 >= 0) s[i][j] -= s[i - 1][j - 1];
        } 


    while(t --)
    {
        int n, m;
        scanf("%d%d", &n, &m);
        printf("%d\n", s[n][m]);
    }
    return 0;
}

思路:使用二维前缀和与组合数的递推性质

具体见:AcWing 523. 组合数问题(递推 + 前缀和) - AcWing

各种最长字串or序列问题

最长回文子串

DP写法(n*n)

#include <bits/stdc++.h>
using namespace std;

int main() 
{
    string s;
    cin >> s;

	int n = s.length();
    vector<vector<bool>> dp(n, vector<bool>(n, false));
    int maxLength = 1;  // 最小回文子串长度为1
    
    // 所有长度为1的子串都是回文子串
    for (int i = 0; i < n; i++) {
        dp[i][i] = true;
    }
    
    // 检查长度为2的子串
    for (int i = 0; i < n - 1; i++) {
        if (s[i] == s[i + 1]) {
            dp[i][i + 1] = true;
            maxLength = 2;
        }
    }
    
    // 检查长度大于2的子串
    for (int len = 3; len <= n; len++) {
        for (int i = 0; i <= n - len; i++) {
            int j = i + len - 1;
            if (s[i] == s[j] && dp[i + 1][j - 1]) {
                dp[i][j] = true;
                maxLength = max(maxLength, len);
            }
        }
    }
    
    printf("%d",maxLength);
    return 0;
}

manacher算法(n)

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 22000010;
char s[N], str[N];
int p[N];   //用于存放每个字符可以中心扩展几次

int init()              //处理原字符串
{
    int len = strlen(s);
    str[0] = '#'; str[1] = '$';         //@是防止越界
    int j = 2;
    for (int i = 0; i < strlen(s); i++)
        str[j++] = s[i], str[j++] = '$';
    str[j] = '\0';
    return j;
}

int manacher()
{
    int ans = -1, len = init(), r = 0, c = 0;
    for (int i = 1; i < len; i++)
    {
        if (i <= r) p[i] = min(p[c * 2 - i], r - i);             //situation1
        else p[i] = 1;                                  //situation2
        while (str[i + p[i]] == str[i - p[i]]) p[i]++;                //扩展  
        if (p[i] + i > r)
        {
            r = p[i] + i;
            c = i;          //更新中心位置
        }

        ans = max(ans, p[i] - 1);
    }
    return ans;
}

int main()
{
    int cnt = 0;
    while (cin >> s)
        cout << manacher() << endl;
}

思路参考:

【蓝桥杯每日一题】3.8 最长回文子序列_蓝桥杯最长回文子串-CSDN博客

最长上升序列

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

vector<int>v;
int m, n, a[1006];
int f[1006];

void fun()
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j < i; j++)
		{
			if (a[j] < a[i]) f[i] = max(f[j] + 1, f[i]);
		}
	}
}

int main()
{
	cin >> m;
	while (m--)
	{
		cin >> n;
		for (int i = 1; i <= n; i++) f[i] = 1;
		for (int i = 1; i <= n; i++) cin >> a[i];
		fun();
		int Max = -1;
		for (int i = 1; i <= n; i++)
			if (f[i] > Max) Max = f[i];
		cout << Max << endl;
	}
}

 思路参考:

最长上升子序列 (LIS) 详解+例题模板 (全)-CSDN博客

最长公共子串

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

string s1, s2;
int f[105][105];

void fun()
{
	for (int i = 1; i <= s2.size(); i++)
	{
		for (int j = 1; j <= s1.size(); j++)
		{
			if (s2[i - 1] == s1[j - 1]) f[i][j] = f[i - 1][j - 1] + 1;
			else f[i][j] = max(f[i - 1][j], f[i][j - 1]);
			
		}
	}
}

int main()
{
	while (cin >> s1 >> s2)
	{
		fun();
		cout << f[s2.size()][s1.size()] << endl;
		for (int i = 0; i <= s2.size(); i++)
			for (int j = 0; j <= s1.size(); j++) f[i][j] = 0;
	}
}

思路参考:

最长公共子序列问题 C++ (超详细)_最长公共子序列c++代码-CSDN博客

逆波兰表达式

将正常运算表达式转化为逆波兰表达式
#include <iostream>
#include <string>
#include <vector>
#include<stack>
#include <algorithm>
#include<sstream>
using namespace std;

int main()
{
	stack<string>s;//用于存放符号的栈
	vector<string>v;//存放后缀表达式
	string str, S;//str是当前要处理的字符串,S是输入的正常的运算表达式

	getline(cin, S);
	stringstream ss(S);

	while (ss >> str)//依次获取正常运算表达式的每一项
	{
		int flag = 1;//标志位,若当前字符是运算符号,则置为0
		if (!isdigit(str[0])) flag = 0;

		if (flag) v.push_back(str);//是数字,直接进入队列
		else//若是运算符,那么处理稍复杂
		{
			if (str == "(" || str == "*" || str == "/") s.push(str);//是 ( * /,那么直接入栈
			else if (str == ")")//是 ),则应该把与 ( 之间的符号都弹栈存到队列中
			{
				while (s.top() != "(")
				{
					v.push_back(s.top());
					s.pop();
				}
				s.pop();//弹出 (
			}
			else if (str == "+" || str == "-")//是 + or - ,那么若栈顶符号是 * or /,先将栈顶出栈存入队列中
			{
				if (!s.empty())
				{
					while (s.top() == "*" || s.top() == "/")
					{
						v.push_back(s.top());
						s.pop();
					}
				}
				s.push(str);//入栈
			}
		}
	}
	while (!s.empty())//把栈中存留元素出栈存入队列
	{
		v.push_back(s.top());
		s.pop();
	}

	for (string it : v) cout << it << " ";//输出后缀表达式
}

	

思路参考:逆波兰表达式(后缀表达式)-CSDN博客

逆波兰表达式的计算

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值