算法竞赛进阶指南学习笔记(基础算法篇)

用这个博客记录一下自己学习过程中遇到的问题和心得体会.
学习这本书时用的是oj是acwing,上面有几乎所有书上的题目.

二次更新记录一下要注意的题和一些心得:
3最短Hamilton路径(这题虽然很经典.不过是状压dp的经典题.)
4.起床困难综合征(这是运算顺序的问题,很多时候前往后算不对,后往前算可能就可能对了,尤其是贪心题.)
9奇怪的汉诺塔(思维题,递推一般都是搞脑子的题,多见能多开阔思路.)
10约数之和(分治很经典的题.可以拿来练分治)
11分形之城(分形很经典的题.还有旋转矩阵的知识.练它)
18七夕祭(问题分解+环形分割纸牌.问题分解的思想很重要(虽然很基础),环形分割纸牌感觉没有开拓很多思路,就是学习了一个很厉害的解法.)
22天才ACM(倍增练习题(也是很难的练习题),倍增是一个很厉害的算法,归并排序也是很厉害的一个算法.在需要多次用到排序的时候,归并排序的作用就很明显了.合并两个有序序列只需要O(n+m)的复杂度,代码难度也不小,可以练手)
23防晒(贪心经典题,很多题目都是这题引申出来的.提供一个可能不完全正确但很多用到的思路(只是一个类似的思路):一个物品,在全部人都能选的情况下(或者是满足全部某一维的情况),给一个要求最低的人,因为这样才能空出更多好的东西给挑剔的人选.)
26国王游戏(贪心的一种难题,感觉很难总结出什么.领项交换,微扰有些确实不好想.假设解的结构.还是思维要够开阔.多想)
27给树染色(这题和天才ACM一样,不仅思维难度大,代码难度也不小.我觉得属于开阔视野题.这种题目,和后面基础数据类型里面有几题有点像,数学归纳法.在具体做题的时候靠猜,靠感觉,证明是数学专业要干的事.)
29袭击(最近平面点对,不要太经典了.归并排序的妙用.分治练手题,练手题不代表简单题…)
31赶牛入圈(这题属于思维难度可能不是很大.但是代码难度确实不小…离散化很经典的题目.很好的练手题)
33士兵(思维题.观察解的结构我觉得也是很重要的一个能力,解的结构往往能给人很多做题的思路.不错的开阔思维题,)
35最大的和(很经典的我不会做的题目…数组转矩阵是真的很不擅长了…多练习吧.)
36数的进制转换(高精度练习题,短除法的妙用.)
37任务(也是能很好开阔思维的题目.因为题目的一些限制,有些条件可能会被弱化(这是挺重要的一种思路).练它)

0x01
1.a^b
快速幂模板题
所谓快速幂.是利用了数二进制的特点.后面倍增算法的思想也和这个有异曲同工之妙.二进制有什么特点呢?最大的一个特点就是它的每一位只有1和0,说明在该位上面,这位上的数只有存在和不存在两种可能.也就是说ab可以把它拆成ak1ak2…这里的k1k2是把b拆分成二进制后对应的次幂20,21这样子.每次都让a=a×a就可以拿到b的每一位对应的数了,如果b这一位上面的数是1.那么就让res = res×a. 要注意的是p可能是1所以在运算前要先让res=1%p;
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int main(){
	LL a,b,p;
	cin >> a >> b >> p;
	LL res = 1%p;
	while(b){
		if(b & 1) res = res*a%p;
		b >>= 1;
		a = a*a%p;
	}
	cout << res;
	
	return 0;
}

2.64位整数乘法
也就是慢速乘,这里的abp范围都是在1e18内的.如果直接相乘能达到1e36.这是肯定会溢出的.根据快速幂的思想.我们一样把b拆成若干二进制组成的01串.表达式可以转换成(20+21…)*a. 所以每次a都只需要×2就可以了.这样就不会溢出了.
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int main(){
	LL a,b,p;
	cin >> a >> b >> p;
	LL res = 0;
	while(b){
		if(b&1) res = (res+a)%p;
		b >>= 1;
		a = a*2%p;
	}
	cout << res;
	
	return 0;
}

3.最短Hamilton路径
这题是一道经典的状态压缩dp题. dp[i][j]表示当前以i这个点为终点.j表示所有的状态(和书上的的i,j顺序是反的).为什么可以这么表示呢?我们先考虑终点.终点是11111…n个1.表示经过了所有点以后以n这个点为终点的最优路径(为什么要加一个终点来限制可以画图深刻理解一下.).它肯定是由(假设n=5)11110这个状态加上一条1-4 -> 5 的边转移过来的.
说的很混乱,还是上代码吧.

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int w[21][21];
int dp[21][(1 << 21)];
int main(){
	int n;
	cin >> n;
	for(int i=0;i<n;++i) for(int j=0;j<n;++j) cin >> w[i][j];
	memset(dp,0x3f,sizeof(dp));
	dp[0][1] = 0;
	for(int i=1;i<(1 << n);++i){
		for(int j=0;j<n;++j){
			if((1 << j) & i){
				for(int k=0;k<n;++k){
					if(j == k) continue;
					if((1 << k) & i){
						dp[j][i] = min(dp[j][i],dp[k][(1 << j) ^ i] + w[k][j]);
					}
				}	
			}
		}
	}
	cout << dp[n-1][(1 << n) - 1];
	
	return 0;
}

4.起床困难综合征
位运算嘛.突出的就是一个拆位.一位一位的考虑如果0和1都可以让答案变成1那就选0不然只有1可以的话就选1.但是不能超过m.按道理说应该倒着推能保证正确.但是顺着推也能ac.
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int n,m;
pii a[N];
bool calc(int x,int bit){
	for(int i=0;i<n;++i){
		int t = (a[i].second>>bit)&1;
		if(a[i].first == 0) x &= t;
		if(a[i].first == 1) x |= t; 
		if(a[i].first == 2) x ^= t;
	}
	return x;
}
int main(){
	cin >> n >> m;
	for(int i=0;i<n;++i){
		string s;
		cin >> s >> a[i].second;
		if(s[0] == 'A') a[i].first = 0;
		if(s[0] == 'O') a[i].first = 1;
		if(s[0] == 'X') a[i].first = 2;
	}
	LL res = 0,tmp = 0;
	for(int i = 30;i>=0;--i){
		if(calc(0,i)) res += (1 << i);
		else {
			if(calc(1,i)&&(1 << i) + tmp <= m) {tmp+=(1 << i);res+=(1 << i);}
		}
	}
	cout << res;
	
	return 0;
}

0x02
5.递归实现指数型枚举
6.递归实现组合型枚举
7.递归实现排列型枚举
这三题没啥好说的.主要是练习简单的剪枝和回溯.
5 代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int n;
vector<int> v;
void dfs(int k){
	if(k == n + 1){
		for(auto x:v) cout << x << " ";
		cout << endl;
		return;
	}
	dfs(k+1);
	v.pb(k);
	dfs(k+1);
	v.pop_back();
}
int main(){
	cin >> n;
	dfs(1);
	
	return 0;
}

6 代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int n,m;
vector<int> v;
void dfs(int k){
	if(n-k+1 < (m-v.size())) return;
	if(k == n + 1){
		for(auto x:v) cout << x << " ";
		cout << endl;
		return;
	}
	v.pb(k);
	dfs(k+1);
	v.pop_back();
	dfs(k+1);
}
int main(){
	cin >> n >> m;
	dfs(1);
	
	return 0;
}

7 代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int n;
int v[10];
bool vis[10];
void dfs(int k){
	if(k == n+1){
		for(int i=1;i<=n;++i) cout << v[i] << " ";
		cout << endl;
		return;
	}
	for(int i=1;i<=n;++i){
		if(vis[i]) continue;
		v[k] = i;
		vis[i] = 1;
		dfs(k+1);
		vis[i] = 0;
	}
}
int main(){
	cin >> n;
	dfs(1);
	
	return 0;
}

8.费解的开关
一道很经典的题目.需要先处理一下问题. 枚举每一个位置复杂度不可以接受.但我们可以枚举第一行.为什么这么做呢.因为第一行的位置确定后我们以后对每一行处理的时候只要看这一行上面的位置是开着的还是关着的,如果是关的就翻转一次.看最后这样处理完后最后一行是不是全亮着的(因为这么操作已经确保了上面所有行都是开着的).可以的话更新一下答案就好了.
附上代码(非递归形式)

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int a[5][5],tmp[5][5];
int dr[4] = {0,0,-1,1};
int dc[4] = {1,-1,0,0};
bool inside(int r,int c){
	if(r >=0 && r<5 && c >=0 && c<5) return true;
	return false;
}
void flip(int r,int c){
	tmp[r][c] = !tmp[r][c];
	for(int i=0;i<4;++i) if(inside(r+dr[i],c+dc[i])) tmp[dr[i]+r][c+dc[i]] = !tmp[dr[i]+r][c+dc[i]];
}	
int main(){
	int t;
	cin >> t;
	while(t--){
		int ans = 1e6;
		for(int i=0;i<5;++i){
			string s;
			cin >> s;
			for(int j=0;j<5;++j){
				if(s[j] == '0') a[i][j] = 0;
				else a[i][j] = 1;
			}
		}
		for(int k=0;k<(1 << 5);++k){
			int cnt = 0;
			for(int i=0;i<5;++i) for(int j=0;j<5;++j) tmp[i][j] = a[i][j];
			for(int i=0;i<5;++i) if((1 << i) & k) flip(0,i),cnt++;
			for(int i=1;i<5;++i){
				for(int j=0;j<5;++j){
					if(!tmp[i-1][j]) flip(i,j),cnt++;
				}
			}
			bool f= 1;
			for(int i=0;i<5;++i) if(!tmp[4][i]){f = 0;break;}
			if(f) ans = min(cnt,ans);
		}
		if(ans <= 6) cout << ans << endl;
		else cout << -1 << endl;
	}
	
	return 0;
}

9.奇怪的汉诺塔
4塔n盘问题.一开始看到很懵逼.想了几种递推方式都是错的.最后看了题解才明白.题解:先把前i个盘在四塔模式下移动到B上,这样的话剩下n-i个盘就变成了三塔模式,因为这i个盘子是最小的那几个.等于B柱是废的.用不了.所以答案就是min(2×f[i]+d[n-i]).三塔模式就很好求了. d[i] = 2×d[n-1]+1.
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int dp[13],d[13];
int main(){
	memset(dp,0x3f,sizeof(dp));
	dp[1] = 1;
	d[1] = 1;
	cout << 1 << endl;
	for(int i=2;i<=12;++i){
		d[i] = 2*d[i-1]+1;
		for(int j=1;j<i;++j){
			dp[i] = min(dp[i],dp[j]*2+d[i-j]);
		}
		cout << dp[i] << endl;
	}
	
	return 0;
}

10.约数之和.
一道经典的分治题.具体方法书上写的很清楚了.讨论一下偶数情况怎么算最快.其实知道奇数算法了,偶数可以转换成奇数形式.就是(1+p1…pn-1)+pn就变成奇数+pn的形式了.有个坑点是a有可能是0,需要特判一下.
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
LL qpow(LL a,LL b){
	LL res = 1;
	while(b){
		if(b&1) res = res*a%mod;
		a = a*a%mod;
		b >>= 1;
	}
	return res;
}
LL sum(LL p,LL k){
	if(!k) return 1;
	if(k&1) return (sum(p,(k-1)/2)*(qpow(p,(k+1)/2)+1)) %mod;
	else return (sum(p,k-1) + qpow(p,k))%mod;
}
int main(){
	LL a,b;
	cin >> a >> b;
	LL res = 1;
	if(!a) res = 0;
	for(LL i=2;i*i<=a;++i){
		LL cnt = 0;
		while(a % i == 0){
			cnt++;
			a /= i;
		}
		res = res*sum(i,cnt*b)%mod;
	}
	if(a > 1) res = res*sum(a,b)%mod;
	cout << res;
	
	return 0;
}

11 分形之城
这题算是目前为止最难搞的一题了.思路应该不难想.就是分治嘛.但是公式有点难推(主要是左下角那个公式).旋转矩阵和矩阵乘法.具体做法就不赘述了…(怕说的太混乱).有些小坑点注意一下.1 << 32是不可以的.但是1LL << 32 是可以的.
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

pll calc(int n,LL num){
	if(!n) return (pll){0,0};
	LL len = (1LL << (n-1)),size = (1LL << (2*n-2));
	pll cur = calc(n-1,num%size);
	LL x= cur.first,y=cur.second;
	int pos = num/size;
	if(pos == 0) return (pll){y,x};
	else if(pos == 1) return (pll){x,y+len};
	else if(pos == 2) return (pll){x+len,y+len};
	else return (pll){2*len-y-1,len-x-1};
}
int main(){
	int t;
	cin >> t;
	while(t--){
		int n;
		LL a,b;
		cin >> n >> a >> b;
		pll pa = calc(n,a-1),pb = calc(n,b-1);
		double dx = pa.first-pb.first,dy = pa.second-pb.second;
		cout << (LL)((double)sqrt(dx*dx*1.0+1.0*dy*dy)*10.0 + 0.5) << endl;
	}
	
	return 0;
}

0x03
12 激光炸弹
二维前缀和模板题.数据再大点可能要考虑离散化了

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 5e3+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int a[N][N];
int main(){
	int n,r;
	cin >> n >> r;
	while(n--){
		int x,y,v;
		cin >> x >> y >> v;
		a[x+1][y+1]+= v;
	}
	for(int i=1;i<=5001;++i) for(int j=1;j<=5001;++j) a[i][j] += a[i-1][j];
	for(int i=1;i<=5001;++i) for(int j=1;j<=5001;++j) a[i][j] += a[i][j-1];
	int ans = 0;
	for(int i=r;i<=5001;++i){
		for(int j=r;j<=5001;++j){
			ans = max(ans,-a[i][j-r]-a[i-r][j]+a[i][j]+a[i-r][j-r]);
		}
	}
	cout << ans;
	
	return 0;
}

13.增减序列
思维题.书上已经说的很详细了.就不赘述了.

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

LL a[N];
int main(){
	int n;
	cin >> n;
	for(int i=1;i<=n;++i) cin >> a[i];
	LL n1=0,n2=0;
	for(int i=n;i>=1;--i){
		a[i] -= a[i-1];
		if(i == 1) continue;
		if(a[i] > 0) n1 +=a[i];
		else n2+= -a[i];
	}
	LL ans = min(n1,n2);
	LL d = abs(n1-n2);
	cout << ans + d << endl << d+1;
	
	return 0;
}
  1. 最高的牛
    算是一道差分模板题吧.注意用set判重.
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9907;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int d[1000005];
set<pii> s;
int main(){
	int n,i,h,r;
	cin >> n >> i >> h >> r;
	while(r--){
		int l,r;
		cin >> l >> r;
		if(l > r) swap(l,r);
		if(s.count(make_pair(l,r))) continue;
		s.insert(make_pair(l,r));
		d[l+1]--;
		d[r]++;
	}
	for(int i=1;i<=n;++i){
		d[i] += d[i-1];
	}
	for(int i=1;i<=n;++i){
		cout << d[i] + h <<endl;
	}
		
	return 0;
}

0x03
15.最佳牛围栏
一道用二分判断可行的问题.关键在判断上.长度不小于k我们只需要记录可选区间里面最小的那个区间.然后用大区间减掉它就可以了.平均数的日常操作.先把平均数给减了然后判断最后能否大于0.

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9907;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
double a[N],b[N];
int n,m;
bool judge(double val){
	double tmp = 1e10;
	double ans = -1e10;
	for(int i=1;i<=n;++i){
		b[i] = a[i]-val;
		b[i] += b[i-1];
	}
	for(int i=m;i<=n;++i){
		tmp = min(tmp,b[i-m]);
		ans = max(ans,b[i]-tmp);
	}
//	cout << ans << endl;
	if(ans >= 0) return true;
	else return false;
}
int main(){
	cin >> n >> m;
	for(int i=1;i<=n;++i){
		cin >> a[i];
	}
	double l = -1e6, r = 1e6;
	while(r - l > 1e-5){
		double mid = (l+r) / 2;
		if(judge(mid)) l = mid;
		else r = mid;
	}
	r = int(r*1000);
	cout << (int)r << endl;
		
	return 0;
}
  1. 电影
    可以说是离散化模板题了.不多赘述了.
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 2e6+10;
const int mod = 9907;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
vector<int> v,all;
vector<int> vb,vc;
int n,m;
int num[N];
int get(int x){
	return lower_bound(all.begin(),all.end(),x)-all.begin();
}
int main(){
	cin >> n;
	int tn = n;
	while(tn--){
		int x;
		cin >> x;
		v.pb(x);
		all.pb(x);
	}
	cin >> m;
	for(int i=1;i<=m;++i){
		int x;
		cin >> x;
		vb.pb(x);
		all.pb(x);
	}
	for(int i=1;i<=m;++i){
		int x;
		cin >> x;
		vc.pb(x);
		all.pb(x);
	}
	sort(all.begin(),all.end());
	all.erase(unique(all.begin(),all.end()),all.end());
	for(int i=0;i<n;++i){
		num[get(v[i])]++;
	}
	pii ans = make_pair(-1,-1);
	int res = 0;
	for(int i=1;i<=m;++i){
		int b,c;
		b = vb[i-1],c = vc[i-1];
		int posb = get(b);
		int posc = get(c);
		if(num[posb] > ans.first){
			ans.first = num[posb];
			ans.second = num[posc];
			res = i;
		}
		else if(num[posb] == ans.first && num[posc] > ans.second){
			ans.second = num[posc];
			res = i;
		}
	}
	cout << res << endl;
		
	return 0;
}

17.货仓选址
排序中位数.完事.

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 2e5+10;
const int mod = 9907;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
vector<int> v,all;
int n,m;
int a[N],b[N];
int get(int x){
	return lower_bound(all.begin(),all.end(),x)-all.begin();
}
int main(){
	cin >> n;
	for(int i=0;i<n;++i){
		cin >> a[i];
		b[i] = a[i];
	}
	sort(a,a+n);
	int v = a[n/2];
	LL ans = 0;
	for(int i=0;i<n;++i){
		ans += (LL)abs(v-a[i]);
	}
	cout << ans;
		
	return 0;
}

18 七夕祭
这题就比较牛逼了.看完lyd大神的讲解才明白咋回事.我就不赘述了.说的也没lyd大神好.
代码

#define LL long long
#define pb push_back
#define pii pair
#include 

using namespace std;

const int N = 1e6+10;
const int mod = 9901;


LL gcd(LL a,LL b){return b == 0?a:gcd(b,a%b);}

int r[N],c[N];
int main(){
	int n,m,t;
	cin >> n >> m >> t;
	for(int i=0;i<t;++i){
		int tr,tc;
		cin >> tr >> tc;
		r[tr]++;
		c[tc]++;
	}
	LL ans1=0,ans2=0;
	if(t%n==0){
		for(int i=1;i<=n;++i){
			 r[i] -= t/n;
			 r[i] += r[i-1];
		}
		sort(r+1,r+1+n);
		int v = r[(n+1)/2];
		for(int i=1;i<=n;++i) ans1 += abs(r[i]-v);
		if(t%m)
			cout << "row "<<ans1 << endl;
	}
	if(t%m==0){
		for(int i=1;i<=m;++i){
			c[i] -= t/m;
			c[i] += c[i-1];
		}
		sort(c+1,c+1+m);
		int v = c[(m+1)/2];
		for(int i=1;i<=m;++i) ans2 += abs(c[i]-v);
		if(t%n)
			cout << "column "<<ans2 << endl;
	}
	if(t%m && t % n) cout << "impossible";
	else if(t%m==0&&t%n==0)cout << "both " << ans1+ans2;
					
	return 0;
}
  1. 动态中位数
    一道很很经典的题目.维护两个堆.把前一半放到大根堆,后一半放到小根堆.
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

priority_queue<int> q1;
priority_queue<int,vector<int>, greater<int> > q2;
int main(){
	int t;
	cin >> t;
	while(t--){
		int tot,n;
		cin >> tot >> n;
		cout << tot << " " << (n+1)/2 <<endl;
		int x;
		while(!q1.empty()) q1.pop();
		while(!q2.empty()) q2.pop();
		cin >> x;
		q2.push(x);
		cout << x << " ";
		int cnt = 1;
		for(int i=2;i<=n;++i){
			cin >> x;
			if(i & 1){
				if(x*2 >= (q1.top()+q2.top()) ) q2.push(x);
				else q1.push(x);
			}
			else{
				if(x >= q2.top()) q2.push(x);
				else q1.push(x);
 			}
 			while(q1.size() >  q2.size()){
 				q2.push(q1.top());
 				q1.pop();
 			}
 			while(q2.size() > 1 + q1.size()){
 				q1.push(q2.top());
 				q2.pop();
 			}
 			if(i & 1){
 				cnt++;
 				cout <<q2.top() << " ";
 				if(cnt == 10 && i !=n){ cout << endl; cnt=0;}
 			}
		}
		cout << endl;
	}
	
	return 0;
}

20 超快速排序
其实就是统计逆序数.这个问题也是不要太经典了.归并排序的拓展.
代码

#define LL long long
#define pb push_back
#define pii pair
#include 

using namespace std;

const int N = 1e6+10;
const int mod = 9901;


LL gcd(LL a,LL b){return b == 0?a:gcd(b,a%b);}

int a[N],b[N];
int h[N];
LL ms(int l,int r){
	if(l >= r) return 0;
	LL res = 0;
	int mid = (l+r) >> 1;
	res = ms(l,mid) + ms(mid+1,r);
	int p1=l,p2=mid+1;
	int i = 0;
	while(p1 <= mid && p2 <= r){
		if(p1 <= mid && a[p1] <= a[p2]){
			 h[++i] = a[p1++];
		}
		else{
			h[++i] = a[p2++];
			res += mid-p1+1;
		}
	}
	while(p1 <= mid) h[++i] = a[p1++];
	while(p2 <= r) h[++i] = a[p2++];
	for(int k=l,j=1;k<=r;++k,++j){
		 a[k] = h[j];
	}
	return res;
}
int main(){
	int n;
	while(cin >> n){
		if(!n) break;
		for(int i=0;i<n;++i) cin >> a[i];
		LL ans = ms(0,n-1);
		cout << ans << endl;
	}
					
	return 0;
}

21 奇数码问题
左右移动逆序数不变.上下移动改变的逆序数是偶数的.所以开始和结尾逆序数的奇偶性要相同.
代码

#define LL long long
#define pb push_back
#define pii pair
#include 

using namespace std;

const int N = 1e6+10;
const int mod = 9901;


LL gcd(LL a,LL b){return b == 0?a:gcd(b,a%b);}

int a[N],b[N];
int h[N];
LL ms(int l,int r){
	if(l >= r) return 0;
	LL res = 0;
	int mid = (l+r) >> 1;
	res = ms(l,mid) + ms(mid+1,r);
	int p1=l,p2=mid+1;
	int i = 0;
	while(p1 <= mid && p2 <= r){
		if(p1 <= mid && a[p1] <= a[p2]){
			 h[++i] = a[p1++];
		}
		else{
			h[++i] = a[p2++];
			res += mid-p1+1;
		}
	}
	while(p1 <= mid) h[++i] = a[p1++];
	while(p2 <= r) h[++i] = a[p2++];
	for(int k=l,j=1;k<=r;++k,++j){
		 a[k] = h[j];
	}
	return res;
}
int main(){
	int n;
	while(cin >> n){
		int cnt = 0;
		for(int i=0;i<n;++i) for(int j=0;j<n;++j){ 
			int x;
			cin >> x;
			if(!x) continue;
			a[cnt++] = x;
		}
		LL a1 = ms(0,cnt-1);
		for(int i=0;i<n;++i) for(int j=0;j<n;++j){ 
			int x;
			cin >> x;
			if(!x) continue;
			a[cnt++] = x;
		}
		LL a2 = ms(0,cnt-1);
		if((a1&1) == (a2&1)) cout << "TAK\n";
		else cout << "NIE\n";
	}
					
	return 0;
}

22 天才ACM
又是很厉害的一道题.书上讲的比较笼统.在这里总结一下.
其实倍增不是最难写的.最难的是归并部分的写法…很容易写错.
要得出正确的答案.需要三个数组.一个原数组,一个临时储存的数组,一个归并用的辅助数组.讲一下临时储存数组的作用.因为在倍增判定的时候.不能改变原数组的顺序.只能用一个临时数组来存一下. 而且要注意的是.在归并之后不要急着放到临时数组上面.应该先存在辅助数组里面.判断S不超过T之后再扔回临时数组里面.超过的话是不能放回去的,因为会打乱掉原有的顺序.
还有就是记得开long long

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 5e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

LL T;
int n,m;
LL a[N],tmp[N],help[N],ans;

bool judge(int l,int r,int p){
	for(int i=r+1;i<=r+p;++i) tmp[i] = a[i];
	sort(tmp+r+1,tmp+r+p+1);
	int h = 0;
	{
		int i = l,j = r+1;
		while(i <= r && j <=r+p){
			if(tmp[i] < tmp[j]) help[h++] = tmp[i++];
			else help[h++] = tmp[j++];
		}
		while(i <= r) help[h++] = tmp[i++];
		while(j <= r+p) help[h++] = tmp[j++];
	}
	LL s = 0;
	for(int i=0,j=h-1;i < j && i<m;++i,--j){
		s += (help[j]-help[i])*(help[j]-help[i]);
	}
	if(s <= T){
		for(int p=0,z=l;p<h;++p,++z) tmp[z] = help[p];
		return true;
	}
	return false;
}
int main(){
	int t;
	cin >> t;
	while(t--){
		cin >> n >> m >> T;
		for(int i=0;i<n;++i) cin >> a[i];
		int l=0,p=1,r = -1;
		ans = 0;
		while(1){
			l = r+1,p=1; r = l;
			for(int i=l;i<=r;++i) tmp[i] = a[i];
			while(p){
				if(r + p < n && judge(l,r,p)){
					r += p;
					p *= 2;
				}
				else p/=2;
			}
			ans ++ ;
			if(r == n-1) break;
		}
		cout << ans << endl;
	}
	
	return 0;
}
  1. 防晒
    贪心题.往往是做的最懵逼的.如何当一个老贪比是一门学问.按照右端点从小到大排序.然后从小到大考虑每一瓶防晒霜,因为从小到大考虑的话每一瓶如果小于这个区间的最大值.那么一定小于后面所有区间的最大值,说明这瓶防晒霜是所有后面的人可以选的.那么应当在满足该牛条件的情况下选最小的一瓶给他.这样后面的牛才会有更多选择. 至于正确性…不会证明…
    代码
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 5e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int n,m;
pii cow[N];
pii oil[N];
bool cmp(pii a,pii b){
	return a.first == b.first?a.second < b.second:a.first < b.first;
}
int main(){
	cin >> n >> m;
	for(int i=1;i<=n;++i) cin >> cow[i].second >> cow[i].first;
	for(int i=1;i<=m;++i) cin >> oil[i].first >> oil[i].second;
	int ans = 0;
	sort(cow+1,cow+1+n,cmp);
	sort(oil+1,oil+1+m);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(oil[j].second && oil[j].first <= cow[i].first && oil[j].first >= cow[i].second){
				ans ++;
				oil[j].second --;
				break;
			}
		}
	}
	cout << ans;
	
	return 0;
}

24.畜栏预定
做题真的不要先看题解.不然反而会限制了你的思想,尤其是贪心题.
我们先抛开花里胡哨的题目说法.用自己的语言描述一下.就是给定了很多个区间.问把这些区间分成不相交的几组最少需要多少组. 随便画几个区间看一下.就会发现.每组区间都是算法竞赛进阶指南学习笔记(基础算法篇)_第1张图片
类似这样的.画的可能丑了点,但意思是对的. 题目只是要求我们把这一组一组区分开来就好了.所以只需要左端点排序.然后每一组给它安排上就完事了.
尽量别先看题解,不然反而会限制自己的思维!

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#include 

using namespace std;
const int N = 5e5+10;
const int mod = 1e9+7;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
struct Cow{
	int l,r,p,v;
	bool operator<(const Cow & x)const {
		return r > x.r;
	}
}cow[N];
int num[N];
bool cmp(Cow a,Cow b){
	return a.l < b.l;
}
int main(){
	int n;
	cin >> n;
	for(int i=1;i<=n;++i) cin >> cow[i].l >> cow[i].r,cow[i].p=i;
	sort(cow+1,cow+1+n,cmp);
	priority_queue<Cow> pq;
	int cnt = 0;
	cow[1].v = ++cnt;
	num[cow[1].p] = 1;
	pq.push(cow[1]);
	for(int i=2;i<=n;++i){
		if(cow[i].l <= pq.top().r){
			cow[i].v = ++cnt;
			num[cow[i].p] = cnt;
			pq.push(cow[i]);
		}
		else{
			cow[i].v = pq.top().v;
			num[cow[i].p] = pq.top().v;
			pq.pop();
			pq.push(cow[i]);
		}
	}
	cout << cnt << endl;
	for(int i=1;i<=n;++i) cout << num[i] << endl;
	
	return 0;
}

25.雷达设备
问题转换.第一次做确实没什么思路.现在第二次做很快就写出来了.
贪心的思路不难,就是在多个区间里,用最少的点覆盖每一个区间.只需要按照右端点升序,然后每次都放在右边界上就好了.

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 1e3+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

pdd a[N];
bool cmp(pdd x,pdd y){return x.sd < y.sd;}
int main(){
	int n;
	db d;
	cin >> n >> d;
	fir(i,1,n){
		db x,y;
		cin >> x >> y;
		if(y > d){
			cout << -1;
			return 0;
		}
		db cur = sqrt(d*d-y*y);
		a[i].ft = x-cur;
		a[i].sd = x+cur; 
	}
	int ans = 0;
	sort(a+1,a+1+n,cmp);
	db pos = -1e11;
	fir(i,1,n) if(pos < a[i].ft) ans++,pos=a[i].sd;
	cout << ans;
		
	return 0;
}
  1. 国王游戏
    神仙题.证明过程书上很完整了.就不多提了. 还有就是高精度的问题.身为只会用板子的我.也不提了…
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 1e3+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

pii a[N];
bool cmp(pii x,pii y){
	if(x.ft*x.sd == y.ft*y.sd) return x.sd < y.sd;
	return x.ft*x.sd < y.ft*y.sd;
}
vector<int> mul(vector<int> x,int y){
	vector<int> z;
	int t = 0;
	for(int i=0;i<x.size();++i){
		t += x[i]*y;
		z.pb(t%10);
		t /= 10;
	}
	while(t){
		z.pb(t%10);
		t /= 10;
	}
	return z;
}
vector<int> div(vector<int> x,int y){
	vector<int> z;
	bool i_f = 1;
	int t = 0;
	for(int i=x.size()-1;i>=0;--i){
		t = t * 10 +x[i];
		int x = t/y;
		if(!i_f || x){
			i_f = 0;
			z.pb(x);
		}
		t %= y;
	}
	reverse(z.begin(),z.end());
	return z;
}
vector<int> copm(vector<int> x,vector<int> y){
	if(x.size() > y.size()) return x;
	if(y.size() > x.size()) return y;
	if(vector<int>(x.rbegin(),x.rend()) > vector<int>(y.rbegin(),y.rend())) return x;
	return y;
}
int main(){
	int n;
	cin >> n;
	cin >> a[0].ft >> a[0].sd;
	fir(i,1,n) cin >> a[i].ft >> a[i].sd;
	sort(a+1,a+1+n,cmp);
	vector<int> tmp(1,1);
	vector<int> ans(1,0);
	fir(i,0,n){
		if(i) ans = copm(ans,div(tmp,a[i].sd));
		tmp = mul(tmp,a[i].ft);
	}
	for(int i=ans.size()-1;i>=0;--i) cout << ans[i];
	
	return 0;
}
  1. 给树染色
    也是一道神仙题.书上的结论是用平均权值来代替真正的权值.心里只有对lyd大神的膜拜.
    代码
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 1e3+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

struct Node{
	int v,p,s;
	double avg;
}node[N];
int n,root;
int find(){
	int res = 1;
	double maxavg = 0;
	fir(i,1,n){
		if(i == root) continue;
		if(maxavg < node[i].avg){
			res = i;
			maxavg = node[i].avg;
		}
	}
	return res;
}// 找到当前均值最大的一个节点
int main(){
	cin >> n >> root;
	LL ans = 0;
	fir(i,1,n) cin >> node[i].v,node[i].s=1,ans += node[i].v,node[i].avg = node[i].v;
	fir(i,1,n-1){
		int x,y;
		cin >> x >> y;
		node[y].p = x;
	}
	fir(i,1,n-1){
		int pos = find();
		int fa = node[pos].p;
		ans += node[pos].v*node[fa].s*1LL;
		for(int j=1;j<=n;++j){
			if(node[j].p == pos){
				node[j].p = fa;
			}
		}
		node[fa].s += node[pos].s;
		node[fa].v += node[pos].v;
		node[fa].avg = (double)node[fa].v/(double)node[fa].s;
		node[pos].avg = -1;
	}
	cout << ans;
	
	return 0;
}
  1. 飞行员兄弟
    简单搜索题.
#define LL long long
#define pb push_back
#define pii pair
#include 

using namespace std;

const int N = 1e3+10;
const int mod = 9901;


LL gcd(LL a,LL b){return b == 0?a:gcd(b,a%b);}

int a[4][4];
int r[4],c[4];
vector<pii> v;
int main(){
	ios::sync_with_stdio(false);
	int ans=  1e6;
	char x;
	for(int i=0;i<4;++i) for(int j=0;j<4;++j) cin >> x,a[i][j] = x == '+'?0:1;
	for(int i=0;i<(1 << 16);++i){
		int tmp[4][4];
		memset(r,0,sizeof(r));
		memset(c,0,sizeof(c));
		for(int j=0;j<4;++j) for(int k=0;k<4;++k) tmp[j][k] = a[j][k];
		int cnt = 0;
		for(int j=0;j<4;++j){
			for(int k=0;k<4;++k){
				if(( 1 << (j*4+k) )&i){
					for(int z=0;z<4;++z) tmp[j][z] = !tmp[j][z];
					for(int z=0;z<4;++z) tmp[z][k] = !tmp[z][k];
					tmp[j][k] = !tmp[j][k];
					cnt++;
				}
			}
		}
		bool f = 1;
		for(int j=0;j<4;++j){
			for(int k=0;k<4;++k){
				if(!tmp[j][k]){ f = 0;break;}
			}
		}
		if(f){
			 if(cnt >= ans) continue;
			 ans = cnt;
			 v.clear();
			 for(int j=0;j<4;++j){
			for(int k=0;k<4;++k){
				if(( 1 << (j*4+k) )&i){
					v.pb((pii){j+1,k+1});
				}
			}
		}
		}
	}
	cout << ans << endl;
	for(int i=0;i<ans;++i) cout << v[i].first << " " << v[i].second << endl;
		
	return 0;
}

29.袭击
经典问题,平面最近点对.分治解决. 两种不同的点给一个color值判断,同种点返回INF就可以了. 归并部分和天才ACM差不多(归并排序NB)

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

struct Node{
	double x,y;
	bool c;
	bool operator < (const Node & w)const{
		return x < w.x;
	}
}node[N],tmp[N];
double dis(Node a,Node b){
	if(a.c == b.c) return 1e11;
	double dx = b.x-a.x,dy = b.y-a.y;
	return sqrt(dx*dx+dy*dy);
}
int n;
double calc(int l,int r){
	if(l >= r) return 1e11;
	int mid = l+r>>1;
	double mx = node[mid].x;
	double cur = min(calc(l,mid),calc(mid+1,r));
	{
		int i=l,j=mid+1,h=0;
		while(i <= mid && j <= r){
			if(node[i].y < node[j].y) tmp[h++] = node[i++];
			else tmp[h++] = node[j++];
		}
		while(i <= mid) tmp[h++] = node[i++];
		while(j <= r) tmp[h++] = node[j++];
		fir(i,0,h-1) node[i+l] = tmp[i];
	}
	vector<Node> us;
	fir(i,l,r){
		if(abs(node[i].x-mx) <= cur) us.pb(node[i]);
	}
	fir(i,0,us.size()-1){
		fir(j,i+1,us.size()-1){
			if(us[j].y-us[i].y < cur) cur = min(cur,dis(us[i],us[j]));
			else break;
		}
	}
	return cur;
}
int main(){
	int t;
	cin >> t;
	while(t--){
		cin >> n;
		fir(i,0,n-1) cin >> node[i].x >> node[i].y,node[i].c=1;
		fir(i,n,2*n-1) cin >> node[i].x >> node[i].y,node[i].c=0;
		sort(node,node+2*n-1);
		printf("%.3lf\n",calc(0,2*n-1));
	}
	
	return 0;
}

30.防线
典型二分题,只有一个地方是奇数.二分区间的奇偶性就可以了.

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 2e5+10;
const int mod = 9901;
const LL INF = 1e11;
LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
int n;
LL s[N],e[N],d[N];
LL sum(LL p){
	LL res = 0;
	fir(i,1,n){
		if(s[i] > p) continue;
		LL re = min(e[i],p);
		res += 1LL*((re-s[i])/d[i] + 1);
	}
	return res;
}
bool judge(LL l,LL r){
	if((sum(r)-sum(l-1) )&1) return true;
	return false;
}
int main(){
	int t;
	cin >> t;
	while(t--){
		cin >> n;
		fir(i,1,n) cin >> s[i] >> e[i] >> d[i];
		LL l = 1,r = INF;
		while(l < r){
			LL mid = (l+r)/2;
			if(judge(l,mid)) r = mid;
			else l = mid+1;
		}
		if(l == INF) cout << "There's no weakness.\n";
		else cout << l << " " << sum(l)-sum(l-1) << endl;
	}
	
	return 0;
}

31.赶牛入圈
二分答案+离散化+二维前缀和. 有一个小技巧.因为前缀和一般不使用0下标.所以可以在all里面先扔一个0进去(因为题目的范围>1)这样就可以用1开始的下标了.x,y可以分别离散化.但是一起离散化也是不会出问题的(顶多慢一点).
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 2e3+10;
const int mod = 9901;
const LL INF = 1e11;
LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
vector<int> all;
pii p[N];
int n,r,s;
int a[N][N];
int get(int v){
	return lower_bound(all.begin(),all.end(),v)-all.begin();
}
bool judge(int len){
	for(int x1=0,x2=1;x2<s;++x2){
		while(all[x2] - all[x1] + 1 > len) x1++;
		for(int y1=0,y2=1;y2<s;++y2){
			while(all[y2] - all[y1] + 1 > len) y1++;
			if(a[x2][y2]+a[x1-1][y1-1]-a[x1-1][y2]-a[x2][y1-1] >= r) return true;
		}
	}
	return false;
}
int main(){
	cin >> r >> n;
	all.pb(0);
	fir(i,1,n){
		int x,y;
		cin >> x >> y;
		all.pb(x),all.pb(y);
		p[i] = (pii){x,y};
	}
	sort(all.begin(),all.end());
	all.erase(unique(all.begin(),all.end()),all.end());
	s = all.size();
	fir(i,1,n) a[get(p[i].ft)][get(p[i].sd)]++;
	fir(i,1,s-1) fir(j,1,s-1) a[i][j] += a[i-1][j];
	fir(i,1,s-1) fir(j,1,s-1) a[i][j] += a[i][j-1];
	int l = 1,r = 1e5;
	while(l < r){
		int mid = l+r>>1;
		if(judge(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << endl;
	
	return 0;
}
  1. 糖果传递
    和七夕祭一样的题.环形分割纸牌问题.排序+中位数.
    代码
#define LL long long
#define pb push_back
#define pii pair
#include 

using namespace std;

const int N = 1e6+10;
const int mod = 9901;
const double INF = 1e10;

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

int n;
LL a[N];
int main(){
	cin >> n;
	LL sum = 0;
	for(int i=1;i<=n;++i) cin >> a[i],sum += a[i];
	sum /= n;
	for(int i=1;i<=n;++i) a[i] -= sum,a[i] += a[i-1];
	sort(a+1,a+1+n);
	LL v= a[(n+1)/2];
	LL ans = 0;
	for(int i=1;i<=n;++i) ans += abs(v-a[i]);
	cout << ans;
		
	return 0;
}

33.士兵
y轴排序中位数.x轴的话最后是d=1等差数列.减掉 i 就是y轴一样的问题了.
代码

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;
const LL INF = 1e11;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int x[N],y[N];
int main(){
	int ans = 0;
	int n;
	cin >> n;
	fir(i,0,n-1) cin >> x[i] >> y[i];
	sort(x,x+n);
	sort(y,y+n);
	fir(i,0,n-1) x[i] -= i;
	sort(x,x+n);
	int v = x[n/2],v1=y[n/2];
	fir(i,0,n-1) ans += abs(v-x[i]),ans+=abs(v1-y[i]);
	cout << ans;
	
	return 0;
}

34.耍杂技的牛
和国王游戏一模一样的题.不过不用高精度.
代码

#define LL long long
#define pb push_back
#define pii pair
#include 

using namespace std;

const int N = 5e4+10;
const int mod = 9901;
const double INF = 1e10;

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

int n;
pii a[N];
bool cmp (pii x,pii y){
	return x.first+x.second == y.first+y.second ? x.first > y.first:x.first+x.second > y.first+y.second;
}
int main(){
	int n;
	cin >> n;
	LL sum = 0;
	for(int i=1;i<=n;++i){ cin >> a[i].first >> a[i].second;sum+=a[i].first;}
	sort(a+1,a+1+n,cmp);
	LL ans = -1e17-10;
	for(int i=1;i<=n;++i){
		sum -= a[i].first;
		ans = max(sum-a[i].second,ans);
	}
	cout << ans;
		
	return 0;
}
  1. 最大的和
    一维的很简单.上升到二维我就有点蒙圈了.看了题解才明白原来还有这种操作. 就是把一列看成一个元素.就转换成一维的问题了.
    代码
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define ft first
#define sd second
#include 

using namespace std;
const int N = 1e2+10;
const int mod = 9901;
const LL INF = 1e11;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}

int a[N][N];
int main(){
	int n;
	cin >> n;
	int ans = -1e9;
	fir(i,1,n) fir(j,1,n) cin >> a[i][j];
	fir(i,1,n) fir(j,1,n) a[i][j] += a[i-1][j];
	fir(i,1,n) fir(j,1,n) a[i][j] += a[i][j-1];
	fir(i,1,n) fir(j,i,n){
		int tmp = 0,tmp1 = -1e9;
		fir(k,1,n){
			if(tmp < 0) tmp = a[i-1][k-1] + a[j][k] - a[i-1][k] - a[j][k-1];
			else tmp += a[i-1][k-1] + a[j][k] - a[i-1][k] - a[j][k-1];
			tmp1 = max(tmp1,tmp);
		}
		ans = max(tmp1,ans);
	}
	cout << ans;
		
	return 0;
}
  1. 数的进制转换
    高精度的题.写的时候顺带复习了一遍高精度.模拟短除法.平时除法是×10,现在是×对应的进制 而且逆序存数的话可以去掉前导0.
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector
#define sd second
#define ALL(a) a.begin(),a.end()
#include 

using namespace std;
const int N = 1e3+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}


int main(){
	int t;
	cin >> t;
	while(t--){
		int a,b;
		string s,ans;
		vi v,v1;
		cin >> a >> b >> s;
		afir(i,s.size()-1,0){
			if(isdigit(s[i])) v.pb(s[i]-'0');
			else if(s[i]>='A' && s[i]<='Z') v.pb(s[i]-'A'+10);
			else v.pb(s[i]-'a'+36);
		}
		while(v.size()){
			int r = 0;
			afir(i,v.size()-1,0){
				r = r*a + v[i];
				v[i] = r/b;
				r %= b;
			}
			v1.pb(r);
			while(v.size() && !v.back()) v.pop_back();
		}
		afir(i,v1.size()-1,0){
			if(v1[i] < 10) ans += char('0'+v1[i]);
			else if(v1[i]<36) ans += char('A'+v1[i]-10);
			else ans += char('a'+v1[i]-36);
		}
		cout << a << " " << s << endl << b << " " << ans << endl << endl;
	}
	
	return 0;
}	
  1. 任务
    这题也很秀…虽然是一个二维限制的问题.但是因为题给数据和函数的问题导致只有x会主导这个任务的价值.所以只需要和上面的防晒问题一样.从大到小排好x后为每个任务匹配一台可以用的集合里面最烂的机器.不过这里用数组+lower_bound是会超时的(我的写法会…).所以引入了multiset的应用.因为每个当前符合x的肯定也符号之后的x,所以有multiset存然后lower_bound检索就ok了.
    超时代码(最后一个测试点超时)
#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector
#define sd second
#define ALL(a) a.begin(),a.end()
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
struct Task{
	int x,y,v;
	bool operator<(const Task& w)const{
		if(x == w.x) return y < w.y;
		return x < w.x;
	}
}task[N],mch[N];

bool vis[N];
int n,m;
int main(){
	cin >> n >> m;
	fir(i,1,n) cin >> mch[i].x >> mch[i].y;
	fir(i,1,m) cin >> task[i].x >> task[i].y,task[i].v = 500*task[i].x+2*task[i].y;
	int cnt = 0;
	LL ans = 0;
	sort(task+1,task+m+1);
	sort(mch+1,mch+1+n);
	afir(i,m,1){
		int pos = lower_bound(mch+1,mch+1+n,task[i])-mch;
		int p=-1,v=1e9;
		fir(j,pos,n){
			if(vis[j]) continue;
			if(mch[j].y >= task[i].y && mch[j].y < v){
				v = mch[j].y;
				p = j;
			}
		}
		if(p != -1){
			vis[p] = 1;
			ans += 1LL*task[i].v;
			cnt++;
		}
	}
	cout << cnt << " " << ans;
		
	return 0;
}	

mutiset解法

#define LL long long
#define pb push_back
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector
#define sd second
#define ALL(a) a.begin(),a.end()
#include 

using namespace std;
const int N = 1e5+10;
const int mod = 9901;

LL gcd(LL a,LL b){return b == 0 ? a : gcd(b,a%b);}
struct Task{
	int x,y,v;
	bool operator<(const Task& w)const{
		if(x == w.x) return y < w.y;
		return x < w.x;
	}
}task[N],mch[N];

int n,m;
int main(){
	cin >> n >> m;
	fir(i,1,n) cin >> mch[i].x >> mch[i].y;
	fir(i,1,m) cin >> task[i].x >> task[i].y,task[i].v = 500*task[i].x+2*task[i].y;
	int cnt = 0;
	LL ans = 0;
	sort(task+1,task+m+1);
	sort(mch+1,mch+1+n);
	multiset<int> s;
	int j =n;
	afir(i,m,1){
		while(mch[j].x >= task[i].x && j >= 1) s.insert(mch[j--].y);
		auto p = s.lower_bound(task[i].y);
		if(p != s.end()){
			cnt++;
			ans += task[i].v;
			s.erase(p);
		}
	}
	cout << cnt << " " << ans;
		
	return 0;
}	

至此.第一部分的基础内容已经过了.总结一下学习到的东西.
从位运算开始,学习了快速幂的知识(主要是引入了二进制拆分的方法)
如何去压缩状态(最短哈密顿路径问题的求解).位运算方面的题一般都
离不开拆位运算的方法.^ & 都是很经常用到的位运算

之后学习了递归,引出了分治的概念,还有递推的问题(如一系列的汉诺塔问题的求解)
分治的经典问题(求等比数列的和以及平面最近点对的求解)也引入了相似的分形问题
主要是坐标的变化问题.

之后学习了前缀和,差分在一维二维上的应用.还有平均数的一般解决方式,也就是不去
直接计算平均数能否到达某个值,而是在一开始的时候减掉平均数.然后判断是否大于等于0.
还有区间选择问题(不断的扩充可选区间里面最大或者最小的一个).二分问题,三分问题的求解.
又引入了离散化的概念.有效的去处理一系列的区间问题.还有中位数的几个代表性题目(货仓选址,环形分割纸牌).
也介绍了归并排序求逆序对,和快排的partation问题.

倍增算法是以前没又接触过的新方法.也花了一点时间掌握.感觉主要是解决区间单调
问题.比二分法要更强大一点.但没那么好写.天才ACM里面对归并排序的妙用也是很值得
学习的一个方法(平面最近点对也用到了一样的方法).

贪心题是思维难度最大的题目了.各种各样的区间问题.还有上树的贪心问题的求解.区间问题
主要是把握住题目的意思,不要被表面的题意迷惑,尽量转化成区间的描述来做题会更好下手.
树上的问题也一样.要多画图去理解和规划好代码的结构.

还有最重要的就是问题转化的能力培养问题.比如七夕祭,士兵,防线等等一类需要转化问题后才
比较好下手的问题.要多思考,去看题目的要求和结果尽量的发散思维.

你可能感兴趣的:(算法竞赛进阶指南学习笔记(基础算法篇))