2020-11-28 洛谷月赛 LGR-080 解题报告
前排先膜出题人sooke!!!
由于我还不会E和F,先把前四题(div.2四题)写了。
先放个总链接
比赛链接
【LGR-080】洛谷 11 月月赛 II div.1 - 洛谷
【LGR-080】洛谷 11 月月赛 II div.2 - 洛谷
文章目录
A 双生独白
题目链接
题目大意
输入一个16进制颜色编码,输出其“反色”的16进制编码。
反色即: ( R , G , B ) → ( 255 − R , 255 − G , 255 − B ) (R,G,B)\to (255-R, 255-G, 255-B) (R,G,B)→(255−R,255−G,255−B)
解题报告
做法很多,我直接用printf
和scanf
解决了这题!
关键代码
scanf("#%2x%2x%2x", &a, &b, &c);
a = 255 - a, b = 255 - b, c = 255 - c;
printf("#%02X%02X%02X\n", a, b, c);
我们稍加分析。
第1行的%2x
是:限制宽度为2,读入一个16进制数;
第3行的%02X
是:若宽度小于2则用0补到2,输出一个不含前导0x的16进制数,且字母使用大写。(这是x与X的差别)
那么这题就水过去了。
B 天选之人
题目链接
题目大意
构造一组 x i ( 1 ≤ i ≤ n ) x_i(1\le i\le n) xi(1≤i≤n),满足以下条件:
- x i ∈ [ 0 , m ] x_i\in[0,m] xi∈[0,m]
- ∑ i = 1 n x i = k \sum_{i=1}^nx_i=k ∑i=1nxi=k
- 这 n n n个 x i x_i xi中有且仅有 p p p个最大值
Hint
- 1 ≤ p , n ≤ 1 0 5 , 1 ≤ m ≤ 1 0 9 , 0 ≤ k ≤ n m 1\le p,n\le 10^5, 1\le m\le 10^9, 0\le k\le nm 1≤p,n≤105,1≤m≤109,0≤k≤nm
解题报告
首先一个很显然的贪心,我们这 p p p个最大值应该尽可能大。这样的话,一是后面需要选的数少了,二是后面的数可以选的范围也更宽松了。
于是我们的最大值应该取 M x = min { m , ⌊ k p ⌋ } Mx=\min\{m,\lfloor\dfrac{k}{p}\rfloor\} Mx=min{m,⌊pk⌋}
那么剩下的 r e s = k − M x res=k-Mx res=k−Mx分给剩下 n − p n-p n−p个数。
于是再贪心地选择,每个数尽量大。那么都选 min { M x − 1 , r e s } \min\{Mx-1,res\} min{Mx−1,res}。到最后查看 r e s res res是否为0即可。
时间是
O
(
n
)
O(n)
O(n)的其实好像可以O(1)的
代码就不放了。
C 移花接木
论我是怎么丢掉这题的分的。。没考虑 b = 1 b=1 b=1的情况。
1 ≤ a , b ≤ 1 0 9 , 0 ≤ h ≤ 1 0 9 1\le a, b\le 10^9,0\le h\le 10^9 1≤a,b≤109,0≤h≤109
题目链接
题目大意
把一棵高度为 ∞ \infty ∞的满 a a a叉树变为高度为 h h h的满 b b b叉树,有以下两种操作可以使用:
- 删去一棵子树
- 让一棵子树成为另一个节点的儿子( 必须仍保持树的形态)
求最小操作次数,对 1 0 9 + 7 10^9+7 109+7取余。
- 1 ≤ a , b ≤ 1 0 9 , 0 ≤ h ≤ 1 0 9 1\le a,b\le10^9,0\le h\le 10^9 1≤a,b≤109,0≤h≤109
解题报告
PART1: 当 a ≥ b a\ge b a≥b时,我们不增加子树,只砍掉子树。这时我们发现只用操作1就可以了,操作2没有任何的好处。
对于 0 ⋯ h − 1 0 \cdots h-1 0⋯h−1层的节点,我们需要砍掉 ( a − b ) (a-b) (a−b)个子树。显然我们应该从上往下砍。这样以后,第0层砍掉了 ( a − b ) (a-b) (a−b),第1层还剩 b b b个节点,需要砍 b ( a − b ) b(a-b) b(a−b)。一直到第 h − 1 h-1 h−1层还剩 b h − 1 b^{h-1} bh−1个节点,需要砍 b h − 1 ( a − b ) b^{h-1}(a-b) bh−1(a−b)。最后 h h h层全部要砍光,即 b h ⋅ a b^h\cdot a bh⋅a。
最后答案为
( a − b ) ( 1 + b + b 2 + ⋯ + b h − 1 ) + b h ⋅ a (a-b)(1+b+b^2+\cdots+b^{h-1})+b^h\cdot a (a−b)(1+b+b2+⋯+bh−1)+bh⋅a
= ( a − b ) 1 − b h 1 − b + b h ⋅ a =(a-b)\dfrac{1-b^{h}}{1-b}+b^h\cdot a =(a−b)1−b1−bh+bh⋅a
快速幂+逆元计算。注意 b = 1 b=1 b=1的情况!!!
PART2: 当 a < b a<b a<b时,我们应当多多地用操作2。为了减少次数,应选择第 h h h层节点的子树往上移动。容易发现,一个子树往上移,又会多出好多 h h h层的节点,又有许多的子树往上移。所以我们发现:这些子树是移不完的!
于是我们贪心地先一换一,把子树全部往上填,填完了再删去第 h h h层节点的全部子树。最后答案就是 b h ⋅ a b^h\cdot a bh⋅a
D 滴水不漏
题目链接
题目大意
交互题。有 n n n个杯子,编号为 i i i的杯子的容积为 i i i,且一开始装有 a i ( 0 ≤ a i ≤ i ) a_i(0\le a_i\le i) ai(0≤ai≤i)的水量(你并不知道)。每次你可询问 ( i , j ) (i,j) (i,j)。若 i = j i=j i=j,表示询问 i i i有没有满(1/0);若 i ≠ j i\ne j i=j,表示把 i i i里的水往 j j j倒,直到 j j j满或 i i i空,再告诉你 j j j有没有满(1/0)。
- 2 ≤ n ≤ 1000 2\le n\le 1000 2≤n≤1000,交互次数 ≤ 20000 \le20000 ≤20000
解题报告
我们设当前的水量为 b i b_i bi
我们看看倒一次水会发生啥:比如 p → q p\to q p→q,只有两种情况:
- q q q满了,那么我们知道了 b q = q b_q=q bq=q,然而我们就不知道 b p b_p bp了。
- q q q没满,那么我们知道了 b p = 0 b_p=0 bp=0,然而我们还是不知道 b q b_q bq。
有了这个性质,我们就应当用已知推出未知。
显然我们不能在两个未知水量的杯子之间倒水,搞得不好就再也不知道水量了。我们也没必要在已知水量的杯子之间倒来倒去浪费次数。我们每次操作必须在一个未知杯子和已知杯子之间倒水。但是我们发现,这样倒来倒去,未知数并没有减少,问题本质一样的。
所以我们寻求一种手段,能够直接把一个未知变为已知。一般情况下,我们可以把 未知数规约到1号杯子,再通过一次1 1
的询问就可以得到这个时候1号杯子的值!
考虑怎么规约问题。一种很明显的方法就是二进制拆分,即把当前杯子(假设编号为 p p p)与小于它的2的整幂发生关系,然后一通神仙操作(详细请看别的博客或sooke的课件)得到新的 b 1 ⋯ b p b_1\cdots b_p b1⋯bp。由于我们是顺序地求解,现在 1 ⋯ p 1\cdots p 1⋯p杯子之间只会互相发生关系,那么通过“物质守恒定律”,我们有 ∑ i = 1 p a i = ∑ i = 1 p b i \sum_{i=1}^pa_i=\sum_{i=1}^pb_i ∑i=1pai=∑i=1pbi,可以得到 a p = ∑ i = 1 p b i − ∑ i = 1 p − 1 a i a_p=\sum_{i=1}^pb_i-\sum_{i=1}^{p-1}a_i ap=∑i=1pbi−∑i=1p−1ai。
详细代码
const int MAXN = 1005;
int n, b[MAXN], a[MAXN], lg2[MAXN];
int ask(int i, int j) {//pour from i into j
printf("? %d %d\n", i, j); fflush(stdout);
return read();
}
void calc(int p) {
int ans = 0;
for(int i = 1; i <= p; i++) ans += b[i];
for(int i = 1; i < p; i++) ans -= a[i];
a[p] = ans;
}
void solve(int p, int ty = 0) { //calculate a[p](if ty = 1) & b[p]; sure that b[p] < p
if(p == 1) {
b[p] = 0;
if(ty) calc(p);
return ;
}
for(int i = lg2[p-1]; i >= 0; i--) {
int q = 1 << i;
if(b[q] == 0) continue;
int t = ask(q, p);
if(t) {//p is full
b[p] = p;
solve(q);//now q is unsure
if(ty == 1) calc(p);
return ;
} else {//q is empty
b[q] = 0;
}
}
//now b[2^0],b[2^1]... is all empty, and p is not full yet
for(int i = lg2[p-1]; i >= 0; i--) {
int q = 1 << i;
int t = ask(p, q);
if(t) {//q is full
b[q] = q;
} else {//p is empty
b[p] = 0;
solve(q);
if(ty == 1) calc(p);
return ;
}
}
b[p] = 0;
if(ty == 1) calc(p);
}
int main() {
n = read();
lg2[0] = -1;
for(int i = 1; i <= n; i++) lg2[i] = lg2[i >> 1] + 1;
for(int i = 1; i <= n; i++) {
int t = ask(i, i);
if(t) {
a[i] = b[i] = i;
continue;
}
solve(i, 1);
}
printf("! ");
for(int i = 1; i < n; i++) printf("%d ", a[i]);
printf("%d\n", a[n]);
fflush(stdout);
return 0;
}