最近遇到了三道數形結合的題目,不同的動機都直接指向了凸包(凸殼),利用凸殼上斜率(極角)的單調性進行二分。
1 .一個在傻X那裏淘到的一道數據結構題,from spoj:
維護一個數據結構,支持:序列區間加/減一個數, 求區間最大前綴和。
前面的部分是利用塊狀數組平衡複雜度, 最後一步需要維護:
max(s[i] + bj * i);
這裏構造所有的點(i,s[ i ] ), 對這些點求一個凸包(一個上凸殼,一個下凸殼), 當bj爲負時,在s的凸殼上二分(利用叉積),找到變化率剛好無法抵消bj的時候,bj爲正時,同理。
# include <cstdlib>
# include <cstdio>
# include <cmath>
//# define int long long
using namespace std;
const long long oo = floor(1e18);
const int maxs = 1000, maxn = 100000+5;
int size, num, n, m, c, d, sl, sr;
long long begin[maxs], end[maxs], bj[maxs], tot[maxs], lf[maxs], rf[maxs], lz[maxs], rz[maxs];
long long stz[maxn], stf[maxn], pr[maxn], id[maxn], a[maxn];
inline long long max(long long x, long long y)
{
return x > y? x: y;
}
inline long long min(long long x, long long y)
{
return x < y? x: y;
}
inline long long abs1(long long x)
{
return x > 0? x: -x;
}
void change(int x)
{
int i;
for (i = begin[x]; i <= end[x]; i++) a[i] += bj[x];
for (tot[x] = 0, i = begin[x]; i <= end[x]; i++) tot[x] += a[i];
for (pr[begin[x]] = a[begin[x]], i = begin[x]+1; i <= end[x]; i++) pr[i] = pr[i-1] + a[i];
bj[x] = 0;
for (i = lz[x]; i <=rz[x]; stz[i++] = 0);
rz[x] = lz[x];
stz[lz[x]] = begin[x];
for (i = begin[x]+1; i <= end[x]; i++)
{
for (;(rz[x]>=lz[x] && pr[stz[rz[x]]] <= pr[i]) ||
(rz[x]>lz[x] && (abs1(pr[stz[rz[x]]] - pr[stz[rz[x]-1]])) * (i-stz[rz[x]])
>= (abs1(pr[i]-pr[stz[rz[x]]])) * (stz[rz[x]]-stz[rz[x]-1]));
rz[x]--);
stz[++rz[x]] = i;
}
for (i = lf[x]; i <=rf[x]; stf[i++] = 0);
rf[x] = lf[x];
stf[lf[x]] = begin[x];
for (i = begin[x]+1; i <= end[x]; i++)
{
for (;(rf[x]>lf[x] &&
(pr[stf[rf[x]]] - pr[stf[rf[x]-1]]) *(i - stf[rf[x]]) <= (pr[i] - pr[stf[rf[x]]]) * (stf[rf[x]] - stf[rf[x]-1]))
;
rf[x]--);
if (pr[stf[rf[x]]] < pr[i]) stf[++rf[x]] = i;
}
}
long long askmax(int x)
{
if (bj[x] >= 0)
{
long long ask = pr[stz[lz[x]]] + bj[x] * (stz[lz[x]] - begin[x] + 1);
int mid, ll = lz[x]+1, rr = rz[x];
if (ll > rr) return ask;
for (;ll < rr;)
{
mid = (ll + rr + 1) >> 1;
if (abs1(pr[stz[mid]] - pr[stz[mid-1]]) >= bj[x] * (stz[mid] - (stz[mid-1]) )) rr = mid - 1;
else ll = mid;
}
return max(ask, pr[stz[ll]]+bj[x] * (stz[ll] - begin[x]+1));
}
else
{
long long ask = pr[stf[lf[x]]] + bj[x] * (stf[lf[x]] - begin[x] + 1);
int mid, ll = lf[x]+1; int rr = rf[x];
rr = rf[x];
if (ll > rr) return ask;
for (;ll < rr;)
{
mid = (ll + rr + 1) >> 1;
if (pr[stf[mid]] - pr[stf[mid-1]] > abs(bj[x]) * (stf[mid] - (stf[mid-1]) )) ll = mid;
else rr = mid-1;
}
return max(ask, pr[stf[ll]]+bj[x] * (stf[ll]-begin[x]+1));
}
}
void read()
{
int i, j;
scanf("%d%d", &n, &m);
for (i = 1; i <=n; i++) scanf("%I64d", &a[i]);
size = floor(sqrt(n))+1;
for (i = 1, num = 1; i <=n; i+=size, num++)
{
lz[num]=lf[num] = begin[num] = i, end[num] = i+size-1;
if (i + size > n) end[num] = n;
for (j = begin[num]; j <= end[num]; j++)
id[j] = num, tot[num] += a[j];
}
for (i = 1; i <=num; i++)
change(i);
}
void modify(int l,int r, int d)
{
int i;
int gl = id[l], gr = id[r];
if (gr == gl)
{
for (i = l; i <= r; i++) a[i] += d;
change(gl);
}
else
{
for (i = l; i <= end[gl]; i++) a[i] += d;
for (i = begin[gr]; i <= r; i++) a[i] += d;
change(gl); change(gr);
for (i = gl+1; i < gr; i++) bj[i] += d;
}
}
long long query(int l, int r)
{
long long now = 0, ask = -oo;
int i, gl = id[l], gr = id[r];
for (i = 1; i < gl; now += tot[i]+ bj[i] * size, i++);
for (i = begin[gl]; i < l; now += a[i] + bj[gl], i++);
if (gl == gr)
{
for (i = l; i <= r; i++)
{
now += a[i]+bj[gl];
ask = max(ask, now);
}
}
else
{
for (i = l; i <= end[gl]; i++)
{
now += a[i] + bj[gl];
ask = max(ask, now);
}
for (i = gl+1; i < gr; i++)
{
ask = max(ask, now + askmax(i));
now += tot[i] + size * bj[i];
}
for (i = begin[gr]; i <= r; i++)
{
now += a[i] + bj[gr];
ask = max(ask, now);
}
}
return ask;
}
int main()
{
int i;
freopen("notdiff.in", "r", stdin);
freopen("notdiff.out", "w", stdout);
read();
for (i = 1; i <= m; i++)
{
scanf("%d", &c);
if (c == 1)
{
scanf("%d%d%d", &sl, &sr, &d);
modify(sl, sr, d);
}
else
{
scanf("%d%d", &sl, &sr);
printf("%I64d\n", query(sl, sr));
}
}
return 0;
}
2.wc2012 卓亮ppt / coci 2011&2012 contest3
一個工廠製造產品,有N個流程。第i個流程的時間係數是Ti。
有M個產品要製造,第i個產品的容易程度是Fi。一個產品j,在
流程i所需時間爲TiFj。流程順序不可顛倒,產品也必
須按給定的順序製作。一旦一個流程完成,就交給下一個流程。
此時,下一個流程必須是空閒的,不然就會出錯。問完成所有產
品需要的時間。
數據滿足1 ≤ N ≤ 100000,1 ≤ M ≤ 100000。
貪心的認爲,每個產品儘量早的開始生產;
令time[i]爲i產品的開始時間,則for (all j) time[i] + Fi*sigma(Tj) >= time[i+1] +Fi+1 *sigma(Tj-1);
令Si = sigma(Ti);
則time[i +1] - time[i] >= Fi*Sj - Fi+1* Sj-1; 後面就是叉積~\(≧▽≦)/~啦啦啦
如果求出最大叉積,就可以有time[i] 推出time[j];
後面那個式子,就是對於一個(Fi, Fi+1) 求出對於所有的(Sj-1, Sj)中叉積最大且爲正的那個。
那麼對於(Sj-1, Sj)維護一個上凸殼,二分一下就可以了 。
# include <cstdlib>
# include <cmath>
# include <cstdio>
# include <cstring>
using namespace std;
const int maxn = 200000;
struct point
{
long long x, y;
}sd[maxn];
int que[maxn];
int n, m, i;
long long s[maxn];
int e[maxn], t[maxn];
long long cross(point a, point b, point c, point d)
{
long long x1=b.x-a.x,y1=b.y-a.y,x2=d.x-c.x,y2=d.y-c.y;
return x1*y2-x2*y1;
}
void prepare()
{
for (i = 1; i < n; i++)
{
for (;que[0]>1&cross(sd[que[que[0]-1]], sd[i], sd[que[que[0]-1]], sd[que[que[0]]])<=0; que[0]--);
que[++que[0]]= i;
}
/* cut = que[0];
for (i = n-1; i >= 1; i--)
{
for (;que[0]>1&cross(sd[que[0]-1], sd[i], sd[que[0]-1], sd[que[0]])<=0; que[0]--);
que[++que[0]]= i;
}
*/
}
inline long long max(long long x, long long y)
{
return x > y?x: y;
}
long long check(int x, int y)
{
point c; c.x=x; c.y=y;
long long ask = cross(sd[0], c, sd[0], sd[que[que[0]]]);
int mid, l = 1, r = que[0]-1;
for (;l <= r;)
{
mid = l+r>>1;
ask = max(ask, cross(sd[0], c, sd[0], sd[que[mid]]));
if (cross(sd[que[mid]], sd[que[mid+1]], sd[0], c)<0) l = mid+1; else r = mid-1;
}
return ask;
}
int main()
{
freopen("traka.in", "r", stdin);
freopen("traka.out", "w", stdout);
scanf("%d%d", &n, &m);
for (i = 1; i <= n; i++) scanf("%d", &t[i]);
for (i = 1; i <= m; i++) scanf("%d", &e[i]);
for (i = 1; i <= n; i++) s[i] = s[i-1] + t[i];
for (i = 1; i < n; i++) sd[i].x = s[i], sd[i].y = s[i+1];
prepare();
long long btime = 0, inc;
for (i = 1; i < m; i++)
{
inc = max(1LL*e[i]*t[1], check(e[i],e[i+1]));
btime += inc;
}
btime += s[n]* e[m];
printf("%I64d", btime);
return 0;
}
還是wc2012卓亮論文中提到的:
某人要組織一場比賽。她有N道備選題,這場比賽有K題。每位選手要做這K題。
她想,如果選手把這K題全做出來,選手會覺得這個比賽過於簡單,很無趣。
但如果選手只做出了很少的題目,又會覺得很難過。
因此她想選這K道題,使得解出恰好K − 1題的概率儘量大。假設她已經進行了實驗,得出了每道題被解出的概率。1 ≤ K ≤ N ≤ 36。
令ai是選出的第i題做出的概率,
那麼題目是求max((1−a1)a2a3...aK+a1(1−a2)a3a4...aK+···+a1a2...aK−1(1−aK));
化一化,變成pai ai * sigma((1-a[i])/a[i]), 直接裸搜是不行的,考慮折半搜索,那麼左式分成兩部分,會驚喜的發現是一個叉積的形式,求叉積的最大值,那麼先搜左邊一半,建立凸殼,再搜右邊一半,二分即可。
不能輸公式所以寫不清楚,卓亮的論文上倒是很清楚。
# include <cstdlib>
# include <cstdio>
# include <cmath>
# include <cstring>
using namespace std;
const int maxn = 30;
const double eps=1e-10;
struct point
{
double x,y;
};
int have[maxn];
int n, cut, k;
double ans, a[maxn*2];
inline bool bezero(double x)
{
return x<eps&&x>-eps?true:false;
}
inline double cross(point a, point b, point c, point d)
{
double x1=b.x-a.x,y1=b.y-a.y,x2=d.x-c.x,y2=d.y-c.y;
return x1*y2-x2*y1;
}
inline double cj(point a, point b)
{
return a.x*b.y-a.y*b.x;
}
inline double max(double x, double y)
{
return x>y?x:y;
}
struct forkcase
{
int que[100000]; point sd[100000];
void sort(int l, int r)
{
int i = l, j = r; point d = sd[l+r>>1], tmp;
for (;i <= j;)
{
for (;d.x-sd[i].x> eps||(bezero(d.x-sd[i].x) && d.y-sd[i].y> eps);i++);
for (;d.x-sd[j].x<-eps||(bezero(d.x-sd[j].x) && d.y-sd[j].y<-eps);j--);
if (i <= j)
tmp=sd[i], sd[i]=sd[j], sd[j]=tmp,i++,j--;
}
if (i< r) sort(i, r);
if (l< j) sort(l, j);
}
void tidy(int p)
{
int i;
que[0] = 1; que[1] = 1;
for (i = 2; i <= have[p]; i++)
{
for (;que[0]>1 && cross(sd[que[que[0]-1]], sd[que[que[0]]], sd[que[que[0]-1]],sd[i])<=eps; que[0]--);
que[++que[0]] = i;
}
}
void updata(point g, int p)
{
point d=(point){0,0};
if (p < 0) return;
ans = max(ans,
cj(sd[que[que[0]]], g));
int l= 1, r= que[0]-1, mid;
for (;l <= r;)
{
mid = l+r>>1; ans=max(ans, cj(sd[que[mid]],g));
if (cross(d, g, sd[que[mid]], sd[que[mid+1]])<0)
l = mid+1;
else r = mid-1;
}
}
}kcase[maxn];
void dfs1(int now, int tot, double s, double t)
{
int i; if (tot > k) return;
if (tot == k) ans = max(ans, s*t);
have[tot]++;kcase[tot].sd[have[tot]]=(point){t*s, -t};
for (i = now; i <= cut; i++)
dfs1(i+1, tot+1, s+(1-a[i])/a[i], t*a[i]);
}
void dfs2(int now, int tot, double s, double t)
{
if (tot > k) return;
if (tot== k) ans = max(ans, s*t);
int i; point g = (point){t*s, t}; kcase[k-tot].updata(g, k-tot);
for (i = now; i <= n; i++)
dfs2(i+1, tot+1, s+(1-a[i])/a[i], t*a[i]);
}
int main()
{
int i;
freopen("pro.in", "r", stdin);
freopen("pro.out", "w", stdout);
scanf("%d%d", &n, &k);
for (i = 1; i <= n; i++) scanf("%lf", &a[i]);
for (i = 1; i <= n; i++) a[i]/=100;
cut = n/2;
dfs1(1,0,0,1);
for (i = 0; i <= k; i++)
kcase[i].sort(1, have[i]);
for (i = 0; i <= k; i++)
kcase[i].tidy(i);
ans = 0;
dfs2(cut+1,0,0,1);
printf("%.4lf", ans);
return 0;
}