Codeforces Round #608 (Div. 2)
区分度太差了,C1300,然后DE都是2100,都没1600~1900的,假如有多点1900难度的然后突然2400就有可能起飞。补了DE两道2100的题感觉自己实力up了。
A - Suits
题意:给5种资源和2种套餐,套餐的交集只有1种资源,给出套餐的单价,凑出最贵的组合。
题解:交集的资源是最宝贵的,尽可能把交集的资源交给贵的套餐,剩下的尽可能交给另一个。
void test_case() {
ll a,b,c,d,e,f;
cin>>a>>b>>c>>d>>e>>f;
ll sum=0;
if(e>=f){
ll t1=min(a,d);
sum+=t1*e;
a-=t1;
d-=t1;
ll t2=min(min(b,c),d);
sum+=t2*f;
b-=t2;
c-=t2;
d-=t2;
}
else{
ll t2=min(min(b,c),d);
sum+=t2*f;
b-=t2;
c-=t2;
d-=t2;
ll t1=min(a,d);
sum+=t1*e;
a-=t1;
d-=t1;
}
cout<<sum<<endl;
}
B - Blocks
题意:一排黑白格,每次可以选连续的两个格子进行一次翻转,把两个格子的颜色分别换成其相反的颜色。求一种构造使得全黑或者全白。
题解:假如要全黑,那么第一个必须是黑的,假如它本身是黑的必须continue,否则必须翻转,到最后判断最后一个格子是不是也是黑的,假如不是黑的则这个不可能构造出全黑,再反过去判断一次全白就可以了。两种方案都枚举得到的一定就是最佳方案。
int n;
char cs[2005];
bool s[2005];
int ans[2005], atop;
void test_case() {
cin >> n >> (cs + 1);
for(int i = 1; i <= n; ++i)
s[i] = (cs[i] == 'W');
atop = 0;
for(int i = 2; i <= n; ++i) {
if(s[i - 1])
continue;
ans[++atop] = i - 1;
s[i - 1] ^= 1, s[i] ^= 1;
}
if(s[n] == 1) {
printf("%d\n", atop);
for(int i = 1; i <= atop; ++i)
printf("%d%c", ans[i], " \n"[i == atop]);
return;
}
for(int i = 1; i <= n; ++i)
s[i] = (cs[i] == 'B');
atop = 0;
for(int i = 2; i <= n; ++i) {
if(s[i - 1])
continue;
ans[++atop] = i - 1;
s[i - 1] ^= 1, s[i] ^= 1;
}
if(s[n] == 1) {
printf("%d\n", atop);
for(int i = 1; i <= atop; ++i)
printf("%d%c", ans[i], " \n"[i == atop]);
return;
}
puts("-1");
}
C - Shawarma Tent
题意:一个坐标平面,学校在(sx,sy)位置,然后有很多同学分别住在(xi,yi)位置,他们可能重叠但是不会住在学校。设置一个点在学校以外,使得其中一条曼哈顿距离最短路经过它的同学数量最多。
题解:贪心,很显然是放在坐标轴方向上比放在象限中好,统计一下然后乱摸一波,应该不会出现越界的情况不过还是判了。事实上应该答案坐标的初值赋值为什么并不要紧,反正至少会有一个学生带到正确的结果中。注意越界的点是不可能更好的,越界的点经过它的曼哈顿最短路的学生明显是0。
int cnt[8];
void test_case() {
int n, sx, sy;
scanf("%d%d%d", &n, &sx, &sy);
for(int i = 1; i <= n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
x -= sx;
y -= sy;
if(x > 0) {
if(y > 0)
++cnt[1];
else if(y == 0)
++cnt[2];
else
++cnt[3];
} else if(x == 0) {
if(y > 0)
++cnt[0];
else
++cnt[4];
} else {
if(y < 0)
++cnt[5];
else if(y == 0)
++cnt[6];
else
++cnt[7];
}
}
int maxans = 0, maxx = 1, maxy = 1;
if(sx + 1 <= 1e9 && cnt[1] + cnt[2] + cnt[3] >= maxans) {
maxx = sx + 1;
maxy = sy;
maxans = cnt[1] + cnt[2] + cnt[3];
}
if(sy - 1 >= 0 && cnt[3] + cnt[4] + cnt[5] >= maxans) {
maxx = sx;
maxy = sy - 1;
maxans = cnt[3] + cnt[4] + cnt[5];
}
if(sx - 1 >= 0 && cnt[5] + cnt[6] + cnt[7] >= maxans) {
maxx = sx - 1;
maxy = sy;
maxans = cnt[5] + cnt[6] + cnt[7];
}
if(sy + 1 <= 1e9 && cnt[7] + cnt[0] + cnt[1] >= maxans) {
maxx = sx;
maxy = sy + 1;
maxans = cnt[7] + cnt[0] + cnt[1];
}
printf("%d\n", maxans);
printf("%d %d\n", maxx, maxy);
}
D - Portals
题意:你要去征服直线上的按顺序的n个城堡,征服第i个城堡需要拥有至少ai个战士(只是需要拥有,并不会消耗),占领完成之后会获得bi个战士。你可以留下一个战士来驻守城堡,驻守会获得ci的得分。有两种方式驻守:你在i点,那么可以派人驻守i点;或者你在u点,u->v有传送门,那么你可以派人驻守v点。传送门只会通向前面的城堡。不能走回头路,假如未能占领完则输掉。求最大的得分,注意在占领完n号城堡后也可以进行驻守和使用n点的传送门。
题解:连题意都暗示要贪心?看起来很像CCPC网络赛的摸鱼宗师那道题。首先全部不进行驻守强行占领看看是不是有解。然后每一步都会有一个多余值,多余值的最小值就是可以拿去派的,派给全局得分最高的城堡就行了。然后全场的多余值都下降了,会有某个碰到了0,假如最后一个碰到0的城堡后面的城堡还有多余值,那么可以派给这一段城堡能够连向的地方的最高得分。也就是每次是询问一个包含结尾的城堡集及其传送门包含的城堡集里面的最大值。
那么每打一个城堡就把兵留下来并尽可能派往传送门,然后把这些兵放进小根堆里面等待召回,假如需要更多兵的时候就要召回他并清除城堡的得分及占领标记。注意到每个城堡最多收回5000个兵然后派出5000个兵,只有5000个城堡,再套个log就比较爆炸。
但是这个算法很有问题,打了补丁也还是很有问题,问题在于每次贪心取城堡,但是有时候把好的城堡空出来给后面的传送门拿更优。
就算不急着把城堡拿去驻守,那么在士兵不够用的情况肯定要去除一部分驻守的名额,要去除哪些呢?去除最小的显然不对,因为去除大的也有可能后面会有机会重新派过去驻守而那时候有空闲的士兵。发现一个很显然的贪心,派去驻守同一个城堡的时间越靠后越好,所以每个城堡实际上只有最后的指向它的传送门有效。在那个传送门时假如没有空闲的士兵就再也没有机会了。但是我们可以先保持这个城堡的名额,只有当人手不够的时候才会有城堡因为价值最小且目前持有的城堡以后都没有办法再驻守而被淘汰。反向遍历可以计算出每个点时空闲的士兵的数量,会发现其实过了某些险峻的关卡只会空闲度会突然上升。
int a[5005], b[5005], c[5005];
int prefixb[5005], dif[5005];
int from[5005];
vector<int> to[5005];
priority_queue<pii, vector<pii>, greater<pii> >pq;
void test_case() {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d%d%d", &a[i], &b[i], &c[i]);
from[i] = i;
}
for(int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
from[v] = max(from[v], u);
}
for(int i = 1; i <= n; ++i)
to[from[i]].push_back(i);
for(int i = 1; i <= n; ++i) {
prefixb[i] = b[i] + prefixb[i - 1];
dif[i] = k + prefixb[i] - a[i + 1];
}
for(int i = n - 1; i >= 1; --i)
dif[i] = min(dif[i], dif[i + 1]);
if(dif[1] < 0) {
puts("-1");
return;
}
for(int i = 1; i <= n; ++i) {
for(auto &v : to[i])
pq.push({c[v], v});
while(pq.size() > dif[i])
pq.pop();
}
int sum = 0;
while(pq.size()) {
sum += pq.top().first;
pq.pop();
}
printf("%d\n", sum);
}
总结:这类问题不是要拿到城堡的时候立刻按照价值贪心,因为后面有可能会重新更新大的城堡所以有可能现在去除大的城堡更优。先观察出是只有最后一次机会派往城堡时才需要把他派出去,在这之前肯定是带着士兵一起走最好。就算拿到了派往城堡的名额也不是立刻把士兵派过去,是保留派过去的资格直到因为某种原因失去资格。在这里失去资格的原因是因为某个时刻下城堡数严格多于后面要用到的最少的士兵数,就选择最差的城堡淘汰掉。
E - Common Number
为什么,就是不肯先打表呢?
题意:给定n,k<=1e18,对每个1<=v<=n,都进行如下的分解:
f(v)=v-1(v是奇数)
=v/2(v是偶数)
一直分解到0为止,例如:31->30->15->14->7->6->3->2->1->0,这个就称为路径。
求最大的x,且x被经过至少k次。
题解:先打个表观察一下每个数字被遍历多少次,以及分别被谁遍历!
vector<int> cnt[10005];
void f(int x){
printf("i=%d:\n ",x);
int i=x;
while(i){
printf(" %d,",i);
cnt[i].push_back(x);
if(i&1)
--i;
else
i>>=1;
}
puts("");
}
void test_case() {
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
f(i);
for(int i=1;i<=n;++i){
printf("cnt[%d]:\n ",i);
for(auto &j:cnt[i])
printf(" %d,",j);
puts("");
}
}
观察次数可以得到一个很显然的结论:
i是奇数:
dp[i]=dp[i*2]+1
i是偶数:
dp[i]=dp[i+1]+dp[i*2]+1
而且偶数和奇数是分别下降的,可以在这上面二分。
但是这个是没有用的,最坏的情形要把所有的dp值都算出来,比如他要你计算k==n的时候那么就是1,每次只+1的算法肯定T了,还在这里搞什么(奇偶分开的)二分优化?
观察下面的表:
cnt[4]:
4, 5, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
cnt[5]:
5, 10, 11, 20, 21, 22, 23, 40, 41, 42, 43, 44, 45, 46, 47, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
cnt[6]:
6, 7, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 96, 97, 98, 99, 100,
cnt[7]:
7, 14, 15, 28, 29, 30, 31, 56, 57, 58, 59, 60, 61, 62, 63,
cnt[8]:
8, 9, 16, 17, 18, 19, 32, 33, 34, 35, 36, 37, 38, 39, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
cnt[9]:
9, 18, 19, 36, 37, 38, 39, 72, 73, 74, 75, 76, 77, 78, 79,
cnt[10]:
10, 11, 20, 21, 22, 23, 40, 41, 42, 43, 44, 45, 46, 47, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
偶数被2个、4个、8个、16个遍历。
奇数被1个、2个、4个、8个、16个遍历。
仔细观察,还有!
偶数被(1倍开始的)2个、(2倍开始的)4个、(4倍开始的)8个、(8倍开始的)16个遍历。
奇数被(1倍开始的)1个、(2倍开始的)2个、(4倍开始的)4个、(8倍开始的)8个、(16倍开始的)16个遍历。
所以结合前面发现的规律奇偶分离二分,就可以出答案了。做不出来是表打错了。下次可以从这个角度观察一下。需要注意这个时候二分的L和R的值以及退出的条件,这种check(x)有时候会被0卡住卡到TLE。
ll n, k;
bool check(ll x) {
if(!x)
return 0;
if(x & 1) {
ll tmp = 0, cnt = 1;
while(x <= n) {
tmp += min(cnt, n - x + 1);
x <<= 1;
cnt <<= 1;
}
return tmp >= k;
} else {
ll tmp = 0, cnt = 2;
while(x <= n) {
tmp += min(cnt, n - x + 1);
x <<= 1;
cnt <<= 1;
}
return tmp >= k;
}
}
ll bs0() {
ll L = 0, R = n >> 1;
while(1) {
ll M = (L + R) >> 1;
if(L == M) {
if(check(R << 1))
return R << 1;
if(check(L << 1))
return L << 1;
return 0;
}
if(check(M << 1))
L = M;
else
R = M - 1;
}
}
ll bs1() {
ll L = 0, R = (n - 1) >> 1;
while(1) {
ll M = (L + R) >> 1;
if(L == M) {
if(check(R << 1 | 1))
return R << 1 | 1;
if(check(L << 1 | 1))
return L << 1 | 1;
return 0;
}
if(check(M << 1 | 1))
L = M;
else
R = M - 1;
}
}
void test_case() {
scanf("%lld%lld", &n, &k);
printf("%lld\n", max(bs0(), bs1()));
}