今天是钟皓曦老师的讲授~
今天的题比昨天的难好多,呜~
T1
我们需要找到一个能量传递最多的异构体就好了;
整体答案由花时间最多的异构体决定;
现在的问题就是这么确定一个异构体在花费时间最优的情况下所花的时间是多少;
我们去枚举一个异构体 i,以这个异构体为分界线将其分成左右两部分,设左半部分的异构体的能量和为 sum;
如果 sum > ( i-1 ) * v,v * n - sum(右部分) < 0 ,说明左边的能量是够的,不需要再输入能量了,但是右部分却不够,所以我们需要从左边往右边输送能量,那么输送的能量的多少就是 i 这个异构体的贡献;
做法:去枚举每一个 i,考虑这个节点它的作用(往哪里传递能量) ,注意一共四种情况:
1. 左边多,右边少;那么 i 节点的作用就是把左边多余的能量传递到右边;
2. 左边少,右边多;那么 i 节点的左右就是把右边多余的能量传递到左边;
3. 左边多,右边也多;也就是说 i 这个点少了好多能量,我们就要把两边多余的能量搬到 i,时间取决于耗时长的一遍:max(x,y);
4. 左边少,右边也少;也就是说 i 这个点多了好多能量,我们要把多余的能量搬到两边,但是注意每一时刻只能搬一点能量,所以需要 x+y 的时间;
所以这个题就做完了;
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; const int maxn=100010; int n,z[maxn]; long long sum[maxn]; void read() { scanf("%d",&n); for (int a=1;a<=n;a++) scanf("%d",&z[a]); } long long work() { for (int a=1;a<=n;a++) sum[a] = sum[a-1]+z[a]; if (sum[n]%n) return -1; long long ans=0,v=sum[n]/n; for (int a=1;a<=n;a++) { long long l=sum[a-1]-1ll*(a-1)*v; long long r=sum[n]-sum[a]-1ll*(n-a)*v; if (l<0 && r<0) ans=max(ans,-l-r); ans=max(ans,max(abs(l),abs(r))); } return ans; } int main() { read(); printf("%lld ",work()); return 0; }
T2
语文太差了,还以为必须要从 1 走到 n,然后 100 -> 0(其实是玄学 RE);
没要求必须从左往右走?炸了~
我们从小到大排好序,这样的话我们选数的时候只需要考虑所选的数是不是前面的数的倍数就行了(因为前面的数一定是更前面所有数的倍数);
f [ i ] 表示已经取了第 i 个数了,我们去枚举前面选的那个数是多少,看是不是它的倍数就可以了:
f [ i ] = max ( f [ j ] ) + 1,a [ i ] 是 a [ j ] 的倍数;
这个题搜索写得好好像可以过。。
我们看到有个很重要的隐藏 bug:有 20% 的数据保证所有城市的数都不重复;
考虑到由于所选的数都是倍数关系,所以这些城市的数最多就 20 个不同的数!
那么我们可以先去重,然后如果一个数 a [ i ] 被选上了,那么和它相等的数也都要选上,这样才能保证最优嘛;
所以我们可以在此基础上搜索,或者按照上面说的那样 dp 一下,就可以过了;
然而不是正解?
正解:
我们可以开一个桶,记录每个数出现了多少次;
改造一下 dp:f [ i ] 我取出来的序列最后一个数等于 i 的情况下最长是多少;
那么接下来我们要取得的是 i 的倍数,所以我们就去枚举 i 的倍数,一直枚举到一百万就行了;
f [ k * i ] = max ( f [ i ] + cnt [ k * i ] );
我们不关心那个数是多少,我们只关心有多少个 。
#include<cstdio> #include<cstdlib> #include<cstring> using namespace std; const int maxn=1000010; int n,cnt[maxn],f[maxn]; int main() { scanf("%d",&n); for (int a=1;a<=n;a++) { int v; scanf("%d",&v); cnt[v]++; f[v]++; } int ans=0; for (int a=1;a<=1000000;a++) if (f[a]) { if (f[a]>ans) ans=f[a]; for (int b=a+a;b<=1000000;b+=a) if (cnt[b] && f[a]+cnt[b]>f[b]) f[b]=f[a]+cnt[b]; } printf("%d ",ans); return 0; }
T3
zhx:明天考试不考 2-SAT,真香~
最小值最大化,我们用二分;
我们二分答案 v,那么我们所选的任意两张卡牌的差值的绝对值都要大于 v;
假如我们有两组卡牌,他们的战斗力分别是 a1 , b1 和 a2 , b2 ;
如果 a1 - a2 < v,那么说明 a1 和 a2 不能同时选,所以如果我们选了 a1 ,那么我们就只能选 b2 了,从 a1 向 b2 ;
这样能过 50% 的数据;
考虑到建边的时间复杂度是 O ( n2 ) 的,显然过不了 100% 的数据;
线段树优化建边 。
我们将这 2n 个数从小到大排序,假如现在我们考虑 ci ,需要和它建边的点一定是一个连续的区间;
发现这是一个区间加边的操作,怎么搞呢?
我们建一棵线段树,然后对线段树进行区间加边的操作,我们可以把线段树上的边也看做是图的一部分的话,那么我们就只需要建 log n 条边:
每次只需要加 log n 条边,总时间复杂度 O ( n log 2 n );
#include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<algorithm> using namespace std; const int BUF_SIZE = 30; char buf[BUF_SIZE], *buf_s = buf, *buf_t = buf + 1; #define PTR_NEXT() { buf_s ++; if (buf_s == buf_t) { buf_s = buf; buf_t = buf + fread(buf, 1, BUF_SIZE, stdin); } } #define readint(_n_) { while (*buf_s != '-' && !isdigit(*buf_s)) PTR_NEXT(); bool register _nega_ = false; if (*buf_s == '-') { _nega_ = true; PTR_NEXT(); } int register _x_ = 0; while (isdigit(*buf_s)) { _x_ = _x_ * 10 + *buf_s - '0'; PTR_NEXT(); } if (_nega_) _x_ = -_x_; (_n_) = (_x_); } #define readstr(_s_) { while (!isupper(*buf_s)) PTR_NEXT(); char register *_ptr_ = (_s_); while (isupper(*buf_s) || *buf_s == '-') { *(_ptr_ ++) = *buf_s; PTR_NEXT(); } (*_ptr_) = '