果然乱搞能力才是王道!
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6299
题目来源:2018 HDU 多校第一场
给你 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 个括号串,每个括号串长度为 ∣ s i ∣ |s_i| ∣si∣。
你可以任意改变这些括号串的顺序,之后拼成一个新串。
问最大可能得到的新串中匹配括号子序列的长度。
共有 T T T 组读入,题目保证所有的括号串长度之和不超过 5 ⋅ 1 0 6 5 \cdot 10^6 5⋅106。
首先,显而易见的是,每一个小的括号串中已经匹配的括号子序列可以直接加到答案中。
因此,我们遍历所有的括号串,将每个串用栈将已经匹配的括号子序列去除,并记录去除的总长。
之后,每个串只剩下四种情况:
((((((
形式(只有左括号);)))((((((
(左括号比右括号多);))))))(((
(右括号比左括号多);))))))
(只有右括号)。将处理之后的所有括号串按照上面的类型顺序为第一关键字排序。
如果类型相同,对于第 1 类和第 4 类,无需处理,其他类型处理方式如下”:
我们的原则是左边尽量不要有残留的无法匹配的右括号,右边尽量不要有残留的无法匹配的左括号。这样排序,可以保证左边残留的右括号和右边残留的左括号尽量少。
排序之后,将所有的括号串拼接起来,再次用栈进行括号匹配,并计算答案。
最后不要忘记加上最初算出的那部分答案。
时间复杂度: O ( n log n + ∑ ∣ s i ∣ ) O(n \log n + \sum |s_i|) O(nlogn+∑∣si∣)。
int n, delta;
string input[maxn];
struct node
{
string str;
int type, cnta, cntb;
} data[maxn];
bool operator < (node a, node b)
{
if(a.type != b.type) return a.type < b.type;
if(a.type == 1) return a.cntb < b.cntb;
else if(a.type == 2) return a.cnta > b.cnta;
}
void init()
{
delta = 0;
for(int i = 0; i <= n; i++)
{
data[i].cnta = 0;
data[i].cntb = 0;
data[i].str = "";
}
}
int main()
{
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--)
{
cin >> n;
init();
for(int i = 1; i <= n; i++) cin >> input[i];
for(int i = 1; i <= n; i++)
{
stack<char> st;
bool mark[2];
memset(mark, false, sizeof(mark));
int length = input[i].length();
for(int j = 0; j < length; j++)
{
if(st.empty()) st.push(input[i][j]);
else if(st.top() == '(' && input[i][j] == ')')
{
st.pop();
delta += 2;
}
else st.push(input[i][j]);
}
stack<char> st2;
while(!st.empty())
{
st2.push(st.top());
st.pop();
}
while(!st2.empty())
{
data[i].str += st2.top();
st2.pop();
}
length = data[i].str.length();
for(int j = 0; j < length; j++)
{
if(data[i].str[j] == '(')
{
mark[0] = true;
data[i].cnta++;
}
else
{
mark[1] = true;
data[i].cntb++;
}
}
if(mark[0] && mark[1])
{
if(data[i].cnta >= data[i].cntb) data[i].type = 1;
else data[i].type = 2;
}
else if(mark[0]) data[i].type = 0;
else if(mark[1]) data[i].type = 3;
}
sort(data + 1, data + 1 + n);
string str;
for(int i = 1; i <= n; i++) str += data[i].str;
stack<char> st;
int length = str.length();
for(int i = 0; i < length; i++)
{
if(st.empty()) st.push(str[i]);
else
{
if(st.top() == '(' && str[i] == ')')
{
delta += 2;
st.pop();
}
else st.push(str[i]);
}
}
cout << delta << endl;
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6300
题目来源:2018 HDU 多校第一场
在一个二维坐标平面上,给你 3 n ( 1 ≤ n ≤ 1000 ) 3n(1 \le n \le 1000) 3n(1≤n≤1000) 个点,保证不存在三点共线的情况。现在要你构造 n n n 个不相交的三角形。
不要把这个题想的太复杂!
只需将所有点按照 x x x 值为第一关键字, y y y 值为第二关键字排序,之后连续三个连续三个地构造就可以了。
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6301
题目来源:2018 HDU 多校第一场
给定 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 和 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1≤m≤105),现在需要构造一个长度为 n n n 的正数序列 a a a,并满足 m m m 个条件。
第 i i i 个条件用 l i , r i l_i, r_i li,ri 表示,表示 a [ l . . r ] a[l..r] a[l..r] 中间的数字不可以重复。
对于每一个 l l l,计算出以 l l l 为左端点的区间中,最大的 r r r。
基于此,计算出新的极大区间,然后我们只处理这些极大区间即可。
基本的思路就是对于当前的极大区间,每次填入数字为当前极大区间中未出现的最小数字。
具体实现可参考下面代码。
时间复杂度: O ( n ) O(n) O(n)。
struct node
{
int l, r;
} interval[maxn];
int n;
int a[maxn], maxpos[maxn];
bool vis[maxn];
void init()
{
for(int i = 0; i <= n; i++)
{
maxpos[i] = i;
a[i] = 0;
vis[i] = false;
}
}
int main()
{
int t, m, l, r;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &l, &r);
maxpos[l] = max(maxpos[l], r);
}
for(int i = 1; i <= n; i++)
{
interval[i] = (node){i, maxpos[i]};
}
l = 1, r = 1;
int pos = 1;
int num = 1;
while(r <= n)
{
while(vis[num]) num++;
vis[num] = true;
a[r] = num;
r++;
if(r > interval[pos].r)
{
while(r > interval[pos].r && pos <= n) pos++;
num = 1;
while(l < interval[pos].l)
{
vis[a[l]] = false;
l++;
}
}
}
for(int i = 1; i <= n; i++)
{
if(i > 1) printf(" ");
printf("%d", a[i]);
}
printf("\n");
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6315
题目来源:2018 HDU 多校第二场
给你两个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 的序列 a a a 和 b b b,其中 b b b 为一个 [ 1 , n ] [1,n] [1,n] 的排列,并保持不变, a a a 序列的值最初为全 0 0 0。
现在有 q ( 1 ≤ q ≤ 1 0 5 ) q(1 \le q \le 10^5) q(1≤q≤105) 次查询,查询分为两种类型:
题目共有不超过 5 5 5 组输入。
我们可以把对 a a a 区间加的操作转化为对 b b b 进行区间减的操作(当某个位置减到 0 0 0 就将当前位置的答案加 1 1 1,并将当前位置的 b i b_i bi 恢复到最初的 b i b_i bi 值)。
这样做的话,查询进行简单的区间和查询即可。
如果我们细心观察,会发现 b i b_i bi 变成 0 0 0 的次数最多只有
∑ i = 1 n ⌊ q i ⌋ = O ( q log n ) \sum_{i=1}^n \lfloor \frac{q}{i} \rfloor = O(q \log n) i=1∑n⌊iq⌋=O(qlogn)
次,因此加上线段树本身的时间复杂度,处理 b b b 的总时间复杂度为 O ( q log 2 n ) O(q \log^2 n) O(qlog2n) 。这个复杂度是可以接受的。
因此线段树需要维护三个值:
对于第 2 种查询,直接查询当前区间的 v a l u e value value 之和即可。
对于第 1 种查询,先将区间内 m a x v a l u e maxvalue maxvalue 和 m i n v a l u e minvalue minvalue 都减 1 1 1,之后进行最上面所述的操作。
ll b[maxn];
struct node
{
ll maxvalue, minvalue, lazy;
ll value;
} tree[4 * maxn];
void pushup(int id)
{
tree[id].maxvalue = max(tree[id << 1].maxvalue, tree[id << 1 | 1].maxvalue);
tree[id].minvalue = min(tree[id << 1].minvalue, tree[id << 1 | 1].minvalue);
tree[id].value = tree[id << 1].value + tree[id << 1 | 1].value;
}
void pushdown(int id)
{
if(tree[id].lazy)
{
tree[id << 1].maxvalue += tree[id].lazy;
tree[id << 1 | 1].maxvalue += tree[id].lazy;
tree[id << 1].minvalue += tree[id].lazy;
tree[id << 1 | 1].minvalue += tree[id].lazy;
tree[id << 1].lazy += tree[id].lazy;
tree[id << 1 | 1].lazy += tree[id].lazy;
pushup(id);
tree[id].lazy = 0;
}
}
void build(int id, int l, int r)
{
tree[id].lazy = 0;
if(l == r)
{
tree[id].maxvalue = tree[id].minvalue = b[l];
tree[id].value = 0;
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
pushup(id);
return;
}
void update(int id, int ns, int nt, int qs, int qt, ll value)
{
if(nt < qs || ns > qt) return;
if(ns >= qs && nt <= qt)
{
tree[id].maxvalue += value;
tree[id].minvalue += value;
tree[id].lazy += value;
return;
}
pushdown(id);
int mid = (ns + nt) >> 1;
update(id << 1, ns, mid, qs, qt, value);
update(id << 1 | 1, mid + 1, nt, qs, qt, value);
pushup(id);
return;
}
ll query(int id, int ns, int nt, int qs, int qt)
{
if(nt < qs || ns > qt) return 0;
if(ns >= qs && nt <= qt) return tree[id].value;
pushdown(id);
int mid = (ns + nt) >> 1;
return query(id << 1, ns, mid, qs, qt) + query(id << 1 | 1, mid + 1, nt, qs, qt);
}
void operate(int id, int ns, int nt, int qs, int qt)
{
if(nt < qs || ns > qt) return;
if(ns == nt)
{
tree[id].maxvalue = tree[id].minvalue = b[ns];
tree[id].value++;
return;
}
pushdown(id);
int mid = (ns + nt) >> 1;
if(tree[id << 1].minvalue == 0) operate(id << 1, ns, mid, qs, qt);
if(tree[id << 1 | 1].minvalue == 0) operate(id << 1 | 1, mid + 1, nt, qs, qt);
pushup(id);
return;
}
int main()
{
int n, q, l, r;
char op[15];
while(~scanf("%d%d", &n, &q))
{
for(int i = 1; i <= n; i++) scanf("%lld", &b[i]);
build(1, 1, n);
while(q--)
{
scanf("%s", op);
scanf("%d%d", &l, &r);
if(op[0] == 'a')
{
update(1, 1, n, l, r, -1);
operate(1, 1, n, l, r);
}
else
{
printf("%lld\n", query(1, 1, n, l, r));
}
}
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6319
题目来源:2018 HDU 多校第三场 - A 题
给你一个长度为 n n n 的序列 a ( 0 ≤ a i ≤ 1 0 9 ) a(0 \le a_i \le 10^9) a(0≤ai≤109),对于每个 l ( 1 ≤ l ≤ n − m + 1 ) l(1 \le l \le n - m + 1) l(1≤l≤n−m+1),求出区间 [ l , l + m − 1 ] [l, l + m - 1] [l,l+m−1] 中的最大值 m a x r a t i n g maxrating maxrating 和最大值的变化次数 c o u n t count count。
最终输出两个值 A A A 和 B B B,定义为
KaTeX parse error: No such environment: eqnarray* at position 8: \begin{̲e̲q̲n̲a̲r̲r̲a̲y̲*̲}̲ A&=&\sum_{i=…
做题的时候,尝试了正向去做,会发现维护滑动窗口的最大值和最大值变化次数很困难。
实际上,我们可以倒着考虑。先考虑最右边的窗口,然后不断向左滑动。
之后不断维护一个单调递减的栈(从 a a a 的右侧向左向栈中加元素),则每一个窗口的最大值就是当前栈底元素的值,最大值的变化次数就是当前栈的大小。
可以这样维护的原因是,右边的最大值一定存在于历史最大值序列中,同时单调栈的操作恰好把那些没有成为过最大值的值都去掉了。
时间复杂度: O ( n ) O(n) O(n)。
ll a[maxn];
bool mark[maxn];
struct node
{
int id;
ll value;
} st[maxn];
int main()
{
int t, n, m, k;
ll p, q, r, mod;
scanf("%d", &t);
while(t--)
{
scanf("%d%d%d%lld%lld%lld%lld", &n, &m, &k, &p, &q, &r, &mod);
for(int i = 1; i <= n; i++) mark[i] = false;
for(int i = 1; i <= k; i++) scanf("%lld", &a[i]);
for(int i = k + 1; i <= n; i++)
{
a[i] = (p * a[i - 1] + q * 1LL * i + r) % mod;
}
int l = 1, r = 0;
st[0] = (node){0, 0x3f3f3f3f3f3f3f3f};
ll A = 0, B = 0;
for(int i = n; i >= n - m + 1; i--)
{
while(a[i] >= st[r].value)
{
mark[st[r].id] = true;
st[r] = (node){r, 0x3f3f3f3f3f3f3f3f};
r--;
}
st[++r] = (node){i, a[i]};
}
A += (st[l].value ^ (1LL * (n - m + 1)));
B += ((1LL * (r - l + 1)) ^ (1LL * (n - m + 1)));
for(int i = n - m; i >= 1; i--)
{
int j = i + m;
if(!mark[j])
{
st[l] = (node){l, 0x3f3f3f3f3f3f3f3f};
l++;
}
while(a[i] >= st[r].value && r >= l)
{
mark[st[r].id] = true;
st[r] = (node){r, 0x3f3f3f3f3f3f3f3f};
r--;
}
st[++r] = (node){i, a[i]};
A += (st[l].value ^ (1LL * i));
B += ((1LL * (r - l + 1)) ^ (1LL * i));
}
printf("%lld %lld\n", A, B);
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6324
题目来源:2018 HDU 多校第三场 - F 题
有一棵 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 个节点的树,每个点都有一个权值 w i ( 1 ≤ w i ≤ 1 0 9 ) w_i(1 \le w_i \le 10^9) wi(1≤wi≤109)。
现在 quailty
和 tangjz
两个人在树上玩一个游戏,quailty
先手。
先手可以选择一些不相邻的点,其得分 A A A 为这些点权值的异或和。
后手会将剩余的点拿走,其得分 B B B 为剩余点权值的异或和。
最终得分大的一方获胜,也可能存在平局。
如果两个人都以最优策略进行,问比赛结果。
注意到 A xor B A \text{ xor } B A xor B 恰好为所有 w i w_i wi 的异或和。
如果这个总的异或和为 0 0 0 的话,表明无论如何都有 A = B A=B A=B,因此此时为平局。
否则,quailty
只需选择 1 1 1 的最高位最高的数字即可获胜。
时间复杂度: O ( n ) O(n) O(n)。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6396
题目来源:2018 HDU 多校第七场
你有 k ( 1 ≤ k ≤ 5 ) k(1 \le k \le 5) k(1≤k≤5) 种属性 v 1 , v 2 , ⋯ , v k v_1,v_2,\cdots,v_k v1,v2,⋯,vk。
现在有 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 个怪物,每个怪物 i i i 会有 k k k 个防御值 a i , k ( 0 ≤ a i , k ≤ 1 0 9 ) a_{i,k}(0 \le a_{i,k} \le 10^9) ai,k(0≤ai,k≤109) 和 k k k 个经验值 b i , k ( 0 ≤ b i , k ≤ 1 0 9 ) b_{i,k}(0 \le b_{i,k} \le 10^9) bi,k(0≤bi,k≤109)。
你可以打败第 i i i 个怪物的条件是对于任意的 j ∈ [ 1 , k ] j \in [1,k] j∈[1,k],都有 v j ≥ a i , j v_j \ge a_{i,j} vj≥ai,j。打败第 i i i 个怪物之后,你的属性 v j v_j vj 将增加 b i , j b_{i,j} bi,j。
输出最多可以打败的怪物数量以及每个属性最终可以达到的最大值。
题目保证不可能爆 long long
。
建立 k k k 个优先队列,第 i i i 个优先队列存储怪物的第 i i i 个防御值,防御值小的在队首。
之后将所有怪物的第 1 1 1 个防御值全部丢进第 1 1 1 个队列中,之后从 1 1 1 到 k k k 处理每个队列。
处理队列时,如果当前防御值可以达到要求,就将当前怪物的下一个防御值丢到下一个优先队列中;如果在达到要求时,已经是最后一个队列了,就更新答案和经验值。
处理完最后一个队列之后,还要回到第一个队列重新开始处理,因为这时经验值已经发生变化,可能会有新的怪物被打败了。
这个过程进行到没有怪物可以打败为止。
另外本题需要快读,否则会因为读入而超时。
时间复杂度: O ( k ⋅ n log n ) O(k \cdot n \log n) O(k⋅nlogn)。
发现这个代码是自己去年写的,看起来好稚嫩啊!
const int MAXN = 100005;
int v[8], a[MAXN][16], k;
struct node
{
int value, id;
bool operator<(const node &b) const
{
return value > b.value;
}
};
struct ios
{
inline char read()
{
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp>
inline ios &operator>>(_Tp &x)
{
static char c11, boo;
for (c11 = read(), boo = 0; !isdigit(c11); c11 = read())
{
if (c11 == -1)
return *this;
boo |= c11 == '-';
}
for (x = 0; isdigit(c11); c11 = read())
x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} io;
priority_queue<node> que[5], emp;
int main(void)
{
int t, n, tot;
io >> t;
while (t--)
{
tot = 0;
io >> n >> k;
for (int i = 0; i < k; i++)
io >> v[i];
for (int i = 0; i < n; i++)
for (int j = 0; j < 2 * k; j++)
io >> a[i][j];
for (int i = 0; i < k; i++)
que[i] = emp;
for (int i = 0; i < n; i++)
{
node p;
p.value = a[i][0];
p.id = i;
que[0].push(p);
}
while (true)
{
bool isFail = true;
for (int i = 0; i < k; i++)
{
while (!que[i].empty())
{
node f = que[i].top();
if (v[i] >= f.value)
{
isFail = false;
if (i < k - 1)
{
f.value = a[f.id][i + 1];
que[i + 1].push(f);
}
else
{
tot++;
for (int j = 0; j < k; j++)
v[j] += a[f.id][j + k];
}
que[i].pop();
}
else
{
break;
}
}
}
if (isFail)
break;
}
printf("%d\n", tot);
for (int i = 0; i < k; i++)
{
if (i > 0)
printf(" ");
printf("%d", v[i]);
}
printf("\n");
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6415
题目来源:2018 HDU 多校第九场
给定 n , m ( 1 ≤ n , m ≤ 80 ) n,m(1 \le n,m \le 80) n,m(1≤n,m≤80),按照下面要求构造一个 n × m n \times m n×m 的矩阵:
[ 1 , n m ] [1,nm] [1,nm] 中的每个整数都恰好出现一次;
最多存在一个 ( x , y ) (x,y) (x,y) 使得 A x , y A_{x,y} Ax,y 满足
KaTeX parse error: No such environment: align* at position 9: \begin{̲a̲l̲i̲g̲n̲*̲}̲ \begin{cas…
问构造矩阵的方案数,对 K ( 1 ≤ K ≤ 1 0 9 ) K(1 \le K \le 10^9) K(1≤K≤109) 取模。
为了处理方便,我们从 n m nm nm 到 1 1 1 倒序填数。
设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示选了 i i i 个数,占领了 j j j 行 k k k 列的方案数,因为新的数必须在之前的数被占领的范围内填,所以有转移
d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] ⋅ ( j ⋅ k − ( i − 1 ) ) + d p [ i − 1 ] [ j − 1 ] [ k ] ⋅ ( n − j + 1 ) ⋅ k + d p [ i − 1 ] [ j ] [ k − 1 ] ⋅ ( m − k + 1 ) ⋅ j \begin{aligned} dp[i][j][k] &= dp[i-1][j][k] \cdot (j \cdot k - (i - 1))\\ &+ dp[i-1][j-1][k] \cdot (n - j + 1) \cdot k\\ &+ dp[i-1][j][k-1] \cdot (m - k + 1) \cdot j\\ \end{aligned} dp[i][j][k]=dp[i−1][j][k]⋅(j⋅k−(i−1))+dp[i−1][j−1][k]⋅(n−j+1)⋅k+dp[i−1][j][k−1]⋅(m−k+1)⋅j
初始值为 d p [ 1 ] [ 1 ] [ 1 ] = n ⋅ m dp[1][1][1]=n \cdot m dp[1][1][1]=n⋅m,其他为 0 0 0。
时间复杂度: O ( n 2 m 2 ) O(n^2m^2) O(n2m2)。
int dp[85 * 85][85][85];
int main(void)
{
int t, n, m, mod;
scanf("%d", &t);
while(t--)
{
scanf("%d%d%d", &n, &m, &mod);
memset(dp, 0, sizeof(dp));
dp[1][1][1] = n * m;
for(int i = 2; i <= n * m; i++)
{
for(int j = 0; j <= n; j++)
{
for(int k = 0; k <= m; k++)
{
if (dp[i - 1][j][k])
{
long long temp;
temp = ((long long)dp[i - 1][j][k] * (j * k - (i - 1))) % mod;
dp[i][j][k] += temp;
dp[i][j][k] %= mod;
temp = ((long long)dp[i - 1][j][k] * (n - j) * k) % mod;
dp[i][j + 1][k] += temp;
dp[i][j + 1][k] %= mod;
temp = ((long long)dp[i - 1][j][k] * (m - k) * j) % mod;
dp[i][j][k + 1] += temp;
dp[i][j][k + 1] %= mod;
}
}
}
}
printf("%d\n", dp[n * m][n][m]);
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6418
题目来源:2018 HDU 多校第九场
两个人玩石头剪刀布卡牌游戏,第一个人有 a a a 张剪刀, b b b 张石头, c c c 张布;第二个人有 a ′ a' a′ 张剪刀, b ′ b' b′ 张石头, c ′ c' c′ 张布。
每一回合,胜方得 1 1 1 分,负方扣 1 1 1 分,平局则两人得分均不变。
在第一个人随机(等概率)出牌,第二个人按照最优策略出牌的情况下,求第二个人期望得到的最高得分。
题目保证 a + b + c = a ′ + b ′ + c ′ a+b+c = a' + b' + c' a+b+c=a′+b′+c′。
提示:第二个人可以根据之前第一个人的出牌情况,来决定当前回合的出牌情况。
总轮数为 a + b + c a+b+c a+b+c。
我们考虑第二个人出三种不同类型的牌时的期望得分:
在期望的角度下,出牌的顺序不影响答案,最终答案为
a ′ ⋅ s c o r e a + b ′ ⋅ s c o r e b + c ′ ⋅ s c o r e c a' \cdot score_a + b' \cdot score_b + c' \cdot score_c a′⋅scorea+b′⋅scoreb+c′⋅scorec
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6425
题目来源:2018 HDU 多校第九场
共有 n n n 个人,分为四类:
一场比赛顺利进行的条件是参与的人里面至少两个人有拍,一个人有球。
每个人都可以决定是否参与,即存在 2 n 2^n 2n 种可能。
问 $2^n - $ 可以顺利进行比赛的情况数。
数据范围: 0 ≤ a , b , c , d ≤ 1 0 7 , n = a + b + c + d ≥ 1 0 \le a,b,c,d \le 10^7, n=a+b+c+d \geq 1 0≤a,b,c,d≤107,n=a+b+c+d≥1。
我们直接计算非法情况。
显然第 1 类人对情况没有任何影响,因此最终的答案可以直接乘上 2 a 2^a 2a,我们暂时不考虑 a a a。
第 4 类人只能一个人上场,否则一定能顺利进行,因此情况数为 d d d。
第 2 类人单独上场的情况数为 2 b − 1 2^b-1 2b−1。
第 3 类人单独上场的情况数为 2 c − 1 2^c-1 2c−1。
上述情况中,没有考虑所有 2 − 4 2-4 2−4 类人均不上场的情况。
下面考虑组合上场的情况:
加上所有 2 − 4 2-4 2−4 类人均不上场的情况,总情况数为
2 a ( d + ( 2 b − 1 ) + ( 1 + d + b ) ( 2 c − 1 ) + 1 ) 2^a\left( d+(2^b-1)+(1+d+b)(2^c-1)+1 \right) 2a(d+(2b−1)+(1+d+b)(2c−1)+1)
预处理 2 2 2 的幂次之后, O ( 1 ) O(1) O(1) 计算即可。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6435
题目来源:2018 HDU 多校第十场 - J 题
现在有 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 把主武器和 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1≤m≤105) 把副武器,每把武器都有一能力值 S ( 0 ≤ S ≤ 1 0 9 ) S(0 \le S \le 10^9) S(0≤S≤109) 和 k ( k ≤ 5 ) k(k \leq 5) k(k≤5) 个属性值 x i ( ∣ x i ∣ ≤ 1 0 9 ) x_i(|x_i| \leq 10^9) xi(∣xi∣≤109)。
你需要选择一把主武器 M W MW MW 和一把副武器 S W SW SW,产生的能量为
S M W + S S W + ∑ i = 1 k ∣ x M W , i − x S W , i ∣ S_{MW} + S_{SW} + \sum_{i=1}^k |x_{MW,i}-x_{SW,i}| SMW+SSW+i=1∑k∣xMW,i−xSW,i∣
问最大可以得到的能量。
注意到
∣ x M W , i − x S W , i ∣ = max ( x M W , i − x S W , i , x S W , i − x M W , i ) |x_{MW,i}- x_{SW,i}|=\max(x_{MW,i}- x_{SW,i}, x_{SW,i}- x_{MW,i}) ∣xMW,i−xSW,i∣=max(xMW,i−xSW,i,xSW,i−xMW,i)
而 k k k 最大只有 5 5 5,所以考虑状态压缩, 0 0 0 表示当前武器这一属性取正值, 1 1 1 表示取负值。
对两种武器分别进行状态枚举,存储每种状态所产生的能量值。
最终的答案为两种武器能量值之和的最大值。
时间复杂度: O ( 2 k ⋅ ( n + m ) ) O(2^k \cdot (n+m)) O(2k⋅(n+m))。
const int MAXN = 40;
long long x[8], maxA[MAXN], maxB[MAXN];
int main(void)
{
int t, n, m, k;
scanf("%d", &t);
while(t--)
{
memset(maxA, 128, sizeof(maxA));
memset(maxB, 128, sizeof(maxB));
scanf("%d%d%d", &n, &m, &k);
for(int i = 0; i < n; i++)
{
long long S;
long long maxx = -1e18;
scanf("%lld", &S);
for(int j = 0; j < k; j++)
scanf("%lld", &x[j]);
for(int j = 0; j < (1 << k); j++)
{
int set = j;
long long sum = S;
for(int l = 0; l < k; l++)
{
int bit = set % 2;
if(bit == 1)
sum += x[l];
else
sum -= x[l];
set /= 2;
}
maxA[j] = max(maxA[j], sum);
}
}
for (int i = 0; i < m; i++)
{
long long S;
long long maxx = -1e18;
scanf("%lld", &S);
for (int j = 0; j < k; j++)
scanf("%lld", &x[j]);
for (int j = 0; j < (1 << k); j++)
{
int set = j;
long long sum = S;
for (int l = 0; l < k; l++)
{
int bit = set % 2;
if (bit == 1)
sum += x[l];
else
sum -= x[l];
set /= 2;
}
maxB[j] = max(maxB[j], sum);
}
}
long long answer = -1e18;
for (int i = 0; i < (1 << k); i++)
answer = max(answer, maxA[i] + maxB[(1 << k) - i - 1]);
printf("%lld\n", answer);
}
return 0;
}