稀疏图和稠密图
数据结构中对于稀疏图的定义为:m 远小于n的平方的图称为稀疏图,m 远小于n的平方相近,称为稠密图
n为点数,m为边数
邻接矩阵
g [ N ] [ N ] g[N][N] g[N][N]
邻接表
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;
// 添加一条边a->b
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
// 初始化
idx = 0;
memset(h, -1, sizeof h);
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
void bfs(){
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
}
有向图
存有向边a->b,对于一张有向图,一般有邻接矩阵和邻接表的存储方式
无向图
对于无向图中的边ab,存储两条有向边a->b, b->a。
拓扑排序
- 先把所有入度为0的点入队
- 当队列不为空,执行以下操作
- 获得队头,弹出队头
- 枚举队头的所有出边t,t的入度减1,如t的入度为0,则加入队列
acwing848
/**
* 先把所有入度为0的点入队
* 当队列不为空,执行以下操作
* 获得队头,弹出队头
* 枚举队头的所有出边t,t的入度减1,如t的入度为0,则加入队列
**/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int d[N], st[N], a[N], k;
int e[N], ne[N], h[N], idx;
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void topSort(){
k = 0;
queue<int> q;
//入度为0的点入队
for (int i = 1; i <= n; i ++ ) if(d[i] == 0) q.push(i);
while (q.size() > 0){
int p = q.front();
q.pop();
//若之前被遍历过就继续
if(st[p]) continue;
st[p] = true;
//记录拓扑序
a[++k] = p;
//枚举所有出边,入度减1,为0入队
for (int i = h[p]; i != -1; i = ne[i]){
int j = e[i];
d[j] -= 1;
if(d[j] == 0) q.push(j);
}
}
}
int main()
{
memset(h, -1, sizeof h);
idx = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ ){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
d[b] += 1;
}
topSort();
if(k != n) printf("%d", -1);
else{
for (int i = 1; i <= k; i ++ ){
printf("%d ", a[i]);
}
}
}
最短路
朴素dijkstra算法
1. 1号点到每个点的最短距离
2. 边权不能为负
//时间复杂是 O(n^2+m)O(n2+m), n 表示点数,m 表示边数
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定
// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n - 1; i ++ )
{
int t = -1; // 在还未确定最短路的点中,寻找距离最小的点
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
// 用t更新其他点的距离
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
acwing849
/**
* dist数组存点1到每个点的最短距离
* 第一层循环n-1次,第二层循坏找dist中最小值的下标t,再用t更新其他值
**/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra(){
//初始化dist数组为最大值,说明点1到其他点都不联通
memset(dist, 0x3f, sizeof dist);
//点1到自己的距离为0
dist[1] = 0;
//循环n-1次
for (int i = 0; i < n - 1; i ++ ){
int t = -1;
//从dist中找最小值,且没被标记过的
for (int j = 1; j <= n; j ++ ){
if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
}
//用过的标记以下
st[t] = true;
//用t更新其他值
for (int j = 1; j <= n; j ++ ){
//从点1到点t的距离加上从点t到点j的距离
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
//点1到点n为最大值,说明不存在从点1到点n的道路
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
//最开始点与点之间都不互通
memset(g, 0x3f, sizeof g);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ ){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
//若有重复,取最小值
g[x][y] = min(g[x][y], z);
}
printf("%d", dijkstra());
}
堆优化版acwing850
/**
* 从朴素版的dijkstra算法可以看到,在第二层循坏寻找dist的最小值的时候每次都要遍历整个dist数组,非常耗时
* 根据每次需要取最小值,删除最小值且要保存所有元素来看,可以用最小堆(优先队列)来优化
**/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
int n, m;
//n远小于与n的平方,是稀疏图图,用邻接表存
int e[N], ne[N], h[N], w[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int dijkstra(){
//小顶堆
priority_queue<PII, vector<PII>, greater<PII> > q;
memset(st, 0, sizeof st);
//初始化dist
memset(dist, 0x3f, sizeof dist);
//点1到点1的距离为0
dist[1] = 0;
//往堆加入值,第一个是点,第二个是该点到点1的距离
//点1到点1的距离为0
//添加顺序不能换成{1,0},因为上面定义的堆根据PII的第一个元素进行排序
q.push({0, 1});
while(q.size() > 0){
auto t = q.top();
q.pop();
//p是点,dis是点1到该点的距离
int p = t.second, dis = t.first;
//用过不在用
if(st[p]) continue;
//标记用过
st[p] = true;
//遍历与该点相连的其他点,更新最小值
for (int i = h[p]; i != -1; i = ne[i]){
int j = e[i];
//w[i]:点i到点j的距离,dist[j]:点1到点j的距离
if(dis + w[i] < dist[j]) {
dist[j] = dis + w[i];
q.push({dist[j], j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
//初始化邻接表
idx = 0;
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ ){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
}
printf("%d", dijkstra());
}
Bellman-Ford
1. 从点1到点n,限制经过的边数为k
2. 可以处理负权边
acwing853
三角不等式 对于点a指向点b的边c,若distp[b] <= dist[a] + c,那么称该边满足三角不等式
若所有边都满足三角不等式,则dist数组就是最短路
//三角不等式 对于点a指向点b的边c,若distp[b] <= dist[a] + c,那么称该边满足三角不等式
//若所有边都满足三角不等式,则dist数组就是最短路
//循坏次数,循坏边数
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,M = 10010;
struct E{
int a, b, c;
} e[M];
int n, m, k;
//bk数组防止用上次一循环中更新过的点更新其他点
int dist[N], bk[N];
bool bellmanFord(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 1; i <= k; i ++ ){
memcpy(bk, dist, sizeof dist);
for (int j = 1; j <= m; j ++ ){
int a = e[j].a, b = e[j].b, c = e[j].c;
dist[b] = min(dist[b], bk[a] + c);
}
}
if(dist[n] <= 0x3f3f3f3f / 2) return true;
return false;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= m; i ++ ) scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
if(bellmanFord()){
printf("%d", dist[n]);
}else{
printf("%s", "impossible");
}
}
SPFA
由三角不等式dist[b] <= dist[a] + c可知,dist会变小是因为dist[a]变小;我们可以用单调队列来优化这一过程,队列一开始只有起点1,重复以下步骤直至队列为空
- 取出队头保存的点a
- 遍历该点的所有出边
- 更新最小值,若有最小值更新,若该点不存在队列中,将该点加入如队列尾
acwing851
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int dist[N];
//判断点是否存在队列中,防止重复添加
bool st[N];
//邻接表存
int e[N], ne[N], w[N], h[N], idx;
int n, m;
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
while (q.size() > 0){
int a = q.front();
q.pop();
//取出来的a不在队列中了
st[a] = false;
//遍历a的所有出边
for (int i = h[a]; i != -1; i = ne[i]){
int b = e[i];
//不满足三角不等式,需要更新
if(dist[b] > dist[a] + w[i]) {
dist[b] = dist[a] + w[i];
//判断点b是否在队列中,若不在,加入队列
if(!st[b]){
q.push(b);
st[b] = true;
}
}
}
}
if(dist[n] != 0x3f3f3f3f) return true;
return false;
}
int main()
{
memset(h, -1, sizeof h);
idx = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ ){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
}
if(spfa()){
printf("%d", dist[n]);
}else{
printf("%s", "impossible");
}
}
spfa判断负环(acwing852)
在spfa的基础上再添加一个cnt数组,表示从点1到该点经过的边数,若到达某点的边数大于等于n,则存在负环;
根据抽屉原理,经过n条边=>有n+1个点,总共才有n个点,必定某个地方存在负环,导致其不满足三角不等式,使到达该点经过的边数一直在增加
问题1:为什么起始要将所有点都加入到队列里?
答:(这里引用y总的话)每次做一遍spfa()一定是正确的,但时间复杂度较高,可能会超时。初始时将所有点插入队列中可以按如下方式理解:在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。 此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。 执行到这一步,就等价于视频中的做法了。那么视频中的做法可以找到负环,等价于这次spfa可以找到负环,等价于新图有负环,等价于原图有负环。得证。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int dist[N], cnt[N];
//判断点是否存在队列中,防止重复添加
bool st[N];
//邻接表存
int e[N], ne[N], w[N], h[N], idx;
int n, m;
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa(){
//不需要初始化最大值,因为某点存在负环,其一定不满足三角不等式,导致dist越更来越小
queue<int> q;
/**
* 每次做一遍spfa()一定是正确的,但时间复杂度较高,可能会超时。初始时将所有点插入队列中可以按如下方式理解:
在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。
此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。
执行到这一步,就等价于视频中的做法了。
那么视频中的做法可以找到负环,等价于这次spfa可以找到负环,等价于新图有负环,等价于原图有负环。得证。
**/
for (int i = 1; i <= n; i ++ ){
q.push(i);
st[i] = true;
}
while (q.size() > 0){
int a = q.front();
q.pop();
//取出来的a不在队列中了
st[a] = false;
//遍历a的所有出边
for (int i = h[a]; i != -1; i = ne[i]){
int b = e[i];
//不满足三角不等式,需要更新
if(dist[b] > dist[a] + w[i]) {
//到达该点经过的边数+1
cnt[b] = cnt[a] + 1;
//存在负环
if(cnt[b] >= n) return true;
dist[b] = dist[a] + w[i];
//判断点b是否在队列中,若不在,加入队列
if(!st[b]){
q.push(b);
st[b] = true;
}
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
idx = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ ){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
}
if(spfa()){
printf("%s", "Yes");
}else{
printf("%s", "No");
}
}
多源最短路floyd
作用:求每两个点的最短路径,可以处理负权边
floyd是动态规划,
f[k][i][j]经过若干个编号不超过k的节点,从点i到点j的最短路长度。该问题可划分称两个子问题,经过编号不超过k-1的节点从i到j,或者从i先到k,再从k再到j
D
[
k
,
i
,
j
]
=
m
i
n
(
D
[
k
,
i
,
j
]
,
D
[
k
−
1
,
i
,
k
]
,
D
[
k
−
1
,
k
,
j
]
)
D[k, i , j] = min(D[k, i , j], D[k-1, i, k], D[k-1, k , j])
D[k,i,j]=min(D[k,i,j],D[k−1,i,k],D[k−1,k,j])
初始值为
D
[
0
,
i
,
j
]
=
a
[
i
,
j
]
D[0, i, j] = a[i, j]
D[0,i,j]=a[i,j]
k这一维可以省略
D
[
i
,
j
]
=
m
i
n
(
D
[
i
,
j
]
,
D
[
i
,
k
]
+
D
[
k
,
j
]
)
D[i , j] = min(D[i , j], D[i, k] + D[k , j])
D[i,j]=min(D[i,j],D[i,k]+D[k,j])
具体做法
- 初始化,点p到点p为0,到其他点为正无穷
- 循坏k,循环i,循环j
acwing854
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210, INF = 1e9;
int dist[N][N];
int n, m, q;
void floyd(){
for (int k = 1; k <= n; k ++ ){
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
int main(){
scanf("%d%d%d", &n, &m, &q);
//初始化
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
if(i == j) dist[i][j] = 0;
else dist[i][j] = INF;
}
}
//读入数据
for (int i = 1; i <= m; i ++ ){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
dist[x][y] = min(dist[x][y], z);
}
floyd();
for (int i = 1; i <= q; i ++ ){
int x, y;
scanf("%d%d", &x, &y);
//这里是因为有负权边,只要值大于一个比较大的数即可
if(dist[x][y] > INF / 2) printf("%s\n", "impossible");
else printf("%d\n", dist[x][y]);
}
}
最小生成树
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
Prim算法求最小生成树
具体做法:第一层循环n次,每次把一个点加入集合,共n个点,所以要循环n次;dist[t]的含义是集合外距离集合内最近的点的距离,第二层循环找集合外距离集合内最近的点,若t不是第一个点且t的值为正无穷,即当前距离集合最近的点是正无穷,无解;把t加入集合,更新答案;再用t更新其他点
acwing858
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
//m与n的平方相近,用邻接矩阵
int g[N][N];
//dist是集合外距离集合内最近的点的距离
int dist[N];
//判断该点是否在集合内
bool st[N];
int n, m;
int prim(){
//初始化为正无穷
memset(dist, 0x3f, sizeof dist);
int res = 0;
//循环n次
for (int k = 1; k <= n; k ++ ){
//找出集合外距离集合内最近的点
int t = -1;
for (int j = 1; j <= n; j ++ ){
if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
}
//如果不是第一个点且是正无穷,无解
if(t != 1 && dist[N] == INF) return INF;
//把该点加入集合
st[t] = true;
//如果不是第一个点,更新答案
if(t != 1) res += dist[t];
//用该点更新其他点
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main(){
//一开始点与点不连通
memset(g, 0x3f, sizeof g);
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ ){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
//无向图
g[u][v] = g[v][u] = min(g[u][v], w);
}
int res = prim();
//存在负权边,只要大于比较大的数就是无解
if(res > INF / 2){
printf("%s", "impossible");
}else printf("%d", res);
}
Kruskal算法求最小生成树
具体做法:先根据边的权值从小达到大排序;然后依次枚举每条边,若该边的两点不连通,累加该边的权值到res,连通该两点,经过的边数cnt加1。根据生成树的定义,最后是要有n个点n-1条边连成的连通图,因此,若cnt<n-1,无解
acwing 859
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
struct E{
int a, b, w;
} e[M];
//并查集
int p[N];
int cmp(E e1, E e2){
return e1.w < e2.w;
}
int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
//初始化并查集
for (int i = 1; i <= n; i ++ ) p[i] = i;
for (int i = 0; i < m; i ++ ){
scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].w);
}
//按权值从小到大排序
sort(e, e + m, cmp);
int res = 0, cnt = 0;
//依次枚举每条边
for (int i = 0; i < m; i ++ ){
int a = e[i].a, b = e[i].b, w = e[i].w;
//找到a,b的父亲
int pa = find(a), pb = find(b);
//不相等就是不连通
if(pa != pb){
res += w;
cnt += 1;
//将两点连通
p[pa] = pb;
}
}
//根据生成树的定义,经过的边数小于n-1.无解
if(cnt < n- 1) printf("%s", "impossible");
else printf("%d", res);
}
二分图
设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
若一个图是二分图,当且仅当不存在奇数环,奇数环是边数为奇数的环
染色法判断二分图
根据二分图的定义,一条边的两点不在同一集合且集合内部没有变相连;可以用颜色标记每个点,若一点颜色是1,那么与之相连的所有点颜色是2,若染色过程没有发生矛盾,则该图是二分图
acwing860
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
//稀疏图,邻接表
int e[M], ne[M], h[N], idx;
//每个点染的颜色
int color[N];
int n, m;
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, int c){
//染色操作
color[u] = c;
//遍历该点的所有出边,染成相反颜色
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
//没被染色,去染相反颜色
if(color[j] == 0) {
if(!dfs(j, 3 - c)) return false;
}
//已被染色,判断是否发生矛盾
else {
//相连的两个点的颜色不能相同
if(color[j] == c) return false;
}
}
return true;
}
int main(){
//初始化邻接表
memset(h, -1, sizeof h);
idx = 0;
scanf("%d%d", &n, &m);
//无向图,需要加两次
for (int i = 0; i < m; i ++ ){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
//假定染色没有产生矛盾
int flag = false;
for (int i = 1; i <= n; i ++ ){
//该点没被染色,去染色
if(color[i] == 0){
if(!dfs(i, 1)){
//染色产生矛盾
flag = true;
break;
}
}
}
if(flag) printf("%s", "No");
else printf("%s", "Yes");
}
二分图最大匹配
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
即二分图中有多少个边的端点没有连其他边
acwing861
具体做法:
- 邻接表存边, match数组存右边点集中与之对应的左边点集的点;st数组表示左边的点有没有遍历过右边的点;
- 枚举二分图左边的点集x,每次清空右边点集的状态,表示右边的每个点都没有考虑过;
- 枚举与该点相连的点t,match[t] == 0或为match[t]找到与之匹配的另外一个点,则匹配成功,match[t] =x;否则匹配失败
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M = 1e5 + 10;
int match[N];
bool st[N];
int e[M], ne[M], h[N], idx;
int n1, n2, m;
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool find(int x){
for (int i = h[x]; i != -1; i = ne[i]){
int j = e[i];
//遍历过该点,跳过
if(st[j]) continue;
//标记遍历过
st[j] = true;
//右边点集中的这个点没有被左边的点匹配或已匹配,但为与匹配的点找到另一个点与之匹配,则该点匹配成功
if(match[j] == 0 || find(match[j])){
match[j] = x;
return true;
}
}
return false;
}
int main(){
//初始化邻接表
memset(h, -1, sizeof h);
idx = 0;
scanf("%d%d%d", &n1, &n2, &m);
for (int i = 1; i <= m; i ++ ){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
int res = 0;
for (int i = 1; i <= n1; i ++ ){
//重置状态数组,表示右边的点每一个都没考虑过
memset(st, false, sizeof st);
if(find(i)) res ++;
}
printf("%d", res);
}