Detailed explanation of search and optimization

Posted by sofasurfer on Wed, 12 Jan 2022 12:43:16 +0100

Depth first search (DFS)

The idea of depth first search is to start from a node and keep accessing its child nodes deep until its child nodes have no other nodes connected with it except its father. Then go back to its father node and continue the previous operation.

Therefore, depth first search is a recursive process.

After understanding the idea of depth first search, let's see how its code is implemented:

void dfs (int x) {
    cout << x << endl; //Output the point number currently traversed
    vis[x] = 1; //Mark that this point has been passed
    for (int i = 1; i <= n; i ++)
        if (!vis[i] && (a[x][i] || a[i][x])) //adjacency matrix 
            dfs(i);
}

If you don't understand it, you can write this program, then run through the data you write, and slowly understand qwq

So now let's do a water problem: Sudoku

Obviously, deep search can be used to write this question violently, but how to search it?

Assuming that the position of $(x, y) $has been found, if there is a number on this point, you can skip this point to search for the next point;

If there are no numbers on this point, you can enumerate the numbers from $1 $to $9 $to judge whether these numbers can be put in, and then search the next point.

The general idea is as described above. There are some small details about this problem.

When judging whether a point can put the number of $i $, you can use three arrays to store whether there is the number of $i $in each row, column or square array of $3 * 3 $.

Also, after the search, reset the number on the unknown grid and the previous three arrays, because this number is not necessarily a positive solution, and the number to be searched is not necessarily this number.

If you have found the location of $(9, 9) $, this is definitely the answer. You can output it directly and exit the program.

code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int a[10][10], h[10][10], l[10][10], c[10][10];
void print () {
	for (int i = 1; i <= 9; i ++) {
		for (int j = 1; j <= 9; j ++)
			printf("%d ", a[i][j]);
		printf("\n");
	}
	exit(0); //Exit program
}
void dfs (int x, int y) {
	if (!a[x][y]) {
		for (int i = 1; i <= 9; i ++) {
			if (!h[x][i] && !l[y][i] && !c[(x - 1) / 3 * 3 + (y - 1) / 3 + 1][i]) {
				a[x][y] = i;
				h[x][i] = 1;
				l[y][i] = 1;
				c[(x - 1) / 3 * 3 + (y - 1) / 3 + 1][i] = 1;
				if (x == 9 && y == 9) print();
				if (y != 9) dfs(x, y + 1);
				else dfs(x + 1, 1);
				a[x][y] = 0;
				h[x][i] = 0;
				l[y][i] = 0;
				c[(x - 1) / 3 * 3 + (y - 1) / 3 + 1][i] = 0;
			}
		}
	}else {
		if (x == 9 && y == 9) print();
		if (y != 9) dfs(x, y + 1);
		else dfs(x + 1, 1);
	}
}
int main () {
	for (int i = 1; i <= 9; i ++)
		for (int j = 1; j <= 9; j ++) {
			scanf("%d", &a[i][j]);
			if (a[i][j] != 0) {
				h[i][a[i][j]] = 1;
				l[j][a[i][j]] = 1;
				c[(i - 1) / 3 * 3 + (j - 1) / 3 + 1][a[i][j]] = 1;
			}
		}
	dfs(1, 1);
	return 0;
}

prune

  • Optimize search order

In some search problems, the order of each level and branch of the search tree is not fixed.

Changing the search order will produce different search tree forms, and its scale is also very different.

For example, in the Sudoku topic just now, only legal numbers are selected for search, and those illegal numbers are skipped directly.

  • Eliminate equivalent redundancy

In the search process, if you find that the final results of several different branches are the same, you can select only one of them to search.

  • Feasible pruning

In the process of searching, if it is found that the search branch cannot reach the recursive boundary, it should be traced back immediately.

  • Optimal pruning

If the cost of the current search branch is greater than the optimal solution we already have, we should also backtrack.

  • Memorization

The status of each search branch can be recorded. If the previous branch has been searched, it can be traced back. For example, when traversing a graph in depth first, if the current node has been accessed, you can skip this point.

After reading the above pruning methods, you must still not understand! So do a few questions to understand the above methods:

The kitten climbed the mountain

Small wooden stick

Cut cake

Pruning problem solution

Cat climbing:

The question requires a minimum payment of dollars, which can obviously cycle from the smallest answer to the largest answer.

Once an answer is found to be qualified, it means that the previous smaller answer is not qualified, then the current answer is the best answer, and then you can exit the cycle.

So how to dfs?

Starting from the first kitten, use each cable car with the number of current dfs answers (assuming that our current dfs answer is $x $, we can only use $x $cable cars at most) to load kittens.

If all the kittens are loaded in the end, it means that the current answer is feasible.

It turned out that TLE was found, of course, because there was no pruning!

We know that at least one kitten can be installed in each cable car (the weight of the cable car is greater than that of any kitten), so the $x $kitten must be installed in the first $x $cable car. Therefore, when loading the $x $kitten, you can only select the first $x $cable car.

Finally, you can AC qwq!!

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, w, c[19], sum, ans, m[19], flag;
void dfs (int x) {
	if (x == n + 1) {
		flag = 1;
		return;
	}
	for (int i = 1; i <= ans && i <= x; i ++) {
		if (m[i] + c[x] <= w) {
			m[i] += c[x];
			dfs(x + 1);
			m[i] -= c[x];
			if (flag) return;
		}
	}
}
int main () {
	scanf("%d%d", &n, &w);
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &c[i]);
		sum += c[i];
	}
	for (ans = sum / w; ans <= 18; ans ++) {
		dfs(1);
		if (flag) break;
	}
	printf("%d", ans);
	return 0;
}

Stick:

Note to delete the given stick with a length of more than $50 $!!!

From the data given, we can quickly determine the range of answers. The minimum value is the longest of all small wooden sticks, and the maximum value is the sum of all small wooden sticks (that is, these small wooden sticks are only disassembled from one wooden stick).

Then you can start dfs with a small answer.

The idea of dfs is: first cycle the target length within the answer range of $len $, and then we can get from the total length of all small sticks of $sum $, which can be combined into $num $sticks.

Then start to splice the first stick with other sticks. If the spliced length is exactly equal to $len $, then start to splice the remaining $num - 1 $sticks.

If all $num $sticks are spelled out and there are no small sticks left, the current answer is feasible.

And because it starts to traverse from a small answer, this answer must be the best answer. At this time, the result can be output.

But obviously, no pruning will tle qwq

From the beginning, obviously, if an answer matches, then $sum $must be a multiple of it.

When choosing the answer of dfs, you can cut out many impossible answers!

We know that the shorter the stick, the more times it needs to be put together to form a stick with a length of $len $.

However, if you start with a long stick, you will have less trouble. Therefore, before dfs, you should sort the small sticks from large to small.

How to prune in the process of dfs?

If $x $, $y $, $z $are three consecutive sticks, and they cannot be assembled into a stick with the desired length of $len $.

First, start splicing from $x $. After connecting $y $, it is found that the length is less than $len $, then connect $z $, and the length is still less than $len $. At this time, trace back.

Because $y $is spelled, then $x $is spelled with $z $. We already know that the scheme spelled with $x $, $y $, $z $has been used and does not meet the requirements, which means that it is not necessary to spell $y $after $z $is spelled with $x $.

Thus, it can be found that some of the wooden sticks that have been spliced only need to be spliced from the last small wooden stick, because the previous small wooden sticks have had other schemes to try, and trying again is also a waste.

Because the small sticks have been arranged in order, there may be several sections of small sticks that are continuous and of the same length.

There is no need to try every stick of the same length. If one of them fails, it means that the remaining ones also fail.

For this, you can define a $fail $variable to store the small stick spliced last time. If the length of the small stick spliced next time is the same as that of the $fail $record, it will be skipped.

It is also possible to cut branches. When trying to splice the first small stick of an original stick, if the current answer is feasible, the remaining small sticks can be just spliced into the original stick.

But if the first stick fails to be spliced, it can be returned directly as a failure.

Why? Because this small wooden stick is bound to try to splice with other small wooden sticks. If they can't just splice into the original wooden stick, you can know that this is not the correct scheme and go back directly.

I think it's very cumbersome. Let's look at the code and understand it slowly:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n, m, lenght[101], l, r, vis[101], len, cnt;
bool cmp (int a, int b) {
	return a > b;
}
bool dfs (int deep, int cab, int pos) {
    if (deep > cnt) return true;
    if (cab == len) return dfs(deep + 1, 0, 1);
    int fail = 0;
    for (int i = pos; i <= n; i ++)
    	if (!vis[i] && lenght[i] + cab <= len && lenght[i] != fail) {
            vis[i] = 1;
            if (dfs(deep, cab + lenght[i], i + 1)) return true;
            vis[i] = 0;
            fail = lenght[i];
            if (!cab || cab + lenght[i] == len) return false;
        }
    return false;
}
int main () {
    scanf("%d", &m);
    for (int i = 1; i <= m; i ++) {
    	int x;
        scanf("%d", &x);
        if (x <= 50) {
        	lenght[++ n] = x;
        	l = max(lenght[n], l);
        	r += lenght[n];
        }
    }
    sort(lenght + 1, lenght + n + 1, cmp);
    for (len = l; len <= r; len ++)
        if (r % len == 0) {
        	cnt = r / len;
        	memset(vis, 0, sizeof(vis));
            if (dfs(1, 0, 1)) {
                printf("%d", len);
                break;
            }
        }
    return 0;
}

Cut cake:

I believe everyone can come up with the method of dfs, so we don't talk much and just start talking about pruning.

The topic requires the number of mouths that can be met at most. You might as well order the size of the mouth from small to large.

With greedy thinking, we can find that only the answer obtained from small to large to meet the mouth is the best.

First add up each cake to get the sum of $sum $, $sum $may not be greater than the largest mouth, so you can subtract the largest mouth one by one until the current largest mouth is less than sum, so you can reduce the number of mouths.

The answer to this question is obviously $mid $. As I said just now, it only needs to meet the people with a small $mid $in front of their mouth.

If you choose to feed a person with the $i $cake, the first $i $cake is either too small, or you have tried the cake and failed.

So if the next person's mouth is as big as this person's mouth, you can eat directly from the cake after $i $.

If you can't put the cake together, you may cut the cake and feed it to others. The rest can't even meet the smallest mouth, that is to say, the rest is wasted.

Then if $sum $minus the waste is smaller than the mouth of the first $mid $person, this search branch can return.

Therefore, we need to deal with prefix and before dfs.

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, sum, cake[101], tmp[101], mouth[1100], nex[1100], l, r, mid, waste;
bool dfs (int dep, int pos) {
	if (!dep) return true;
	if (sum - waste < nex[mid]) return false;
	for (int i = pos; i <= n; i ++)
		if (tmp[i] >= mouth[dep]) {
			tmp[i] -= mouth[dep];
			if (tmp[i] < mouth[1])
				waste += tmp[i];
			if (mouth[dep] == mouth[dep - 1]) {
				if (dfs(dep - 1, i)) return true;
			}else if (dfs(dep - 1, 1)) return true;
			if (tmp[i] < mouth[1]) waste -= tmp[i];
			tmp[i] += mouth[dep];
		}
	return false;
}
int main () {
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &cake[i]);
		sum += cake[i];
	}
	scanf("%d", &m);
	for (int i = 1; i <= m; i ++)
		scanf("%d", &mouth[i]);
	sort(mouth + 1, mouth + 1 + m);
	while (sum < mouth[m]) m --;
	for (int i = 1; i <= m; i ++)
		nex[i] += nex[i - 1] + mouth[i];
	r = m;
	while (l <= r) {
		waste = 0;
		for (int i = 1; i <= n; i ++)
			tmp[i] = cake[i];
		mid = (l + r) >> 1;
		if (dfs(mid, 1))
			l = mid + 1;
		else
			r = mid - 1;
	}
	printf("%d", r);
	return 0;
}

Breadth first search (BFS)

The same is search. The difference between breadth first search and depth first search is that it expands from one node.

Every time a node is connected to it, put the node into a queue until no node is connected to it. At this time, take out the node at the head of the queue to expand and repeat the previous operation. This process is like water spreading.

Therefore, queues are usually used in the code of breadth first search. The specific implementation is as follows:

void bfs () {
    queue<int> q;
    q.push(1);
    while (!q.empty()) {
    	int x = q.front();
    	q.pop();
    	cout << x << endl; //Output the point number currently traversed
		vis[x] = 1; //Mark that this point has been passed
		for (int i = 1; i <= n; i ++)
        	if (!vis[i] && (a[x][i] || a[i][x])) //adjacency matrix 
        		q.push(i);
	}
}

Let's do the same exercise: Find the number of cells

This is a very basic search topic.

Traverse each point. If this point is a bacterial point, start searching from this point and turn the bacterial points connected to it into ordinary points (that is, $0 $).

Every time you search the answer, you will add $1 $and output the answer at last.

code:

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int m, n, a[101][101], ans;
char ch[101];
int dx[4] = {1, 0, -1, 0},
	dy[4] = {0, 1, 0, -1};
struct P {
	int x, y;
};
void bfs (int x, int y) {
	queue<P> q;
	q.push((P){x, y});
	while (!q.empty()) {
		int ux = q.front().x, uy = q.front().y;
		q.pop();
		a[ux][uy] = 0;
		for (int i = 0; i <= 3; i ++) {
			int nx = ux + dx[i], ny = uy + dy[i];
			if (a[nx][ny] && nx >= 1 && nx <= m && ny >= 1 && ny <= n)
				q.push((P){nx, ny});
		}
	}
}


int main () {
	scanf("%d%d", &m, &n);
	for (int i = 1; i <= m; i ++) {
		scanf("%s", ch + 1);
		for (int j = 1; j <= n; j ++)
			a[i][j] = ch[j] - 48;
	}
	for (int i = 1; i <= m; i ++)
		for (int j = 1; j <= n; j ++)
			if (a[i][j]) {
				bfs(i, j);
				ans ++;
			}
	printf("%d", ans);	
	return 0;
}

Guangsou deformation

Double ended queue BFS

If the edge weights of some graphs correspond to $1 $or $0 $(not necessarily $1 $or $0 $, as long as there are only two possibilities), it is suitable to use double ended queue BFS.

Obviously, the edge with a weight of $0 $is better than the edge with a weight of $1 $, so if you walk the graph first from the points obtained from walking the $0 $edge, the result will generally be better.

Then you might as well put the points obtained by walking $0 $side at the head of the team and the other points at the end of the team.

In BFS, the element of the team head pops up. Because we put the points obtained by walking the $0 $side into the team head first first, the path is optimal every time.

In this way, we can only walk once at each point, and the time complexity is $O(N) $.

Example: Maintenance circuit

Solution:

The four points around each grid can be regarded as a node, and the number of nodes $n = (r + 1) * (c + 1) $.

If in the box at position $(ri, ci) $:

Point in the upper left corner: $(ri - 1) * (c + 1) + ci $;

Point in the upper right corner: $(ri - 1) * (c + 1) + ci + 1 $;

Point in the lower left corner: $ri * (c + 1) + ci $;

Point in the lower right corner: $ri * (c + 1) + ci + 1 $;

So you can show all the points.

Connect the points in the upper left corner and the lower right corner of the circuit of \ with a $0 $edge weight, and the points in the lower left corner and the upper right corner with a $1 $edge weight, representing a $1 $cost.

Similarly, the circuits of / are connected in the same way.

Finally, the minimum cost can be obtained by running the graph with the double ended queue BFS.

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 300010;
int t, r, c, que[N << 1], li, ri, tot, head[N], ver[N << 2], edge[N << 2], nex[N << 2], val[N], vis[N];
inline void add (int x, int y, int z) {
	ver[++ tot] = y;
	edge[tot] = z;
	nex[tot] = head[x];
	head[x] = tot;
}
int main () {
	scanf("%d", &t);
	while (t --) {
		memset(que, 0, sizeof(que));
		memset(head, 0, sizeof(head));
		memset(ver, 0, sizeof(ver));
		memset(edge, 0, sizeof(edge));
		memset(nex, 0, sizeof(nex));
		memset(val, 0x3f, sizeof(val));
		memset(vis, 0, sizeof(vis));
		li = N + 1;
		ri = N;
		tot = 0;
		scanf("%d%d", &r, &c);
		for (int i = 1; i <= r; i ++) {
			char s[N];
			scanf("%s", s + 1);
			for (int j = 1; j <= c; j ++) {
				if (s[j] == '\\') {
					add((i - 1) * (c + 1) + j, i * (c + 1) + j + 1, 0);
					add(i * (c + 1) + j + 1, (i - 1) * (c + 1) + j, 0);
					add((i - 1) * (c + 1) + j + 1, i * (c + 1) + j, 1);
					add(i * (c + 1) + j, (i - 1) * (c + 1) + j + 1, 1);
				} else {
					add((i - 1) * (c + 1) + j + 1, i * (c + 1) + j, 0);
					add(i * (c + 1) + j, (i - 1) * (c + 1) + j + 1, 0);
					add((i - 1) * (c + 1) + j, i * (c + 1) + j + 1, 1);
					add(i * (c + 1) + j + 1, (i - 1) * (c + 1) + j, 1);
				}
			}
		}
		if ((r + c) % 2) {
			printf("NO SOLUTION\n");
			continue;
		}	
		que[++ ri] = 1;
		val[1] = 0;
		while (ri >= li) {
			int x;
			x = que[ri --];
			for (int i = head[x]; i; i = nex[i]) {
				int y = ver[i], w = edge[i];
				if (val[x] + w < val[y]) {
					val[y] = val[x] + w;
					if (w)
						que[-- li] = y;
					else
						que[++ ri] = y;
				}
			}
		}
		printf("%d\n", val[(r + 1) * (c + 1)]);
	}
	return 0;
}

Bidirectional BFS

Bidirectional BFS means running BFS from both ends, which are usually the starting position and the final position.

The reason for using bi-directional BFS is that both ends can be moved. We can expand both ends as the starting point once respectively, and record the time spent from both ends to a certain position.

Finally, according to the requirements of the question, find the required answer.

Example: flood

Solution:

The two points in this problem, flood and painter, can be moved, so it is obvious that two-way BFS is needed.

We might as well start with the flood and carry out BFS again to find out the time required for the flood to reach different points.

After that, BFS is conducted again with the painter as the starting point. Similarly, the time required for the painter to reach different points can also be calculated.

Because the painter and the flood cannot be at the same point at the same time, we can judge whether the painter can go to this point based on the time they need at a certain point (if the painter spends less time, it means that the painter can reach this point before the flood comes).

At the same time, we also need to pay attention to the small details in the subject (such as the rock can not be reached).

code:

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
struct Pos {
    int x, y, z;
};
int r, c, Time[51][51];
int dx[4] = {0, 0, -1, 1},
    dy[4] = {1, -1, 0, 0};
queue <Pos> q1, q2;
char map[51][51];
int main () {
    scanf("%d%d", &r, &c);
    for (int i = 1; i <= r; i ++)
        scanf("%s", map[i] + 1);
    for (int i = 1; i <= r; i ++)
        for (int j = 1; j <= c; j ++) {
            if (map[i][j] == 'S')
                q1.push((Pos){i, j, 0});
            if (map[i][j] == '*')
                q2.push((Pos){i, j, 0});
        }
    while (!q2.empty()) {
        int ux = q2.front().x, uy = q2.front().y, uz = q2.front().z;
        q2.pop();
        Time[ux][uy] = uz;
        for (int i = 0; i <= 3; i ++) {
            int vx = ux + dx[i], vy = uy + dy[i];
            if (vx <= r && vx >= 1 && vy <= c && vy >= 1 && map[vx][vy] == '.') {
                q2.push((Pos){vx, vy, uz + 1});
                map[vx][vy] = '*';
            }
        }
    }
    while (!q1.empty()) {
        int ux = q1.front().x, uy = q1.front().y, uz = q1.front().z;
        q1.pop();
        for (int i = 0; i <= 3; i ++) {
            int vx = ux + dx[i], vy = uy + dy[i];
            if (vx <= r && vx >= 1 && vy <= c && vy >= 1 && (map[vx][vy] == '.' || map[vx][vy] == 'D' || map[vx][vy] == '*')) {
                if (map[vx][vy] == 'D') {
                    printf("%d", uz + 1);
                    return 0;
                }else if (map[vx][vy] == '.'){
                    q1.push((Pos){vx, vy, uz + 1});
                    map[vx][vy] = 'S';
                }else if (map[vx][vy] == '*')
                    if (Time[vx][vy] > uz + 1) {
                        q1.push((Pos){vx, vy, uz + 1});
                        map[vx][vy] = 'S';
                    }
            }
        }
    }
    printf("KAKTUS");
    return 0;
}

test questions

T1: Water diversion into the city

T2: Weight weighing

Problem solution

Water diversion into the city:

There are two results of this problem: water conservancy facilities can be established in arid areas and water conservancy facilities cannot be established.

It is very simple to judge whether water conservancy facilities can be established. You only need to DFS the water storage stations in the top row to see if there are any water storage stations in the bottom row that have not been visited.

If they have been visited, it is obvious that water conservancy facilities can be built in arid areas. Otherwise, several have not been visited.

But how to find the least water storage station to be built?

First of all, on the premise that water conservancy facilities can be established in all arid areas, the arid area city that any city can irrigate must be an interval.

In this way, in the first step of DFS, we can find the end point of the dry city that all water storage stations can irrigate.

Let's use $tmp $as the left boundary, and the initial value is $1 $.

Then we start to select the water storage station we need: first, the left end point to which it irrigates should be less than or equal to $tmp $, and then the right end point should be as large as possible. After finding the water storage station, we change $tmp $to the largest right endpoint $+ 1 $, and start looking for the next water storage station. Cycle through this operation until $tmp > m $.

In this process, the number of water storage stations we find is the final answer.

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 510;
int n, m, map[N][N], li[N][N], ri[N][N], vis[N][N], ans, tmp = 1, maxr;
int dx[4] = {1, 0, -1, 0},
	dy[4] = {0, 1, 0, -1};
void dfs (int x, int y) {
	vis[x][y] = 1;
	for (int i = 0; i < 4; i ++) {
		int nx = x + dx[i], ny = y + dy[i];
		if (nx < 0 || nx > n || ny < 0 || ny > m || map[nx][ny] >= map[x][y])
			continue;
		if (!vis[nx][ny]) dfs(nx, ny);
		li[x][y] = min(li[x][y], li[nx][ny]);
		ri[x][y] = max(ri[x][y], ri[nx][ny]);
	}
}
int main () {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= m; j ++)
			scanf("%d", &map[i][j]);
	memset(li, 0x3f, sizeof(li));
	memset(ri, 0, sizeof(ri));
	for (int i = 1; i <= m; i ++) {
		li[n][i] = i;
		ri[n][i] = i;
	}
	for (int i = 1; i <= m; i ++)
		if (!vis[1][i])
			dfs(1, i);
	for (int i = 1; i <= m; i ++)
		if (!vis[n][i])
			ans ++;
	if (ans) {
		printf("0\n%d", ans);
		return 0;
	}
	while (tmp <= m) {
		for (int i = 1; i <= m; i ++)
			if (li[1][i] <= tmp && ri[1][i] >= maxr)
				maxr = ri[1][i];
		ans ++;
		tmp = maxr + 1;
	}
	printf("1\n%d", ans);
	return 0;
}

Weight weighing:

Remove $m $weights from $n $weights, and the data range is very small. Obviously, it's OK to search violently.

$dep $indicates the number of weights being searched, and $x $indicates that several weights have been dropped before.

When $dep = n + 1 $means that all weights have been searched and $x = m $means that $m $weights have been rounded off, you can find the result of weight combination.

But how to find out how many kinds of weight combinations are obtained? Backpack!

Because each weight can only be taken once at most, we use 01 backpack to solve it.

$f[i] $indicates whether the number $I $can be combined. The initial value $f[0] = 1 $.

Then, from the weights we selected, once $f[j] = 1 $and $f[j + a[i]] = 0 $, it will be marked
$f[j + a[i]] = 1 $indicates that a new combination has been found.

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 25;
int n, m, a[N], vis[N], f[2010], ans;
void dfs (int dep, int x) {
    if (dep == n + 1 && x == m) {
        int num = 0, tmp = 0;
        memset(f, 0, sizeof(f));
        f[0] = 1;
        for (int i = 1; i <= n; i ++)
            if (!vis[i]) {
                for (int j = num; j >= 0; j --)
                    if (f[j] && !f[j + a[i]]) {
                        f[j + a[i]] = 1;
                        tmp ++;
                    }
                num += a[i];
            }
        ans = max(ans, tmp);
        return;
    }
    if (dep > n || x > m) return;
    vis[dep] = 1;
    dfs(dep + 1, x + 1);
    vis[dep] = 0;
    dfs(dep + 1, x);
}
int main () {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++)
        scanf("%d", &a[i]);
    dfs(1, 0);
    printf("%d", ans);
    return 0;
}

Topics: Algorithm