寒假总结

文章目录

寒假总结笔记

DFS/BFS/二分

BFS

用数据结构中的队列(queue)来实现:把第一个状态放入队列中,随后将这一状态取出,搜索这个状态所能到达的状态,并把这些状态再放入队列中,以此类推。

代码基本格式:

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

int a[450][450] = {0},n,m;
int v[450][450] = {0};
int dx[k] = {};
int dy[k] = {};//这两个数储存可走到的方向

struct node{
	int x,y;
}q,p;

queue<struct node> s;

int bfs(int x,int y){
	int xx,yy,i;
	a[x][y] = 0;
	q.x = x;q.y = y;
	s.push(q);
	while(s.size()){
		q = s.front();s.pop();
		for(i = 0;i<=k;i++){
			xx = q.x+dx[i];
			yy = q.y+dy[i];
			if(xx<1||yy<1||xx>n||yy>m){//越界则跳过
				continue;
			}
			if(v[xx][yy]!=0){//若已经被访问过则跳过
				continue;
			}
			p.x = xx;p.y = yy;
			a[p.x][p.y] = a[q.x][q.y] + 1;
			s.push(p);
		}
	}
}

DFS

DFS与BFS算是关联比较大,有许多题是dfs和bfs都可以做出来,这两个算法最大的别则是,dfs是一条路走到底,若走不通则返回上一层走另外一条路,找到答案,而bfs则是同一层的先遍历完,再遍历下一层。

代码基本格式

int dfs(状态){//dfs当前状态
	int i,j = 0;
	if(终止条件) return 1;
	//剪枝代码.....
	for(i = 0;i<n;i++){
		
		if(vis[i]) continue;//访问过则跳过
		
		if(符合条件){
			vis[i] = 1;//标记为以访问
			j = dfs(新的状态);//dfs新的状态
			if(j == 1) return 1;
			vis[i] = 0;//回溯时取消访问标记
		}
		//剪枝代码....
		
	}
	return 0;
}

二分

二分是一个比较实用的算法,有些题目当中二分的使用可以大大降低时间复杂度,具体思路便是每一步将划分两个区块,做判断筛掉一个半区。有时候在进行二分时先要将数组进行排序

代码基本格式

int l = 下界-1,r = 上界+1;
while(l+1<r){
    int mid = (l+r)>>1;
    if(check(mid))l = mid;
    else r = mid;
}
//l为满足check的最大值,r为不满足check的最小值

相关例题

马的遍历

题目链接

这道题是一道典型的dfs和bfs都可以使用的例题,但是当看到最少走几步时,我们便可以知道使用bfs是更合理的。所以我们边用bfs来解决这道题

ac代码:

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

int a[450][450] = {0},n,m;
int dx[] = {-2,-1,-2,-1,1,2,1,2};
int dy[] = {-1,-2,1,2,-2,-1,2,1};//dx,dy数组储存马下一步走的方向

struct node{//用结构体来表示点坐标
	int x,y;
}q,p;

queue<struct node> s;

int bfs(int x,int y){
	int xx,yy,i;
	a[x][y] = 0;
	q.x = x;q.y = y;
	s.push(q);
	while(s.size()){
		q = s.front();s.pop();
		for(i = 0;i<=7;i++){
			xx = q.x+dx[i];
			yy = q.y+dy[i];
			if(xx<1||yy<1||xx>n||yy>m){
				continue;
			}
			if(a[xx][yy]!=-1){
				continue;
			}
			p.x = xx;p.y = yy;
			a[p.x][p.y] = a[q.x][q.y] + 1;
			s.push(p);
		}
	}
}

int main(){
	int x,y,i,j;
	scanf("%d %d %d %d",&n,&m,&x,&y);
	for(i = 1;i<=n;i++){//初始化所有坐标为-1表示未走过
		for(j = 1;j<=m;j++){
			a[i][j] = -1;
		}
	}
	bfs(x,y);
	for(i = 1;i<=n;i++){
		for(j = 1;j<=m;j++){
			printf("%-5d",a[i][j]);
		}
		printf("\n");
	}
}

Sticks

题目链接

这一道题则是经典的使用dfs更好做的题,因为使用dfs可以更好表示当前的状态(用形参表示)。但是单用dfs是不行的,还需要对dfs进行剪枝否则会超时。并且我们知道如果先放大的,可以简化步骤,因为如果用小的可能要好多个才能凑齐一整枝。

ac代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,sum = 0;
int a[70] = {0};//存放枝条数量
bool vis[70] = {0};//是否使用过

bool cmp(int a,int b){
	return a>b;
}

int dfsv(int spl,int con,int nowlen){//spl代表需要原来的长度,con代表已经用了多少枝条,弄完了呢代表当前所凑到的枝条长度
	int i,j = 0;
	if(nowlen == spl) {
		nowlen = 0;
	}
	if(con == n&&nowlen == 0) return 1;
	
	for(i = 0;i<n;i++){
		
		if(vis[i]) continue;
		
		if(a[i]+nowlen<=spl){
			vis[i] = 1;
			j = dfsv(spl,con+1,a[i]+nowlen);
			if(j == 1) return 1;
			vis[i] = 0;
		}
		
		if(a[i] == (spl - nowlen)||nowlen == 0) break;//a[i] == (spl - nowlen)则证明有这条枝条放不下,那边证明这个枝条之后也没有用,所以直接break,进行下一次spl的统计,nowlen == 0则证明没有用到枝条,也证明spl不正确,不需要继续计算。
		while(a[i] == a[i+1]) i++;//如果a[i]不行则与a[i]相同的枝条肯定也不行
		//这两处为剪枝
	}
	return 0;
}

int main(){
	int i,j,k;
	while(cin>>n&&n){
		sum = 0;
		for(i = 0;i < n;i++){
			scanf("%d",&a[i]);
			sum+=a[i];
		}
		sort(a,a+n,cmp);
		
		for(i = a[0];i<=sum;i++){
			memset(vis,0,sizeof(vis));
			if(sum%i == 0){
				k = dfsv(i,0,0);
				if(k) break;
			}
		}
		printf("%d\n",i);
	}
	return 0;
}

Obtain Two Zeroes

题目链接

这道题看到很多大佬用数学方式推,但是当时我第一时间想到的是使用二分,因为如果这两个数满足条件则一定会满足下列式子

:a = 2x+y,b = x+2y,所以我们只需要找到一个x使得y也成立,便证明这两个数可行。

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

int chk(int x,int y){
	if(x == y&&y == 0) return 0;
	if(y == 0) return -1;
	if(x == 0) return 1;
	if( (x/y == 2)&&(x%y == 0) ) return 0;
	if( x < 2*y) return -1;
	if( x > 2*y) return 1;
}

int main(){
	int a,b,x,l,r,t,mid,z = 0;
	scanf("%d",&t);
	while(t--){
		z = 0;
		scanf("%d %d",&a,&b);
		if(a>b) swap(a,b);//使得b是两个数中最大的数,方便计算
		if(a == 0&&b == 0){
			printf("YES\n");
			continue;
		}
		if(2*a<b||a == 0||b == 0){//我们知道如果a<2b,或者a=0或者b=0,那么不管怎么样都凑不齐。
			printf("NO\n");
			continue;
		}
		l = 1;r = (b/2)+1;
		while(l<=r){
			mid = (l+r)/2;
			x = chk(a-mid,b-2*mid);
			if(x == 0){
				z = 1;
				break;
			}
			if(x == -1){
				l = mid + 1; 
			}
			if(x == 1){
				r = mid - 1;
			}
		}
		if(z) printf("YES\n");
		else printf("NO\n");
		
	}
}

数论

取模

有关取模的几个重要式子:

1.(ab)%c = ( (a%c)(b%c) )%c

2.(b/a)%c = b*(a)^(c-2)%c

快速幂

快速幂这个算法是个死算法,理解代码后把他记下来。

快速幂

int POW(int a,int b){
    int ans = 1;
    int base = a;
    while(b){
        if(b & 1) ans *= base;
        base *= base;
        b >>= 1;
    }
    return ans;
}

快速幂取模

int pow_mod(int a,int b,int c){
    int ans = 1;
    int base = a%c;
    while(b){
        if(b & 1) ans = (ans*base)%c;
        base = (base*base)%c;
        b >>= 1;
    }
    return ans;
}

GCD/欧几里德算法

gcd即最大公因数,求最大公因数,较为简单的方法辗转相除法, 又名欧几里德算法。用较大数除以较小数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。long long gcd(long long a,long long b){

long long gcd(long long a,long long b){
	if(b == 0) return a;
	else return gcd(b,a%b);
} 

扩展欧几里德

讲的很好的一篇文章

对于两个不全为0的整数a、b,必存在一组解x,y,使得ax+by==gcd(a,b);---------1式

代码实现

int exgcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    int r=exgcd(b,a%b,x,y);
    int temp=y;
    y=x-(a/b)*y;
    x=temp;
    return r;
}

由此拓展开ax+by==c,它的解应该是什么呢?x1=x(c/gcd(a,b)),y1=y(c/gcd(a,b))。(其中x,y为1式中解得的x与y)

这个式子的通解x = x1+t*b/d,

​ y = y1- t*a/d。(t为任意实数)

素数筛

参考

埃氏筛

​ 做法其实很简单,首先将2到n范围内的整数写下来,其中2是最小的素数。将表中所有的2的倍数划去,表中剩下的最小的数字就是3,他不能被更小的数整除,所以3是素数。再将表中所有的3的倍数划去……以此类推,如果表中剩余的最小的数是m,那么m就是素数。然后将表中所有m的倍数划去,像这样反复操作,就能依次枚举n以内的素数,这样的时间复杂度是O(nloglogn)。

代码实现:

bool vis[maxn] = {0};//数值为0时代表是素数
int prime[maxn] = {0},x = 0;
void isprime(int n) 
{
    
    for(int i=2;i<=n;i++)
    {
        if(!vis[i]) prime[x++]=i;
        for(int j=2;j*i<=n;j++)
        {
            vis[i*j]=true;
        }
    }
}

欧拉筛

在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。其他复杂度(O(n)).

代码实现

bool vis[maxn] = {0};//数值为0时代表是素数
int prime[maxn] = {0},x = 0;
void oulasai(int n)  //欧拉筛
{
    for(int i=2;i<=n;i++)
    {
        if(!vis[i]) prime[x++]=i;
        for(int j=0;j<x;j++)
        {
            if(i*prime[j]>n) break;
            vis[i*prime[j]]=true;
            if(i%prime[j]==0) break;
        }
    }
}

容斥原理

long long a[max];
void div(long long n) //分解质因子
{
    long long i;
    num=0;
    for(i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            a[num++]=i;
            while(n%i==0)
                n=n/i;
        }
    }
    if(n>1)
        a[num++]=n;
}

long long rongchi(long long m)//计算1~m与n不互素的个数
{
    long long que[10000],i,j,k,sum=0;
    int t=0;
    que[t++]=-1;
    for(i=0;i<num;i++)
    {
        k=t;
        for(j=0;j<k;j++)
           que[t++]=que[j]*a[i]*(-1);
    }
    for(i=1;i<t;i++)
        sum=sum+m/que[i];
    return sum;
}

相关例题

A/B

要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。

Input

数据的第一行是一个T,表示有T组数据。
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。

Output

对应每组数据输出(A/B)%9973。

Sample Input

2
1000 53
87 123456789

Sample Output

7922
6060

解:

想要写出这一题一定要知道这几个公式,(ab)%m = ( (a%m)(b%m) )%m,(a/b)%m = a*b^(m-2)%m;

所以题目中(A/B)%9973 = A*B^(9971)%9973 = ( (A%9973)(B^(9971)%9973) )%9973 = ( n(B^(9971)%9973) )%9973;

这里需要对B(9971)取模,如果把B(9971)这个先算出来显然不行,会爆,所以就要用到幂取模的算法,这里我使用快速幂取模。

ac代码

#include<cstdio>
#include<iostream>
using namespace std;
int powmod(int a,int b,int c){//快速幂取模
    int ans = 1;
    int base = a%c;
    while(b){
        if(b & 1) ans = (ans*base)%c;
        base = (base*base)%c;
        b >>= 1;
    }
    return ans;
}
int main(){
	int t,n,b,ans;
	scanf("%d",&t);
	while(t--){
		scanf("%d %d",&n,&b);
		ans = (n*powmod(b,9971,9973) )%9973;
		printf("%d\n",ans);
	}
	return 0;
}

青蛙的约会

题目链接

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。

Input

输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。

Output

输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"

Sample Input

1 2 3 4 5

Sample Output

4

解:

我们首先可以假设经过t后相遇,如果他们相遇则会有式子(x+mt)-(y+nt) = kl成立,对该式子进行转换可得(n-m)t+lk = x-y;

这就刚刚好符合扩展欧几里得的式子ax+by = c。

ac代码

#include<cstdio>
#include<iostream>
using namespace std;
long long n,m,x,y,l,xx,yy;
long long a,b,c,d,t;
long long exgcd(long long a, long long b, long long &xx, long long &yy) {
    if (b == 0) {
        xx = 1;
        yy = 0;
        return a;
    }
    long long r = exgcd(b, a%b, xx, yy);
    long long t = xx; xx = yy; yy = t - a/b*yy;
    return r;
}
int main(){
	//long long n,m,x,y,l;
	
	scanf("%lld %lld %lld %lld %lld",&x,&y,&m,&n,&l);
	c = x - y;
	a = n - m;
	b = l;
	d = exgcd(a,b,xx,yy);
	//printf("%lld %lld\n",xx,yy);
	if(c%d!=0){
		printf("Impossible\n");
		return 0;
	}
	xx = xx*(c/d);yy = yy*(c/d);
	t = xx*d/b;//x = x1+t*b/d,因为t为任意实数所以令t = -t,令x等于0,解出t = x1*d/b。
	xx = xx - t*b/d;
	if(xx<0) xx+=(b/d);x//xx只能是正数。
	printf("%lld",xx);
	return 0;
}

美素数

小明对数的研究比较热爱,一谈到数,脑子里就涌现出好多数的问题,今天,小明想考考你对素数的认识。
  问题是这样的:一个十进制数,如果是素数,而且它的各位数字和也是素数,则称之为“美素数”,如29,本身是素数,而且2+9 = 11也是素数,所以它是美素数。
  给定一个区间,你能计算出这个区间内有多少个美素数吗?

Input

第一行输入一个正整数T,表示总共有T组数据(T <= 10000)。
接下来共T行,每行输入两个整数L,R(1<= L <= R <= 1000000),表示区间的左值和右值。

Output

对于每组数据,先输出Case数,然后输出区间内美素数的个数(包括端点值L,R)。
每组数据占一行,具体输出格式参见样例。

Sample Input

3
1 100
2 2
3 19

Sample Output

Case #1: 14
Case #2: 1
Case #3: 4

解:

按照题目的意思,先用筛法把素数筛出来,随后进行计算个数。但是我们发现这样子会超时,所以我们还需要做一个预处理,用数组s[i],来存放答案,s[i]表示1-i中美素数的个数所以我们知道答案便是s[r]-s[l-1]。

ac代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 1000000
bool vis[maxn+5] = {0};
int prime[maxn+5] = {0},x = 0;
int s[maxn+5] = {0};
void osai(int n){//欧拉筛
	int i,j;
	for(i = 2;i<=n;i++){
		if(!vis[i]) prime[x++] = i;
		for(j = 0;j<x;j++){
			if(i*prime[j]>n) break;
			vis[i*prime[j]] = 1;
			if(i%prime[j] == 0) break;
		}
	}
}

int change(int n){//求各位数之和
	int sum = 0;
	while(n){
		sum+=n%10;
		n/=10;
	}
	return sum;
}

int main(){
	int t,l,r,count,i,j;
	osai(1000003);
	vis[1] = 1;
	for(i = 1;i<=1000000;i++){//预处理
		s[i] = s[i-1];
		if(vis[i] == 0&&vis[change(i)] == 0) s[i]++;
	}
	scanf("%d",&t);
	for(j = 1;j<=t;j++){
		count = 0;
		scanf("%d %d",&l,&r);
		printf("Case #%d: %d\n",j,s[r]-s[l-1]);
	}
	return 0;
}

动态规划(DP)

动态规划通过将原问题分解成与原问题性质相同的子问题,当求出子问题时,原问题的解也出来了。

动态规划一般有递归和递推两种写法,但是我们一般采用递推而不用递归,因为递归需要调用函数,当数据比较大时是相当耗费时间和空间的,所以我们也是只采用递推的方法来讲

背包问题

01背包

有一个容量为V的背包,还有n个物体。现在忽略物体实际几何形状,我们认为只要背包的剩余容量大于等于物体体积,那就可以装进背包里。每个物体都有两个属性,即体积w和价值v。
问:如何向背包装物体才能使背包中物体的总价值最大?

假设f[i] [j] 表示前i个物品中背包大小为j时所能装下的最大价值;

此时我们可以得到转移方程f [i] [j] = max(f[i-1] [j],f[i-1] [j - w[i] ] + v[i] );

for(i = 1;i<=n;i++){//va[i]表示第i个物品的价值,wei[i]则表示第i个物品的重量。
			for(j = 0;j<=v;j++){
				if(j<wei[i]){
					f[i][j] = f[i-1][j];
					continue;
				}
				f[i][j] = max(f[i-1][j],f[i-1][j-wei[i]]+va[i]);
			}
		}

一位数组优化:

for(i = 1;i<=n;i++){//va[i]表示第i个物品的价值,wei[i]则表示第i个物品的重量。
			for(j = v;j<=0;j++){//一定是从v->0
				if(j<wei[i]){
					f[j] = f[j];
					continue;
				}
				f[j] = max(f[j],f[j-wei[i]]+va[i]);
			}
		}

完全背包

题意:01背包条件加上每个物品有无限个。

for(i = 1;i<=n;i++){//va[i]表示第i个物品的价值,wei[i]则表示第i个物品的重量。
			for(j = 0;j<=v;j++){
				if(j<wei[i]){
					f[i][j] = f[i-1][j];
					continue;
				}
				f[i][j] = max(f[i][j],f[i][j-wei[i]]+va[i]);
			}
		}

用一位数组优化:

for(i = 1;i<=n;i++){//va[i]表示第i个物品的价值,wei[i]则表示第i个物品的重量。
			for(j = 0;j<=v;j++){
				if(j<wei[i]){
					f[j] = f[j];
					continue;
				}
				f[j] = max(f[j],f[j-wei[i]]+va[i]);
			}
		}

多重背包

题意:01背包条件加上每个物品有个数限制

memset(f,-1,sizeof(f));
f[0] = 0;
for(i = 1;i<=n;i++){//a[i]表示第i个物品的数量
			for(j = 0;j<=m;j++){
				if(f[j]>=0) f[j] = a[i];
				else f[j] = -1;
			}
			for(j = 0;j<=m-p[i];j++){
				f[j+p[i]] = max(f[j+p[i]],f[j]-1);
			}
		}

概率DP

参考

概述
一般来说,概率DP找到正确的状态定义后,转移是比较容易想到的。但状态一定是“可数”的,把有范围的整数作为数组下标。事实上,将问题直接作为状态是最好的。如问“n人做XX事的期望次数”,则设计状态为f[i]表示i个人做完事的期望。转移一般是递推,即从上一个状态转移得(填表)或转移向下一个状态(刷表)。

有时期望DP需以最终状态为初始状态转移,即逆推。如f[i]表示期望还要走f[i]步到达终点。这种状态的转移是刷表法,形如f [ i ] = ∑ p [ i → j ] f [ j ] + w [ i → j ] f[i]=\sum p[i\rightarrow j]f[j]+w[i\rightarrow j]f[i]=∑p[i→j]f[j]+w[i→j],其中p [ ] p[]p[]表示转移的概率,w [ ] w[]w[]表示转移对答案的贡献。一般来说,初始状态确定时可用顺推,终止状态确定时可用逆推。

相关例题

送快弟

现在我们有N个配件,他们有不同的价值. 但是我们背包的容量是有限的,因为我们只有一个一级包, 所以我们最多可以装V重量的东西. 但是为了能更好的吃到鸡(不存在的)我们要携带更有价值的配件,请问我们最多能拿多少价值的配件来当快递员呢??

Input

输入的第一行是T, 表示有一共要打T场比赛.

每组数据由三行组成.

第一行包含两个整数N和V(N <= 1000, V <= 1000). N表示配件的个数, V表示一级包的大小(系统会更新嘛).

第二行包含N个整数, 表示每一个配件的价值.

第三行包含N个整数, 表示每个配件的重量.

Output

对每一组数据, 输出我们最多能拿多少价值的配件.

Sample Input

1
10 10
1 3 5 7 9 11 13 15 17 19
19 17 15 13 11 9 7 5 3 1

Sample Output

51

题解:简单的01背包问题

ac代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
long long wei[1005] = {0};
long long va[1005] = {0};
long long f[1005][1005] = {0};
int main(){
	long long t,i,j,n,v;
	scanf("%lld",&t);
	while(t--){
		memset(va,0,sizeof(va));
		memset(wei,0,sizeof(wei));
		memset(f,0,sizeof(f));
		scanf("%lld %lld",&n,&v);
		for(i = 1;i<=n;i++){
			scanf("%lld",&va[i]);
		}
		for(i = 1;i<=n;i++){
			scanf("%lld",&wei[i]);
		}
		for(i = 1;i<=n;i++){
			for(j = 0;j<=v;j++){
				if(j<wei[i]){
					f[i][j] = f[i-1][j];
					continue;
				}
				f[i][j] = max(f[i-1][j],f[i-1][j-wei[i]]+va[i]);
			}
		}
		printf("%lld\n",f[n][v]);
	}
	return 0;
}

划分

现有面值为1、2、3、4、5、6的硬币若干枚,现在需要知道能不能将这些硬币分成等额的两堆。

Input

每行输入6个正整数,分别表是面值为1、2、3、4、5、6的硬币的个数,若输入6个0代表输入结束。单种硬币的数量不会超过20000。

Output

若能分割,输出 Can be divided.'',若不能输出Can’t be divided.’’

Sample Input

1 0 1 2 0 0
1 0 0 0 1 1
0 0 0 0 0 0

Sample Output

Collection #1:
Can't be divided.

Collection #2:
Can be divided.

题解:想要把硬币划分成两堆,显然硬币的总和必然要是偶数。当分成两堆时,这两堆其中一堆的数量 = sum/2,

所以该问题可以转化为这些硬币是否可以刚好凑成sum/2。显然就是一个多重背包的存在性问题

ac代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int a[7] = {0};
int f[7][310009] = {0};
int input(){
	int i,sum = 0;
	for(i = 1;i<=6;i++){
		scanf("%d",&a[i]);
	sum+=a[i]*i;
	}
	return sum;
}
void pri(int n,int v){
	int i,j;
	printf("***\n");
	for(i = 0;i<=n;i++){
		for(j = 0;j<=v;j++){
			printf("%-3d",f[i][j]);
		}
		printf("\n");
	}
	printf("***\n");
}
int main(){
	int i,j,sum,k,c = 0;
	while(sum = input()){
		c++;
		memset(f,0,sizeof(f));
		if(sum%2){
			printf("Collection #%d:\nCan't be divided.\n\n",c);
			continue;
		}
		sum/=2;
		for(j = 1;j<=sum;j++){
			f[0][j] = -1;
		}
		f[0][0] = 0;
		for(i = 1;i<=6;i++){
			for(j = 0;j<=sum;j++){
				if(f[i-1][j]>=0){
					f[i][j] = a[i];
				}else{
					f[i][j] = -1;
				}
			}
			
			for(j = 0;j<=sum-i;j++){
				if(f[i][j]>0) f[i][j+i] = max(f[i][j+i],f[i][j]-1);
			}
			
			//pri(6,sum);
		}
		if(f[6][sum] == -1){
			printf("Collection #%d:\nCan't be divided.\n\n",c);
		}
		else{
			printf("Collection #%d:\nCan be divided.\n\n",c);
		}
	}
	return 0;
}

Unidirectional TSP

题目链接

解:这题的思路类似数字三角形,从右边往左推,推到最左边时看哪一个起点的步数比较小

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
int a[109][109] = {0};
int f[109][109] = {0};
int v[109][109] = {0};
void pri(int n,int m){
	int i,j;
	printf("***\n");
	for(i = 0;i<=n+1;i++){
		for(j = 0;j<=m+1;j++){
			printf("%-3d",v[i][j]);
		}
		printf("\n");
	}
	printf("***\n");
}
int main(){
	int n,m,i,j,k,mm,zz;
	while(scanf("%d %d",&n,&m)!=EOF){
		for(i = 0;i<=108;i++){
			for(j = 0;j<=108;j++){
				a[i][j] = 0;
				f[i][j] = 0;
				v[i][j] = 0;
			} 
		}
		for(i = 1;i<=n;i++){
			for(j = 1;j<=m;j++){
				scanf("%d",&a[i][j]);
			}
		}
		for(j = 1;j<=m;j++){
			a[0][j] = a[n][j];
			a[n+1][j] = a[1][j];
		}
		for(i = 1;i<=n;i++){
			f[i][m] = a[i][m];
		}
		f[0][m] = f[n][m];f[n+1][m] = f[1][m];
		for(j = m-1;j>=1;j--){
			for(i = 1;i<=n;i++){
				mm = min(f[i-1][j+1],min(f[i][j+1],f[i+1][j+1]));
				f[i][j] = a[i][j]+mm;
				for(k = -1;k<=1;k++){
					if(mm == f[i+k][j+1]){//记录路径
						zz = i+k;
						if(zz == 0){
							zz = n;
						}else{
							if(zz == n+1) zz = 1;
						}
						if(v[i][j] == 0){
							v[i][j] = zz;
						}else{
							v[i][j] = v[i][j]<zz?v[i][j]:zz;
						}
					}
				}
			}
			f[0][j] = f[n][j];f[n+1][j] = f[1][j];
		}
		mm = 1<<29;
		for(i = 1;i<=n;i++){
			if(mm>f[i][1]){
				mm = f[i][1];
				zz = i;
			}
		}
		printf("%d",zz);
		for(j = 1;j<=m-1;j++){
			printf(" %d",v[zz][j]);
			zz = v[zz][j];
		}
		printf("\n");
		printf("%d\n",mm);
	}
	return 0;
} 

Flipping Coins

题目链接

首先可以假设f[i]为已经亮出i个新的面,还需要再抛的期望数。这样显然f[m] = 0。由此我们可以得到转移式:

f[i] = (i/n)f[i]+((n-i)/n)f[i+1]+1。f[i]的由来一个是已经抛出了i个新的面,但是一直抛出重复的期望次数,另外一个则是抛出了不重复的期望次数。此外这个多出的1是每一次都至少需要多抛出一次。

ac代码:

#include<cstdio>
#include<iostream>
using namespace std;
double f[100009] = {0};
int main(){
	int t,i,j,n;
	double cnt;
	scanf("%d",&t);
	for(j = 1;j <= t;j++){
		scanf("%d",&n);
		f[n] = 0;
		for(i = n-1;i>=0;i--){
			f[i] = f[i+1]+(1.0*n)/(n-i);
		}
		printf("Case %d: %lf\n",j,f[0]);
	}
	return 0;
}

数据结构

遵循后入先出的原则,在许多题中利用栈可以高效解题,应用场景有dfs等。在c++中用stack来实现栈的操作

队列

遵循先进先出的原则,在bfs算法中有重要作用。在c++中用queue来实现队列的操作

链表

解释略。。。。可以解决约瑟夫环等问题。在c++中用list来实现双向链表的操作

有根树

有一个根的特殊节点的数成为有根树。树的储存一般利用”左子有兄弟“的表示方法来表达(二叉树除外),即有结构体数组{int l,r},其中l表示的是此节点的子节点,r表示的则是此结点的兄弟结点。

二叉树

每个结点最多只有两个子节点的树。

例题

Minimum path

题目链接

You are given a matrix of size n×nn×n filled with lowercase English letters. You can change no more than kk letters in this matrix.

Consider all paths from the upper left corner to the lower right corner that move from a cell to its neighboring cell to the right or down. Each path is associated with the string that is formed by all the letters in the cells the path visits. Thus, the length of each string is 2n−12n−1.

Find the lexicographically smallest string that can be associated with a path after changing letters in at most kk cells of the matrix.

A string aa is lexicographically smaller than a string bb, if the first different letter in aa and bb is smaller in aa.

Input

The first line contains two integers nn and kk (1≤n≤20001≤n≤2000, 0≤k≤n20≤k≤n2) — the size of the matrix and the number of letters you can change.

Each of the next nn lines contains a string of nn lowercase English letters denoting one row of the matrix.

Output

Output the lexicographically smallest string that can be associated with some valid path after changing no more than kk letters in the matrix.

Examples

Input

4 2
abcd
bcde
bcad
bcde

Output

aaabcde

Input

5 3
bwwwz
hrhdh
sepsp
sqfaf
ajbvw

Output

aaaepfafw

Input

7 6
ypnxnnp
pnxonpm
nxanpou
xnnpmud
nhtdudu
npmuduh
pmutsnz

Output

aaaaaaadudsnz

题解:

我们可以知道我们只能往右边或下边走,并且想要使字典序最小那么就一定是把不是a的字符改成a,并且我们还知道在前k个中尽可能的要走到有a的地方,所以首先我们可以先用dp,记录走到这个地方并且前面全变成a(本来就是a除外)所用到的最少次数,并把>0的地方全部变成a,使用队列解决往哪走的问题。

ac代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct node{
	int x,y,num;
}q,p;
int n,k;
char a[2009][2009] = {'\0'};//记录图
int d[2009][2009] = {0};//记录走到这个地方并且前面全变成a(本来就是a除外)所用到的最少次数
bool v[2009][2009] = {0};//记录是否走过这里
char ans[5000] = {'\0'};//记录答案
int dx[] = {1,0};
int dy[] = {0,1};//两个数组记录方向
queue<struct node> s;
int main(){
	int i,j,xx,yy,nu;
	scanf("%d %d",&n,&k);
	for(i = 1;i<=n;i++){
		for(j = 1;j<=n;j++){
			scanf(" %c",&a[i][j]);
		}
	}
	for(i = 1;i<=n;i++){//使用dp记录走到i,j这个地方并且前面全变成a(本来就是a除外)所用到的最少次数
		for(j = 1;j<=n;j++){
			if(i == 1||j == 1){
				if(a[i][j] == 'a'){
					d[i][j] = max(d[i-1][j],d[i][j-1]);
				}else{
					d[i][j] = max(d[i-1][j],d[i][j-1]) + 1;
				}
				continue;
			}
			if(a[i][j] == 'a'){
				d[i][j] = min(d[i-1][j],d[i][j-1]);
			}else{
				d[i][j] = min(d[i-1][j],d[i][j-1]) + 1;
			}
		}
	}
	for(i = 1;i<=n;i++){
		for(j = 1;j<=n;j++){
			if(d[i][j]<=k) a[i][j] = 'a';
		}
	}
	q.x = 1;q.y = 1,q.num = 1;
	s.push(q);
	v[1][1] = 1;
	ans[q.num] = a[q.x][q.y];
	while(s.size()){
		q = s.front();
		s.pop();
		if(ans[q.num] == a[q.x][q.y])//如果不是最小字母则可以不用存到队列中
		for(i = 0;i<=1;i++){
			xx = q.x + dx[i];
			yy = q.y + dy[i];
			if(xx>n||yy>n||(v[xx][yy])) continue;
			v[xx][yy] = 1;
			p.x = xx;p.y = yy;p.num = q.num + 1;
			s.push(p);
			if(!('a'<=ans[p.num]&&ans[p.num]<='z')){
				ans[p.num] = a[p.x][p.y];
			}else{
				ans[p.num] = a[p.x][p.y]<ans[p.num]?a[p.x][p.y]:ans[p.num]; //ans[i]记录第i步所走的最小字母
			}
		}
	}
	for(i = 1;i<=2*n-1;i++){//输出答案
		printf("%c",ans[i]);
	}
	return 0;
}

Largest Rectangle in a Histogram

题目链接

A histogram is a polygon composed of a sequence of rectangles aligned at a common base line. The rectangles have equal widths but may have different heights. For example, the figure on the left shows the histogram that consists of rectangles with the heights 2, 1, 4, 5, 1, 3, 3, measured in units where 1 is the width of the rectangles:
寒假总结
Usually, histograms are used to represent discrete distributions, e.g., the frequencies of characters in texts. Note that the order of the rectangles, i.e., their heights, is important. Calculate the area of the largest rectangle in a histogram that is aligned at the common base line, too. The figure on the right shows the largest aligned rectangle for the depicted histogram.

Input

The input contains several test cases. Each test case describes a histogram and starts with an integer n, denoting the number of rectangles it is composed of. You may assume that 1 <= n <= 100000. Then follow n integers h1, …, hn, where 0 <= hi <= 1000000000. These numbers denote the heights of the rectangles of the histogram in left-to-right order. The width of each rectangle is 1. A zero follows the input for the last test case.

Output

For each test case output on a single line the area of the largest rectangle in the specified histogram. Remember that this rectangle must be aligned at the common base line.

Sample Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Sample Output

8
4000

题解:

这题需要使用到数据结构栈来解决,记答案为mx,首先当栈内没有元素时将元素入栈,当入栈的元素高度大于栈顶元素则入栈,当入栈元素小于等于栈顶元素时则栈顶元素出栈并计算该元素的面积,更新mx,并把出栈元素的宽度累计到准备入栈的元素。最后当输入停止时,若栈内还有元素则按照上面的规则依次出栈。

ac代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
struct tan{
	long long w,h;
}q,p,z;
stack<struct tan> t;
int main(){
	long long mx = 0,i,n,w,tt;
	while(scanf("%lld",&n)&&n){//当输入n = 0时停止
		mx = 0;w = 0;
		for(i = 1;i<=n;i++){
			w = 0;
			scanf("%lld",&q.h);
			q.w = 1;
			if(t.empty()){//如果是空栈则元素直接入栈
				t.push(q);
				continue;
			}
			p = t.top();
			while(t.size()&&p.h>=q.h){//当入栈元素小于等于栈顶元素时则栈顶元素出栈并计算该元素的面积,更新mx。
				t.pop();
				mx = max(mx,p.h*(p.w+w));//更新mx
				w += p.w;//累计入栈元素的宽度
				if(t.size())p = t.top();
			}
			q.w+=w;//入栈元素宽度加上累计宽度
			t.push(q);
		}
		while(t.size()){//当输入停止时若栈内还有元素,则按照上面规则让元素一个个出栈
			p = t.top();
			t.pop();
			mx = max(mx,p.h*p.w);
			if(t.empty()) break;
			q = t.top();
			t.pop();
			q.w+=p.w;
			t.push(q);
		}
		printf("%lld\n",mx);
	}
	return 0;
}

图论

图的储存

图的储存一般使用邻接表或者邻接矩阵,两种方法各有好坏。邻接表一般使用vector来储存,这种方法节省空间,但是当查询某两个点是否有连接时会比较耗费时间。邻接矩阵一般使用二维数组来储存,这种方法花费比较大的空间,但是查询某两个点是否有连接时非常高效,复杂度仅为o(1)。

最短路问题

最短路问题可以使用dfs,bfs算法来求,但是由于使用这两个算法时容易超时需要不断剪枝比较繁琐,所以这里介绍几个算法可以较为简单的求最短路径问题。

弗洛伊德

弗洛伊德算法可以用于基本所有的图(有向图,无向图,树),并且可以快速知道任意两个点之间的最短路程。但是这个算法的复杂度比较大。需要看题目数据使用。

#define inf 1000000000
int a[N][N] = {0};
int i,j,k;
for(i = 1;i<=n;i++){
    for(j = 1;j<=n;j++){
        a[i][j] = i==j?0:inf;
    }
}
for(k = 1;k<=n;k++){
    for(i = 1;i<=n;i++){
        for(j = 1;j<=n;j++){
            if(a[i][j]>a[i][k]+a[k][j]){//以k为跳板,看看i到j之间经过k是否可以缩短路程
                a[i][j] = a[i][k]+a[k][j];
            }
        }
    }
}

迪克斯特拉

迪克斯特拉用于求单元最短路径问题,即求起点到任意点之间最短的路程,复杂度为o(n^2)。

int d[n][n],ans[n] = {0};//ans[i]表示起点到i的最短距离
void dij(){
	int minv,i,j,u;
	int v[n];
	for(i = 1;i<=n;i++){
		ans[i] = inf;v[i] = 0;
	}
	ans[x] = 0;//x表示起点
	while(1){
		minv = inf;
		u = -1;
		for(i = 1;i<=n;i++){
			if(minv>ans[i]&&!v[i]){
				minv = ans[i];
				u = i;
			}
		}
		if(u == -1) break;
		v[u] = 1;
		for(i = 1;i<=n;i++){
			if(!v[i]&&d[u][i]!=inf){
				if(ans[i]>ans[u]+d[u][i]){
					ans[i] = ans[u]+d[u][i];
				}
			}
		} 
	}
	
}
用优先队列优化
#include<queue>
#define inf 1000000000
int n;
vector<pair<int,int> >adj[MAX];//加权有向图邻接表 ,adj[i][].second表示边的权值,i表示起点,adj[i][].first表示起点 
void dij(){
	int i,j,k;
	priority_queue<pair<int,int> > PQ;//注意:这个表示跟adj不同,i表示起点,second表示边的终点,first表示边的权值 
	int vis[MAX];
	int d[MAX];
	for(i = 0;i<n;i++){
		d[i] = inf;
		vis[i] = 0;
	}
	d[x] = 0;//起点为x
	PQ.push(make_pair(0,x));
	while(!PQ.empty){
		pair<int,int> f = PQ.top();PQ.pop();
		int u = f.second;
		vis[u] = 1;
		if(d[u]<f.first*-1) continue;//取出最小值,如果不是最短路径就忽略,为何乘-1往后看便知 
		
		for(j = 0;j<adj[u].size();j++){
			int v = a[u][j].first;//v为终点 
			if(vis[v] == 1) continue;
			if(d[v]>d[u]+adj[u][j].second){
				d[v] = d[u]+adj[u][j].second;
				PQ.push(make_pair(d[v]*-1,v));//因为优先队列是取出最大值,所以乘以-1则可以达到取出最小值的效果,只需在取出时*-1即可。 
			}
		}
	} 
}

最小生成树

有n个点,求出将所有点连起来所需最短的边长(可以间接相连)。

使用普里姆算法

#define inf 1000000000
int n,M[MAX][MAX];
int prim(){
    int u,minv,i,j;
    int vis[MAX],d[MAX],p[MAX];
    for(i = 0;i<n;i++){
        vis[i] = 0;
        d[i] = inf;
        p[i] = -1;
    }
    d[x] = 0;//x为起点,也可以说是树的根。
    while(1){
        minv = inf;
        u = -1;
        for(i =  0;i<n;i++){
        	if(minv>d[i]&&!vis[i]){
        		minv = d[i];
        		u = i;
			}
		}
		if(u == -1) break;
		vis[u] = 1;
		for(i = 0;i<n;i++){
			if(!vis[i]&&M[u][i]!=inf){
				if(d[i]>M[u][i]){
					d[i] = M[u][i];
					p[i] = u;//记录父节点 
				}
			}
		}
    }
}

例题

还是畅通工程

题目链接

某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省*“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。

Input

测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最小的公路总长度。

Sample Input

3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0

Sample Output

3
5

题解:显然这是一个最小生成树问题,直接使用prim算法来实现。

ac代码:

#include<cstdio>
#include<iostream>
#include<cstring> 
#define inf 100000000
using namespace std;
int r[109][109] = {0},n;
int d[109] = {0},p[109] = {0};
int mst(){
	int i,j,u,minv,sum = 0;
	for(i = 1;i<=n;i++){
		d[i] = inf;
		p[i] = 0;
	}
	d[1] = 0;
	while(1){
		//printf("***\n");
		minv = inf;
		u = -1;
		for(i = 1;i<=n;i++){
			if(minv>d[i]&&!p[i]){
				u = i;
				minv = d[i];
			}
		}
		p[u] = 1;
		if(u == -1) break;
		for(i = 1;i<=n;i++){
			if(r[u][i]!=inf&&p[i] == 0){
				if(d[i]>r[u][i]){
					d[i] = r[u][i];
				}
			}
		}
		
	}
	for(i = 1;i<=n;i++){
		sum+=d[i];
	}
	return sum;
}
int main(){
	int i,j,a,b,c,ans;
	while(scanf("%d",&n)&&n){
		memset(r,0,sizeof(r));
		for(i = 1;i<=(n*(n-1))/2;i++){
			scanf("%d %d %d",&a,&b,&c);
			r[a][b] = r[b][a] = c;
		}
		ans = mst();
		printf("%d\n",ans);
	}
}

Shortest Cycle

题目链接

You are given nn integer numbers a1,a2,…,ana1,a2,…,an. Consider graph on nn nodes, in which nodes ii, jj (i≠ji≠j) are connected if and only if, aiai AND aj≠0aj≠0, where AND denotes the bitwise AND operation.

Find the length of the shortest cycle in this graph or determine that it doesn’t have cycles at all.

Input

The first line contains one integer nn (1≤n≤105)(1≤n≤105) — number of numbers.

The second line contains nn integer numbers a1,a2,…,ana1,a2,…,an (0≤ai≤10180≤ai≤1018).

Output

If the graph doesn’t have any cycles, output −1−1. Else output the length of the shortest cycle.

Examples

Input

4
3 6 28 9

Output

4

Input

5
5 12 9 16 48

Output

3

Input

4
1 2 4 8

Output

-1

题解:弗洛伊德算法+思维。首先我们可以知道ai不超过10^18,所以我们可以知道这个数最多有60位,自己可以草稿纸上模拟一下,发现当个数大于120时就肯定有一个个数为3的环。所以当小于120时我们就可以使用弗洛伊德并不会超时。

ac代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define inf 1e18
using namespace std;
long long cnt = 0,a[100009] = {0},b[100009] = {0};
long long d[200][200] = {0},w[200][200] = {0},ans = inf;//这两个数组都用来记录两个点之间的大小,但各有用处。
void floyd(){
	long long i,j,k;//简单的数学小知识,三点可以确定一个圆,所以我们可以取三个点看他们是否符合情况。
	for(k = 1;k<=cnt;k++){
		for(i = 1;i<k;i++){
			for(j = i+1;j<k;j++){
				ans = min(ans,d[i][j]+w[j][k]+w[k][i]);/*"d[i][j]+w[j][k]+w[k][i]"这个式子可以表示从i出发又回到
                i的步数。必须要使用一个更新过的和两个没更新过得才可行,否则组成的并不是环*/
			}
		}
		for(i = 1;i<=cnt;i++){
			if(d[i][k]!=inf)
			for(j = 1;j <= cnt;j++){
				d[i][j] = min(d[i][j],d[i][k]+d[k][j]);//只更新d,让w保持原状。
			}
		}
	}
}
int main(){
	long long n,i,j,k;
	scanf("%lld",&n);
	for(i = 1;i <= n;i++){//0与任何数取&都等于0,所以不放入计算中
		scanf("%lld",&a[i]);
		if(a[i]!=0) b[++cnt] = a[i];
	}
	
	if(cnt>128){
		printf("3\n");
	}else{
		for(i = 1;i <= cnt;i++){
			for(j = i;j <= cnt;j++){
				if(i!=j) d[i][j] = d[j][i] = w[i][j] = w[j][i] = (b[i]&b[j])?1:inf;//距离为1。 
			}
		}
		floyd();
		if(ans == inf){
			printf("-1\n");
		}else{
			printf("%lld\n",ans);
		}
	}
	return 0;
}
上一篇:JVM面试连环炮


下一篇:酸洗烟气SCR烟气SCR脱硝改造设备