初三前半学期日志

日志

二月二十八日:

​ 归并 & 二分:

​ 性质:1.对于正排列中的一个序列a[],以i为前半部分的下标,j为后半部分的下标,则a[i]严格大于等于a[mid + 1]到a[j - 1]的所有数字

​ 2.对于正排列中的一个序列a[],无论任何时候,前半部分的元素的初始下标(即输入时各元素的下标)总是严格小于后半部分元素的初始下 标

在进行归并排序处理时,需要在归并的时候记录一切需要的值,且仅需处理左半部分或右半部分,因为另一部分会被递归处理掉

​ 题型:逆序对,统计右边比该数小的数字的个数

三月七日:

1.完全图:任意两个点之间都有边相邻
2.树:从一个点到另一个点只有唯一的路径
3.欧拉路径:不重复经过边,并把所有的边遍历一次

​ 有向图存在欧拉路径的条件:除起点和终点之外,每个点的入度都等于出度。且起点的出度比入度大一,终点的出度比入度小一

​ 无向图存在欧拉路径的条件:所有点的度数必定是偶数,且最多有两个点的度数为奇数,这两个点分别为起点和终点

4.欧拉回路:起点和终点相同的欧拉路径,但每个点的度必须为偶数
5.遍历方法(邻接表(难于操作,但节省内存) or 邻接矩阵(方便但耗空间)):

​ 遍历的基本思想,即找圈,走一半,再回来。

​ 邻接矩阵:

vector<int> f[MAXN];//存图
bool vis[MAXN][MAXN];//记录某两点之间的路径是否被访问过
vector<int> path;//存路径
void dfs(int now){
	for(int i = 0; i < f[now].size(); i++){
        if(!vis[now][f[now][i]]){
            vis[now][f[now][i]] = vis[f[now][i]][now] = 1;
            dfs(f[now][i]);
        }
    }
    path.push_back(now);//按照逆序存储路径
}

​ 邻接表/链表:

int head[MAXN];//与某点相连的最近添加的一条边
struct edge{
    int edge;
    bool vis;//是否被访问
    int back,next;//与该边相对的边和这个边的上一条边
    int v;//该边指向的点的编号
}edge[MAXN];
int ecnt;
void addedge(int u,int v){//在u,v之间添加一条边
	ecnt++;
    edge[ecnt].v = v;
    edge[ecnt].edge = ecnt;
    edge[ecnt].vis = 0;
    edge[ecnt].back = ecnt + 1;
    edge[ecnt].next = head[u];
    head[u] = ecnt;
    ecnt++;
    edge[ecnt].edge = ecnt;
    edge[ecnt].vis = 1;
    edge[ecnt].back = ecnt - 1;
    edge[ecnt].next = head[v];
    head[v] = ecnt;
}
void dfs(int now){
		while(1){
		int v = MAXN,p;
		flag = 0;
		for(int i = head[now]; i != 0; i = edge[i].nxt){
			if(!edge[i].vis && edge[i].v < v){
				v = edge[i].v;
				p = i;
				flag = 1;
			}
		}
		if(flag == 0)break;
		edge[p].vis = 1;
		edge[edge[p].bck].vis = 1;
		dfs(v);
	}//按最小字典序存路径
}
6.欧拉路径/欧拉回路型题:

​ 大体思路:

​ |有向图:每点出度入度必相等(起点终点除外)

​ |欧拉路径 |

​ | |无向图:每点度数必为偶数(起点终点除外)

​ 判断

​ | | 有向图:每点出度入度必须相等

​ |欧拉回路 |

​ |无向图:每点度数必为偶数

有些题目还需要看图是否连通。如果需要求路径,则必须准确找到起点

三月二十五日:

除void外,函数一定要有返回值,否则devc++不会报错,但编译器会报runtime error

​ 今日成果:uva1589象棋,代码中有注释

三月二十八日:
拓扑排序:在DAG内,根据边的方向(即局部中两个点之间的大小)从而得出整体的大小序列。时间复杂度为O(边数 + 点数)

​ dfs代码(为什么不考虑起点,仍可以得到正确答案?)答案倒序存储

vector<int> g[MAXN],ans;
int vis[MAXN];
bool dfs(int now){//如果某个点只有出度大于0,那么这个点会被加入序列吗?此时,就需要在for循环里继续跑,寻找vis[i] == 0的情况
    vis[now] = -1;
    for(int i = 0; i < g[now].size(); i++){
        int k = g[now][i];
        if(vis[k] == -1)return 0;
        if(vis[k] == 0 && !dfs(k))return 0;
    }
    vis[k] = 1;
    ans.push_back(k);
    return 1;
}

即谁的出度为0,谁先进入序列

​ bfs代码(必须以入度为0的点为起点)答案正序存储(为什么:

int tot[MAXN];//每个点的出度
queue<int> q;//把没有入度的点全push进去
while(!q.empty()){
    int now = q.frontf():
    q.pop();
    for(int i = 0; i < g[now].size(); i++){
        int k = g[now][i];
        tot[k]--;
        if(tot[k] == 0)q.push(k);
    }
}

​ 拓扑排序在寻找最长路中的应用:

例题:

League:

N个选手们将参加网球比赛。 我们用选手1, 选手2, …, 选手N来表示他们。

比赛采用循环赛形式,比赛总数为N*(N-1)/2。现在csa问你是否有可能在满足以下所有条件的前提下安排这些比赛?如果答案是肯定的,你还需要找出所需的最少天数。

1.每个选手一天最多打一场比赛。
2.每个选手i按a[i][1], a[i][2],…, a[i][N - 1]的顺序与其他选手比赛。

奖金:

由于无敌的凡凡在2005年世界英俊帅气男总决选中胜出,Yali Company总经理Mr.Z心情好,决定给每位员工发奖金。公司决定以每个人本年在公司的贡献为标准来计算他们得到奖金的多少。

于是Mr.Z下令召开m方会谈。每位参加会谈的代表提出了自己的意见:“我认为员工a的奖金应该比b高!”Mr.Z决定要找出一种奖金方案,满足各位代表的意见,且同时使得总奖金数最少。每位员工奖金最少为100元。

for(int i = 0; i < ans.size(); i++){//ans为拓扑排序后的结果,g为邻接表,pri为以i结束的最长路径长度
	for(int j = 0; j < g[ans[i]].size(); j++){
		pri[ans[i]] = max(pri[ans[i]],pri[g[ans[i]][j]] + 1);
    }
}
为什么可以这么做?

​ 在一个图的拓扑序中,其第一个元素一定是出度为0的节点,因此,从这个节点开始,就可以将他所连接的所有点的pri都更新一遍,以此类推。

编号问题:

对于一些题目,将问题抽象成一个图,点可能不能用一个状态来进行描述,此时需要给点进行编号,以达到减少空间复杂度和提高效率的目的

如题:

League:

N个选手们将参加网球比赛。 我们用选手1, 选手2, …, 选手N来表示他们。
比赛采用循环赛形式,比赛总数为N*(N-1)/2。现在csa问你是否有可能在满足以下所有条件的前提下安排这些比赛?如果答案是肯定的,你还需要找出所需的最少天数。

1.每个选手一天最多打一场比赛。
2.每个选手i按a[i][1], a[i][2],…, a[i][N - 1]的顺序与其他选手比赛。

对于这个题,将两人之间的比赛抽象成一个点,先后顺序即为有向边。如1和2之间有一场比赛,那么将该点描述成(1,2)。显而易见,这样的描述并不便于存储和进行操作,因此,我们用两种方法来进行编号

方案一(空间复杂度高):
int s(int a,int b){//为了编号的统一性,必须保证a比b小
	return a * 1000 + b;
}

容易看出,用这样的编号来开vis数组会占用很多不必要的空间,虽然方便,但点与点的编号是不连续的,效率并不高。

方案二(采用map,虽然操作稍显复杂,但保证了编号连续,不仅节约了许多不必要的空间,还便于我们的遍历):

全题代码:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000
int b[MAXN+5][MAXN+5];
map<pair<int,int>, int> a;
vector<int>G[MAXN*MAXN+5];
int vis[MAXN*MAXN+5], dep[MAXN*MAXN+5];
int n, tot;
bool dfs(int i){
    int ret = 0;
    vis[i] = -1;
    for(int j = 0; j < G[i].size(); j++){
        int k = G[i][j];
        if(vis[k] == -1) return false;
        if(vis[k] == 0) {
            bool r = dfs(k);
            if(!r) return false;
            ret = max(ret, dep[k]);
        } 
        else if(vis[k] == 1)
            ret = max(ret, dep[k]);
    }
    vis[i] = 1;
    dep[i] = ret + 1;
    return true;
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n-1; j++){
            int k, i;
            scanf("%d", &k);
            b[i][j] = k;
            if(a.find(make_pair(i, k)) == a.end()){
                a[make_pair(i, k)] = a[make_pair(k,i)] = ++tot;
            }
        }
        int p = a[make_pair(i,b[i][1])];
        for(int j = 2; j <= n-1; j++){
            int k = a[make_pair(i,b[i][j])];
            G[p].push_back(k);
            p = k;
        }
    }
    for(int i = 1; i <= tot; i++)
        if(!vis[i]){
            bool r = dfs(i);
            if(!r) {
                puts("-1");
                return 0;
            }
        }
    int ans = 0;
    for(int i = 1; i <= tot; i++)
        ans = max(ans, dep[i]);
    printf("%d\n", ans);
}
四月六日:

​ 完成几道小题

题目一:四分树UVa297

​ 在这道题中,题目给出了两棵四分树的先序遍历,要求进行合并后统计黑色像素的个数

思路一:写程序分别建两棵树,再将其合并,但合并时由于两棵树结构不同,实现上有困难

思路二:将每一棵树对应到二维矩阵上,涂了黑色的就打上标记,并把答案加1,重复两次即可

在思路二中,我们将树映射到二维中,既方便了操作,又减小了代码量,这样的思路值得学习

题目二:平衡的括号UVa673

You are given a string consisting of parentheses () and [].

A string of this type is said to be correct:

(a) if it is the empty string

(b) if A and B are correct, AB is correct,

(c) if A is correct, (A) and [A] is correct.

Write a program that takes a sequence of strings of this type and check their correctness. Your program can assume that the maximum string length is 128.

用双端队列实现的。。。二叉树递归怎么处理还没想明白。。。

题目三:天平UVa839

​ 输入一个树状天平,根据力矩相等原则判断左右是否平衡,即Wl * ml = Wr * mr,其中Wl和Wr分别为左右两边砝码的重量,D为距离

采用先序输入,每个天平的格式为Wl,ml,Wr,mr,当Wl或Wr等于0时,表示该砝码是一个子天平,接下来会描述这个子天平,先描述左子天平,后描述右子天平

既然是先序输入,不如直接将其当作一棵二叉树进行处理。即先处理左右子天平,再判断当前天平是否平衡

采用引用传值的方法,代码量简直不要太精简。。。

#include<bits/stdc++.h>
using namespace std;
bool solve(int &m){
	int wl,wr,dl,dr;
	bool b1 = 1,b2 = 1;
	cin >> wl >> dl >> wr >> dr;
	if(wl == 0)b1 = solve(wl);
	if(wr == 0)b2 = solve(wr);
	m = wl + wr;
	return b1 && b2 && wl * dl == wr * dr;
}
int main(){
	//freopen("B.out","w",stdout); 
	int n,m;
	cin >> n;
	while(n--){
		if(solve(m))printf("YES\n");
		else printf("NO\n");
		if(n)puts("");
	}
	return 0;
}
题目四:树UVa548

此题值得细细钻研,待下次再写

四月八日:

assert可用于测试代码,能够指出哪一行出现问题

结构体构造函数的使用:

struct node{
    int a;
    string s;
    char b;
    void init(int aa,string ss,char bb){
        this->a = aa;
        this->s = ss;
        this->b = bb;
    }//自定义初始化函数
    node():a(),s(),b(){}
    node(int aa,string ss,char bb)a(aa),s(ss),b(bb){}
}

结构体内重载运算符:

struct node{
    int a;
    string s;
    char b;
    bool operator==(const node d)const{
        return this->a == d.a && this->s == d.s && this->b == d.b;
    }
}
四月九日:

数组下标内不要嵌套,代码能多简洁就多简洁。对于一些存在多种情况的模拟题,依次分析每一种情况,尝试抓住其共同特征后而写,可显著减少代码量

四月十三日:

​ 1.安迪的第一本字典uva10815:对于一些需要删除中间空格再进行分开保存的数据,可以先对其进行处理,再用stringstream进行输入

posted @ 2022-07-01 18:37  腾云今天首飞了吗  阅读(45)  评论(0编辑  收藏  举报