最新文章

Popcount问题及算法

Popcount问题及算法

问题描述

在使用状态压缩或树状数组(Binary Indexed Tree)的时候,经常涉及位运算的操作。其中一类问题被称为Popcount问题:对于一个无符号整数$x$,求二进制表示下的$x$(即$(x)_2$)中$1$的个数。

如:$(2)_{10}=(10)_2$,则$x=2$时答案为$1$;$(255)_{10}=(11111111)_2$,则$x=255$时答案为$8$。

算法分析

很暴力的暴力 $O(\log x)$

直接将$x$以二进制形式转换为字符串,按照字符串的方法处理即可。

// Code by KSkun, 2019/10
int popcount(unsigned int x) {
    char str[32];
    memset(str, 0, sizeof(str));
    itoa(x, str, 2); // 以二进制转换x至字符串str
    int counter = 0;
    for (int i = 0; i < 32 && str[i] != 0; i++) {
        if (str[i] == '1') counter++;
    }
    return counter;
}

不那么傻的暴力 $O(\log x)$

直接用位运算处理上面那个暴力的流程不好吗?

// Code by KSkun, 2019/10
int popcount(unsigned int x) {
    int counter = 0;
    while (x > 0) {
        counter += (x & 1);
        x >>= 1;
    }
    return counter;
}

不那么暴力的暴力 最差$O(\log x)$

我们想起了在树状数组中常用的lowbit函数,可以利用那个每次找到一个1然后把它删掉,循环进行这个操作直至所有的1都被统计了一遍。

// Code by KSkun, 2019/10
int popcount(unsigned int x) {
    int counter = 0;
    while (x > 0) {
        counter++;
        x -= (x & -x);
    }
    return counter;
}

更好的算法:Shift and Add $O(\log \log x)$

这里可以采用类似分治的考虑方法。

我们将数字$x$按二进制拆分为2位为一组的形式,例如,令$x=(\overline{abcdefgh})_2$,拆分为$\overline{ab \ cd \ ef \ gh}$。

对于每一组的两个位,例如最高位一组的$a$和$b$,它们的取值非0即1,只需要把这两位上的值简单相加即可得到这两位的统计结果。由于$1+1=2=(10)_2$,最多也只需占用2位来存储结果,因此不妨用这两位之前的空间来存储结果即可。对于“将相邻两位加起来,并存储在原来的这两位的位置上”这一操作,我们可以先对原数$x$与$(01010101)_2$做按位与运算,再用原数右移一位的结果与$(01010101)_2$做按位与运算,再将两个结果加起来即得到这一步的结果。

以最开始的$x=(\overline{abcdefgh})_2$为例,首先做与运算得到$x_1=\overline{0b0d0f0h}$,然后右移一位做与运算得到$x_2=\overline{0a0c0e0g}$,这里设相加的结果为$x’=x_1+x_2=\overline{ABCDEFGH}$。

接下来,我们需要将$\overline{AB \ CD \ EF \ GH}$中每一组位置中的数值相加,即$\overline{AB} + \overline{CD} + \overline{EF} + \overline{GH}$,这里也可以用上面类似的方法计算,只是需要两位两位地移动。即:$\overline{00CD00GH}+\overline{00AB00EF}=\overline{A’B’C’D’ \ E’F’G’H’}$。接下来再进行一次$\overline{0000E’F’G’H’}+\overline{0000A’B’C’D’}$就可以得到结果了。

这种方法每次对相邻的两组做二分的分治,逐层合并结果(将相邻两组相加),最后得到结果。这是一种很巧妙的分治方法。

由于unsigned int本身的长度有限,可以直接以长度为总长度,就有了下面这种很tricky的实现。

// Code from http://leexh.com/blog/2014/10/25/popcount-problem/
// Author: lixinhe
// Adjusted to unsigned int
const unsigned int m1 = 0x55555555; //binary: 0101...
const unsigned int m2 = 0x33333333; //binary: 00110011..
const unsigned int m4 = 0x0f0f0f0f; //binary:  4 zeros,  4 ones ...
const unsigned int m8 = 0x00ff00ff; //binary:  8 zeros,  8 ones ...
const unsigned int m16 = 0x0000ffff; //binary: 16 zeros, 16 ones ...

int popcount(unsigned int x) {
    x = (x & m1) + ((x >> 1) & m1);
    x = (x & m2) + ((x >> 2) & m2);
    x = (x & m4) + ((x >> 4) & m4);
    x = (x & m8) + ((x >> 8) & m8);
    x = (x & m16) + ((x >> 16) & m16);
    x = (x & m32) + ((x >> 32) & m32);
    return x;
}

参考资料

[CF7E]Defining Macros 题解

[CF7E]Defining Macros 题解

题目地址:Codeforces:Problem – 7E – Codeforces、洛谷:CF7E Defining Macros – 洛谷 | 计算机科学教育新生态

题目描述

Most C/C++ programmers know about excellent opportunities that preprocessor #define directives give; but many know as well about the problems that can arise because of their careless use.

In this problem we consider the following model of #define constructions (also called macros). Each macro has its name and value. The generic syntax for declaring a macro is the following:

#define macro_name macro_value

After the macro has been declared, “macro_name” is replaced with “macro_value” each time it is met in the program (only the whole tokens can be replaced; i.e. “macro_name” is replaced only when it is surrounded by spaces or other non-alphabetic symbol). A “macro_value” within our model can only be an arithmetic expression consisting of variables, four arithmetic operations, brackets, and also the names of previously declared macros (in this case replacement is performed sequentially). The process of replacing macros with their values is called substitution.

One of the main problems arising while using macros — the situation when as a result of substitution we get an arithmetic expression with the changed order of calculation because of different priorities of the operations.

Let’s consider the following example. Say, we declared such a #define construction:

#define sum x + y

and further in the program the expression “2 * sum” is calculated. After macro substitution is performed we get “2 * x + y”, instead of intuitively expected “2 * (x + y)”.

Let’s call the situation “suspicious”, if after the macro substitution the order of calculation changes, falling outside the bounds of some macro. Thus, your task is to find out by the given set of #define definitions and the given expression if this expression is suspicious or not.

Let’s speak more formally. We should perform an ordinary macros substitution in the given expression. Moreover, we should perform a “safe” macros substitution in the expression, putting in brackets each macro value; after this, guided by arithmetic rules of brackets expansion, we can omit some of the brackets. If there exist a way to get an expression, absolutely coinciding with the expression that is the result of an ordinary substitution (character-by-character, but ignoring spaces), then this expression and the macros system are called correct, otherwise — suspicious.

Note that we consider the “/” operation as the usual mathematical division, not the integer division like in C/C++. That’s why, for example, in the expression “a*(b/c)” we can omit brackets to get the expression “a*b/c”.

题意简述

给定$n$条宏定义和一个表达式,求编译时给定表达式是否安全。

表达式的安全指宏展开时不会出现与预期不符的结果,例如定义宏#define sum x+y后,对表达值sum*z展开,得到的结果并非(x+y)*z而是x+(y*z)

更加详细的说明请参考原题面。

输入输出格式

输入格式:

The first line contains the only number n (0 ≤ n ≤ 100) — the amount of #define constructions in the given program.

Then there follow n lines, each of them contains just one #define construction. Each construction has the following syntax:

#define name expression

where

  • name — the macro name,
  • expression — the expression with which the given macro will be replaced. An expression is a non-empty string, containing digits,names of variables, names of previously declared macros, round brackets and operational signs +-*/. It is guaranteed that the expression (before and after macros substitution) is a correct arithmetic expression, having no unary operations. The expression contains only non-negative integers, not exceeding 109.

All the names (#define constructions’ names and names of their arguments) are strings of case-sensitive Latin characters. It is guaranteed that the name of any variable is different from any #define construction.

Then, the last line contains an expression that you are to check. This expression is non-empty and satisfies the same limitations as the expressions in #define constructions.

The input lines may contain any number of spaces anywhere, providing these spaces do not break the word “define” or the names of constructions and variables. In particular, there can be any number of spaces before and after the “#” symbol.

The length of any line from the input file does not exceed 100 characters.

输出格式:

Output “OK”, if the expression is correct according to the above given criterion, otherwise output “Suspicious”.

输入输出样例

输入样例#1:

1
#define sum x + y
1 * sum

输出样例#1:

Suspicious

输入样例#2:

1
#define sum  (x + y)
sum - sum

输出样例#2:

OK

输入样例#3:

4
#define sum  x + y
#define mul  a * b
#define div  a / b
#define expr sum + mul * div * mul
expr

输出样例#3:

OK

输入样例#4:

3
#define SumSafe   (a+b)
#define DivUnsafe  a/b
#define DenominatorUnsafe  a*b
((SumSafe) + DivUnsafe/DivUnsafe + x/DenominatorUnsafe)

输出样例#4:

Suspicious

题解

一看就是一道细节巨多很难写的hack大模拟题。我们讨论所有表达式的可能性:

  1. 不包含任何运算符 – 取决于表达式展开后是否安全,若只包含一个变量则安全
  2. 被一对括号包围 – 取决于括号内的表达式是否安全
  3. 计算顺序最末的符号为加减号 – 出现在减号后面或者乘除号的前面或后面都不安全,其余情况安全
  4. 计算顺序最末的符号为乘除号 – 出现在除号后面不安全,其余情况安全

因此可以按照安全程度对安全性分类:不安全、在减号后面或者乘除号的前面或后面都不安全 、在除号后面不安全、安全,分别用0 1 2 3代表这四类。

考虑对一个表达式进行分治解决:找到末尾第一个加减号,将前半部分和后半部分分治求解,并组合分治的结果,如果前部分或后部分不安全,或符号为减号的情况下后部分不安全,则整个式子不安全,否则安全性为1;之后再找末尾第一个乘除号,如果前或后部分安全性为0或1,或符号为除号的情况下后部分不安全,则整个式子不安全,否则式子安全性为2;被括号括起来的式子,只有0和3两种安全性;只包含一个变量名的表达式安全性为3……以此类推,处理完所有的情况即可得到正解。

另外输入可能存在多余的空格,需要及时除掉,#define也不一定连在一起,需要找define处理。

这个题卡了好久,第一次写了纯暴力判断TLE 134,后来参考了题解[Codeforces 7E] Defining Macros – NewErA – 博客园(感谢原作者)重构代码,又因为细节WA了好多次,今天终于解决了,以下是提交记录截图。

cf7e submissions - [CF7E]Defining Macros 题解
本题提交记录截图

代码

// Code by KSkun, 2019/8
#include <iostream>
#include <algorithm>
#include <string>
#include <map>

const int MAXN = 105;

struct Macro {
	std::string name, expr;
} m[MAXN];

int n;
std::string expr;
std::map<std::string, int> lvl;

inline bool isop(char c) {
	return c == '+' || c == '-' || c == '*' || c == '/';
}

inline void parse(std::string line, Macro& m) {
	int res = line.find("define"), l = res + 6, r, len = line.length();
	while(l < len && line[l] == ' ') l++; r = l;
	while(r < len && line[r] != ' ') r++;
	m.name = line.substr(l, r - l);
	while(r < len && line[r] == ' ') r++;
	m.expr = line.substr(r);
}

int process(std::string str) {
	// remove front/back blanks
	int l = 0, r = str.length() - 1;
	while(l < str.length() && str[l] == ' ') l++;
	while(r >= 0 && str[r] == ' ') r--;
	str = str.substr(l, r - l + 1);

	// default
	int cnt = 0;
	for(int i = str.length() - 1; i >= 0; i--) {
		if(str[i] == ')') cnt++;
		if(str[i] == '(') cnt--;
		if((str[i] == '+' || str[i] == '-') && cnt == 0) {
			int res1 = process(str.substr(0, i)), 
				res2 = process(str.substr(i + 1, str.length() - i + 1));
			if(res1 == 0 || res2 == 0) return 0;
			if(str[i] == '+') return 1;
			if(str[i] == '-') return res2 == 1 ? 0 : 1;
		}
	}

	cnt = 0;
	for(int i = str.length() - 1; i >= 0; i--) {
		if(str[i] == ')') cnt++;
		if(str[i] == '(') cnt--;
		if((str[i] == '*' || str[i] == '/') && cnt == 0) {
			int res1 = process(str.substr(0, i)),
				res2 = process(str.substr(i + 1, str.length() - i + 1));
			if(res1 <= 1 || res2 <= 1) return 0;
			if(str[i] == '*') return 2;
			if(str[i] == '/') return res2 == 2 ? 0 : 2;
		}
	}
	
	// brakets surrounded
	if(str.front() == '(' && str.back() == ')')
		return process(str.substr(1, str.length() - 2)) == 0 ? 0 : 3;

	// contain no operator
	return lvl.find(str) != lvl.end() ? lvl[str] : 3;
}

int main() {
	std::cin >> n;
	std::string line;
	std::getline(std::cin, line);
	for(int i = 1; i <= n; i++) {
		std::getline(std::cin, line);
		parse(line, m[i]);
		lvl[m[i].name] = process(m[i].expr);
	}
	getline(std::cin, expr);
	int res = process(expr);
	if(res == 0) puts("Suspicious");
	else puts("OK");
	return 0;
}
[CF6D]Lizards and Basements 2 题解

[CF6D]Lizards and Basements 2 题解

题目地址:Codeforces:Problem – 6D – Codeforces、洛谷:CF6D Lizards and Basements 2 – 洛谷 | 计算机科学教育新生态

题目描述

This is simplified version of the problem used on the original contest. The original problem seems to have too difiicult solution. The constraints for input data have been reduced.

Polycarp likes to play computer role-playing game «Lizards and Basements». At the moment he is playing it as a magician. At one of the last levels he has to fight the line of archers. The only spell with which he can damage them is a fire ball. If Polycarp hits the i-th archer with his fire ball (they are numbered from left to right), the archer loses a health points. At the same time the spell damages the archers adjacent to the i-th (if any) — they lose b (1 ≤ b < a ≤ 10) health points each.

As the extreme archers (i.e. archers numbered 1 and n) are very far, the fire ball cannot reach them. Polycarp can hit any other archer with his fire ball.

The amount of health points for each archer is known. An archer will be killed when this amount is less than 0. What is the minimum amount of spells Polycarp can use to kill all the enemies?

Polycarp can throw his fire ball into an archer if the latter is already killed.

题意简述

有一排共$n$个怪物从左到右依次编号,你可以攻击编号为$2 \sim n-1$的怪物,一次攻击会对攻击怪物造成$a$点伤害,对该怪物两边的怪物造成$b$点伤害,已知每个怪物的生命值,怪物生命值小于0时视为已击败,求击败所有怪物的最少攻击次数及方案。

输入输出格式

输入格式:
The first line of the input contains three integers n, a, b (3 ≤ n ≤ 10; 1 ≤ b < a ≤ 10). The second line contains a sequence of n integers — h1, h2, …, hn (1 ≤ hi ≤ 15), where hi is the amount of health points the i-th archer has.

输出格式:
In the first line print t — the required minimum amount of fire balls.
In the second line print t numbers — indexes of the archers that Polycarp should hit to kill all the archers in t shots. All these numbers should be between 2 and n - 1. Separate numbers with spaces. If there are several solutions, output any of them. Print numbers in any order.

输入输出样例

输入样例#1:

3 2 1
2 2 2

输出样例#1:

3
2 2 2 

输入样例#2:

4 3 1
1 4 1 1

输出样例#2:

4
2 2 3 3 

题解

容易发现的性质是,一个位置上的伤害值只取决于本身、左边和右边三个位置上的攻击次数。考虑动态规划解决本题,令$f(i, j, k)$表示考虑到怪物$i$,且怪物$i$被攻击了$k$次、怪物$i-1$被攻击了$j$次,把$[1, i-1]$内的怪物全部击败的攻击最少次数,由于一定要击败怪物$i$,枚举攻击怪物$i$的次数转移即可,即
$$ f(i, j, k) = \min_{bm+aj+bk>h_{i-1}}\{ f(i-1, m, j) + k \} $$

由于我们不能攻击$n$怪物,这个DP只能最多做到第一维为$n-1$的位置,剩下的需要我们自行判断是否增加几次攻击把怪物$n$击败。只需要在0、击败怪物$n-1$的最少次数、击败怪物$n$的最少次数里取一个max就可以了。

代码在不断的改动中写的比较丑,就凑合着看吧。复杂度$O(nk^3)$,其中$k$指每个位置攻击次数的可能最大值,例如代码中取了$200$。

代码

// Code by KSkun, 2019/8
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
	static char buf[100000], * p1 = buf, * p2 = buf;
	return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
		? EOF : *p1++;
}

inline LL readint() {
	LL res = 0, neg = 1; char c = fgc();
	for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
	for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
	return res * neg;
}

inline char readsingle() {
	char c;
	while(!isgraph(c = fgc())) {}
	return c;
}

const int MAXN = 20;

int n, a, b, h[MAXN], f[MAXN][205][205], prei[MAXN][205][205];

int main() {
	n = readint(); a = readint(); b = readint();
	for(int i = 1; i <= n; i++) {
		h[i] = readint() + 1;
	}
	memset(f, 0x3f, sizeof(f));
	for(int i = 0; i <= 200; i++) {
		if(i * b >= h[1]) f[2][0][i] = i;
	}
	for(int i = 3; i < n; i++) {
		for(int j = 0; j <= 200; j++) {
			for(int k = 0; k <= 200; k++) {
				for(int ii = 0; ii <= 200; ii++) {
					if(ii * b + j * a + k * b >= h[i - 1] && f[i][j][k] > f[i - 1][ii][j] + k) {
						f[i][j][k] = f[i - 1][ii][j] + k;
						prei[i][j][k] = ii;
					}
				}
			}
		}
	}
	int ans = 2e9, ai, aj;
	for(int i = 0; i <= 200; i++) {
		for(int j = 0; j <= 200; j++) {
			int res = f[n - 1][i][j] +
				std::max({0, (int)ceil(double(h[n - 1] - i * b - j * a) / a), (int)ceil(double(h[n] - j * b) / b)});
			if(ans > res) {
				ans = res; ai = i; aj = j;
			}
		}
	}
	printf("%d\n", ans);
	for(int i = 1; i <= ans - f[n - 1][ai][aj]; i++) {
		printf("%d ", n - 1);
	}
	int ni = ai, nj = aj, nn = n - 1;
	while(nn != 1) {
		for(int i = 1; i <= nj; i++) printf("%d ", nn);
		int onj = nj; nj = ni; ni = prei[nn][ni][onj]; nn--;
	}
	return 0;
}