数形结合 + 二分凸壳3题

6 篇文章 0 订阅
3 篇文章 0 订阅


       

        最近遇到了三道数形结合的题目,不同的动机都直接指向了凸包(凸壳),利用凸壳上斜率(极角)的单调性进行二分。

        

        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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值